-
Notifications
You must be signed in to change notification settings - Fork 61
Expand file tree
/
Copy pathHitsList.swift
More file actions
150 lines (131 loc) · 4.23 KB
/
HitsList.swift
File metadata and controls
150 lines (131 loc) · 4.23 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
//
// HitsList.swift
//
//
// Created by Vladislav Fitc on 29/03/2021.
//
#if !InstantSearchCocoaPods
import InstantSearchCore
#endif
#if canImport(Combine) && canImport(SwiftUI) && (arch(arm64) || arch(x86_64))
import Combine
import SwiftUI
/// A view presenting the list of search hits
@available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 7.0, *)
public struct HitsList<Row: View, Item: Codable, NoResults: View>: View {
@ObservedObject public var hitsObservable: HitsObservableController<Item>
/// Closure constructing a hit row view
public var row: (Item?, Int) -> Row
/// Closure constructing a no results view-
public var noResults: (() -> NoResults)?
public init(_ hitsObservable: HitsObservableController<Item>,
@ViewBuilder row: @escaping (Item?, Int) -> Row,
@ViewBuilder noResults: @escaping () -> NoResults) {
self.hitsObservable = hitsObservable
self.row = row
self.noResults = noResults
}
public var body: some View {
if let noResults = noResults?(), hitsObservable.hits.isEmpty {
noResults
} else {
if #available(iOS 14.0, OSX 11.0, tvOS 14.0, *) {
ScrollView(showsIndicators: false) {
LazyVStack {
ForEach(0..<hitsObservable.hits.count, id: \.self) { index in
row(atIndex: index)
}
}
}.id(hitsObservable.scrollID)
} else {
List(0..<hitsObservable.hits.count, id: \.self) { index in
row(atIndex: index)
}.id(hitsObservable.scrollID)
}
}
}
private func row(atIndex index: Int) -> some View {
row(hitsObservable.hits[index], index).onAppear {
hitsObservable.notifyAppearanceOfHit(atIndex: index)
}
}
}
@available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 7.0, *)
public extension HitsList where NoResults == Never {
init(_ hitsObservable: HitsObservableController<Item>,
@ViewBuilder row: @escaping (Item?, Int) -> Row) {
self.hitsObservable = hitsObservable
self.row = row
noResults = nil
}
}
@available(iOS 13.0, tvOS 13.0, watchOS 7.0, *)
struct HitsView_Previews: PreviewProvider {
struct PreviewRecord<Value: Codable>: Codable {
let objectID: ObjectID
let value: Value
init(_ value: Value, objectID: ObjectID = ObjectID(rawValue: UUID().uuidString)) {
self.value = value
self.objectID = objectID
}
static func withValue(_ value: Value) -> Self {
.init(value)
}
}
static let rawHits: Data = """
{
"hits": [
{
"objectID": "1",
"value": "h1"
},
{
"objectID": "2",
"value": "h2"
}
]
}
""".data(using: .utf8)!
static let hitsController: HitsObservableController<PreviewRecord<String>> = .init()
static let interactor = HitsInteractor<PreviewRecord<String>>(infiniteScrolling: .off, showItemsOnEmptyQuery: true)
static var previews: some View {
NavigationView {
HitsList(hitsController) { string, _ in
VStack {
HStack {
Text(string?.value ?? "---")
.frame(maxWidth: .infinity, minHeight: 30, maxHeight: .infinity, alignment: .leading)
.padding(.horizontal, 16)
}
Divider()
}
} noResults: {
Text("No results")
}
.padding(.top, 20)
.onAppear {
hitsController.hitsSource = interactor
let results = try! JSONDecoder().decode(SearchResponse.self, from: rawHits)
interactor.onResultsUpdated.subscribe(with: hitsController) { (reb, hit) in
reb.reload()
}
interactor.update(results)
}
.navigationBarTitle("Hits")
}
NavigationView {
HitsList(hitsController) { string, _ in
VStack {
HStack {
Text(string?.value ?? "---")
}
Divider()
}
} noResults: {
Text("No results")
}
.navigationBarTitle("Hits")
}
}
}
#endif