Skip to content

Commit 7f01d6a

Browse files
committed
Support ContentRootView with a view model
1 parent 13b652b commit 7f01d6a

File tree

1 file changed

+66
-17
lines changed

1 file changed

+66
-17
lines changed

HeaderViewer/ContentView.swift

Lines changed: 66 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,11 @@ import SwiftUI
99
import ClassDump
1010

1111
struct ContentView: View {
12-
@StateObject private var listings: RuntimeListings = .shared
13-
1412
@State private var selectedObject: RuntimeObjectType?
1513

1614
var body: some View {
1715
NavigationSplitView {
1816
ContentRootView(selectedObject: $selectedObject)
19-
.environmentObject(listings)
2017
} detail: {
2118
if let selectedObject {
2219
NavigationStack {
@@ -40,34 +37,86 @@ private enum RuntimeTypeSearchScope: Hashable {
4037
case protocols
4138
}
4239

43-
struct ContentRootView: View {
44-
@EnvironmentObject private var listings: RuntimeListings
40+
private extension RuntimeTypeSearchScope {
41+
var includesClasses: Bool {
42+
switch self {
43+
case .all: true
44+
case .classes: true
45+
case .protocols: false
46+
}
47+
}
48+
var includesProtocols: Bool {
49+
switch self {
50+
case .all: true
51+
case .classes: false
52+
case .protocols: true
53+
}
54+
}
55+
}
56+
57+
private class ContentRootViewModel: ObservableObject {
58+
let runtimeListings: RuntimeListings = .shared
4559

46-
@Binding var selectedObject: RuntimeObjectType?
47-
@State private var searchString: String = ""
48-
@State private var searchScope: RuntimeTypeSearchScope = .all
60+
@Published var searchString: String
61+
@Published var searchScope: RuntimeTypeSearchScope
62+
63+
@Published private(set) var runtimeObjects: [RuntimeObjectType] // filtered based on search
4964

50-
private var runtimeObjects: [RuntimeObjectType] {
65+
private static func runtimeObjectsFor(classNames: [String], protocolNames: [String], searchString: String, searchScope: RuntimeTypeSearchScope) -> [RuntimeObjectType] {
5166
var ret: [RuntimeObjectType] = []
52-
if searchScope != .protocols {
53-
ret += listings.classList.map { .class(named: $0) }
67+
if searchScope.includesClasses {
68+
ret += classNames.map { .class(named: $0) }
5469
}
55-
if searchScope != .classes {
56-
ret += listings.protocolList.map { .protocol(named: $0) }
70+
if searchScope.includesProtocols {
71+
ret += protocolNames.map { .protocol(named: $0) }
5772
}
5873
if searchString.isEmpty { return ret }
5974
return ret.filter { $0.name.localizedCaseInsensitiveContains(searchString) }
6075
}
6176

77+
init() {
78+
let searchString = ""
79+
let searchScope: RuntimeTypeSearchScope = .all
80+
81+
self.searchString = searchString
82+
self.searchScope = searchScope
83+
self.runtimeObjects = Self.runtimeObjectsFor(
84+
classNames: runtimeListings.classList, protocolNames: runtimeListings.protocolList,
85+
searchString: searchString, searchScope: searchScope
86+
)
87+
88+
let debouncedSearch = $searchString
89+
.debounce(for: 0.08, scheduler: RunLoop.main)
90+
91+
$searchScope
92+
.combineLatest(debouncedSearch, runtimeListings.$classList, runtimeListings.$protocolList) {
93+
Self.runtimeObjectsFor(
94+
classNames: $2, protocolNames: $3,
95+
searchString: $1, searchScope: $0
96+
)
97+
}
98+
.assign(to: &$runtimeObjects)
99+
}
100+
}
101+
102+
struct ContentRootView: View {
103+
@StateObject private var viewModel: ContentRootViewModel
104+
@Binding var selectedObject: RuntimeObjectType?
105+
106+
init(selectedObject: Binding<RuntimeObjectType?>) {
107+
_viewModel = StateObject(wrappedValue: ContentRootViewModel())
108+
_selectedObject = selectedObject
109+
}
110+
62111
var body: some View {
63112
NavigationStack {
64-
let runtimeObjects = self.runtimeObjects
113+
let runtimeObjects = viewModel.runtimeObjects
65114
ListView(runtimeObjects, selection: $selectedObject) { runtimeObject in
66115
RuntimeObjectRow(type: runtimeObject)
67116
}
68117
.id(runtimeObjects) // don't try to diff the List
69-
.searchable(text: $searchString)
70-
.searchScopes($searchScope) {
118+
.searchable(text: $viewModel.searchString)
119+
.searchScopes($viewModel.searchScope) {
71120
Text("All")
72121
.tag(RuntimeTypeSearchScope.all)
73122
Text("Classes")
@@ -88,7 +137,7 @@ struct ContentRootView: View {
88137
ImageClassPicker(namedNode: namedNode, selection: $selectedObject)
89138
} else {
90139
NamedNodeView(node: namedNode)
91-
.environmentObject(listings)
140+
.environmentObject(RuntimeListings.shared)
92141
}
93142
}
94143
}

0 commit comments

Comments
 (0)