-
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathList.swift
More file actions
93 lines (80 loc) · 3.21 KB
/
Copy pathList.swift
File metadata and controls
93 lines (80 loc) · 3.21 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
//
// List.swift
// AndroidSwiftUICore
//
// A lazy list: rows are evaluated on demand through an item provider, so a
// large list contributes one node plus the rows Compose actually asks for.
// This is the only unbounded-tree case, and the reason the schema carries
// `count` and `itemProviderID`.
//
public struct List<Data: RandomAccessCollection, ID: Hashable, RowContent: View>: View {
internal let data: Data
internal let id: (Data.Element) -> ID
internal let row: (Data.Element) -> RowContent
public init(_ data: Data, id: @escaping (Data.Element) -> ID, @ViewBuilder rowContent: @escaping (Data.Element) -> RowContent) {
self.data = data
self.id = id
self.row = rowContent
}
public typealias Body = Never
}
public extension List where Data.Element: Identifiable, ID == Data.Element.ID {
init(_ data: Data, @ViewBuilder rowContent: @escaping (Data.Element) -> RowContent) {
self.init(data, id: { $0.id }, rowContent: rowContent)
}
}
extension List: PrimitiveView {
public func _render(in context: ResolveContext) -> RenderNode {
// snapshot the data and row builder; the provider resolves each row on
// demand, keyed by element id so row @State survives scroll and reorder
let elements = Array(data)
let rowBuilder = row
let keyFor = id
let listPath = context.path
let storage = context.storage
let callbacks = context.callbacks
let environment = context.environment
let providerID = callbacks.register(.item { index in
let element = elements[index]
let key = "#\(identityString(keyFor(element)))"
var rowContext = ResolveContext(
storage: storage,
callbacks: callbacks,
environment: environment,
path: listPath + "/" + key
)
return Evaluator.resolve(rowBuilder(element), rowContext)
})
let keys = elements.map { PropValue.string(identityString(keyFor($0))) }
var props: [String: PropValue] = [
"keys": .array(keys),
"itemProvider": .int(Int(providerID)),
]
if let onRefresh = context.refreshSink?.action {
let refreshID = callbacks.register(.void {
Task { await onRefresh() }
})
props["onRefresh"] = .int(Int(refreshID))
}
return RenderNode(type: "List", id: context.path, props: props, count: elements.count)
}
}
// MARK: - Refreshable
public struct _RefreshableView<Content: View>: View {
internal let action: @Sendable () async -> Void
internal let content: Content
public typealias Body = Never
}
extension _RefreshableView: _ResolutionEffectView {
public func _applyEffect(_ context: inout ResolveContext) -> any View {
// attach the refresh action to the List the content resolves to
if context.refreshSink == nil { context.refreshSink = RefreshSink() }
context.refreshSink?.action = action
return content
}
}
public extension View {
func refreshable(action: @escaping @Sendable () async -> Void) -> _RefreshableView<Self> {
_RefreshableView(action: action, content: self)
}
}