Skip to content

Commit 116b72c

Browse files
committed
Made it work for non identifiables
StressTestExample is broken
1 parent e9b94a8 commit 116b72c

File tree

2 files changed

+134
-61
lines changed

2 files changed

+134
-61
lines changed

Sources/SwiftCrossUI/Builders/MenuItemsBuilder.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ public struct MenuItemsBuilder {
1818
}
1919

2020
public static func buildPartialBlock<Items: Collection>(
21-
first: ForEach<Items, [MenuItem]>
21+
first: ForEach<Items, MenuItem, [MenuItem]>
2222
) -> [MenuItem] {
2323
first.elements.map(first.child).flatMap { $0 }
2424
}
@@ -53,7 +53,7 @@ public struct MenuItemsBuilder {
5353

5454
public static func buildPartialBlock<Items: Collection>(
5555
accumulated: [MenuItem],
56-
next: ForEach<Items, [MenuItem]>
56+
next: ForEach<Items, MenuItem, [MenuItem]>
5757
) -> [MenuItem] {
5858
accumulated + buildPartialBlock(first: next)
5959
}

Sources/SwiftCrossUI/Views/ForEach.swift

Lines changed: 132 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -1,86 +1,55 @@
11
import OrderedCollections
22

33
/// A view that displays a variable amount of children.
4-
public struct ForEach<Items: Collection, Child> where Items.Index == Int {
4+
public struct ForEach<Items: Collection, ID: Hashable, Child> where Items.Index == Int {
55
/// A variable-length collection of elements to display.
66
var elements: Items
77
/// A method to display the elements as views.
88
var child: (Items.Element) -> Child
9+
/// The path to the property used as Identifier
10+
var idKeyPath: KeyPath<Items.Element, ID>
911
}
1012

11-
extension ForEach where Child == [MenuItem] {
12-
/// Creates a view that creates child views on demand based on a collection of data.
13-
@_disfavoredOverload
13+
extension ForEach: TypeSafeView, View where Child: View {
14+
typealias Children = ForEachViewChildren<Items, ID, Child>
1415
public init(
1516
_ elements: Items,
16-
@MenuItemsBuilder _ child: @escaping (Items.Element) -> [MenuItem]
17+
id keyPath: KeyPath<Items.Element, ID>,
18+
@ViewBuilder _ child: @escaping (Items.Element) -> Child
1719
) {
1820
self.elements = elements
1921
self.child = child
20-
}
21-
}
22-
23-
extension ForEach where Items == [Int] {
24-
/// Creates a view that creates child views on demand based on a given ClosedRange
25-
@_disfavoredOverload
26-
public init(
27-
_ range: ClosedRange<Int>,
28-
child: @escaping (Int) -> Child
29-
) {
30-
self.elements = Array(range)
31-
self.child = child
22+
self.idKeyPath = keyPath
3223
}
3324

34-
/// Creates a view that creates child views on demand based on a given Range
35-
@_disfavoredOverload
36-
public init(
37-
_ range: Range<Int>,
38-
child: @escaping (Int) -> Child
39-
) {
40-
self.elements = Array(range)
41-
self.child = child
42-
}
43-
}
44-
45-
extension ForEach: TypeSafeView, View where Child: View, Items.Element: Identifiable {
46-
typealias Children = ForEachViewChildren<Items, Child>
47-
4825
public var body: EmptyView {
4926
return EmptyView()
5027
}
5128

52-
/// Creates a view that creates child views on demand based on a collection of data.
53-
public init(
54-
_ elements: Items,
55-
@ViewBuilder _ child: @escaping (Items.Element) -> Child
56-
) {
57-
self.elements = elements
58-
self.child = child
59-
}
60-
6129
func children<Backend: AppBackend>(
6230
backend: Backend,
6331
snapshots: [ViewGraphSnapshotter.NodeSnapshot]?,
6432
environment: EnvironmentValues
65-
) -> ForEachViewChildren<Items, Child> {
66-
return ForEachViewChildren(
33+
) -> Children {
34+
return Children(
6735
from: self,
6836
backend: backend,
37+
idKeyPath: idKeyPath,
6938
snapshots: snapshots,
7039
environment: environment
7140
)
7241
}
7342

7443
func asWidget<Backend: AppBackend>(
75-
_ children: ForEachViewChildren<Items, Child>,
44+
_ children: Children,
7645
backend: Backend
7746
) -> Backend.Widget {
7847
return backend.createContainer()
7948
}
8049

8150
func update<Backend: AppBackend>(
8251
_ widget: Backend.Widget,
83-
children: ForEachViewChildren<Items, Child>,
52+
children: Children,
8453
proposedSize: SIMD2<Int>,
8554
environment: EnvironmentValues,
8655
backend: Backend,
@@ -116,7 +85,6 @@ extension ForEach: TypeSafeView, View where Child: View, Items.Element: Identifi
11685

11786
var layoutableChildren: [LayoutSystem.LayoutableChild] = []
11887

119-
let oldNodes = children.nodes
12088
let oldMap = children.nodeIdentifierMap
12189
let oldIdentifiers = children.identifiers
12290
let identifiersStart = oldIdentifiers.startIndex
@@ -130,11 +98,11 @@ extension ForEach: TypeSafeView, View where Child: View, Items.Element: Identifi
13098
// rendered in the correct order.
13199
var requiresOngoingReinsertion = false
132100

133-
for (i, element) in elements.enumerated() {
101+
for element in elements {
134102
let childContent = child(element)
135103
let node: AnyViewGraphNode<Child>
136104

137-
if let oldNode = oldMap[element.id] {
105+
if let oldNode = oldMap[element[keyPath: idKeyPath]] {
138106
node = oldNode
139107

140108
// Checks if there is a preceding item that was not preceding in
@@ -145,7 +113,8 @@ extension ForEach: TypeSafeView, View where Child: View, Items.Element: Identifi
145113
requiresOngoingReinsertion
146114
|| {
147115
guard
148-
let ownOldIndex = oldIdentifiers.firstIndex(of: element.id)
116+
let ownOldIndex = oldIdentifiers.firstIndex(
117+
of: element[keyPath: idKeyPath])
149118
else { return false }
150119

151120
let subset = oldIdentifiers[identifiersStart..<ownOldIndex]
@@ -158,7 +127,7 @@ extension ForEach: TypeSafeView, View where Child: View, Items.Element: Identifi
158127
}
159128
} else {
160129
// New Items need ongoing reinsertion to get
161-
// displayed at the correct location.
130+
// displayed at the correct locat ion.
162131
requiresOngoingReinsertion = true
163132
node = AnyViewGraphNode(
164133
for: childContent,
@@ -168,8 +137,8 @@ extension ForEach: TypeSafeView, View where Child: View, Items.Element: Identifi
168137
addChild(node.widget.into())
169138
}
170139

171-
children.nodeIdentifierMap[element.id] = node
172-
children.identifiers.append(element.id)
140+
children.nodeIdentifierMap[element[keyPath: idKeyPath]] = node
141+
children.identifiers.append(element[keyPath: idKeyPath])
173142

174143
children.nodes.append(node)
175144

@@ -222,17 +191,18 @@ extension ForEach: TypeSafeView, View where Child: View, Items.Element: Identifi
222191
/// when elements are added/removed.
223192
class ForEachViewChildren<
224193
Items: Collection,
194+
ID: Hashable,
225195
Child: View
226-
>: ViewGraphNodeChildren where Items.Index == Int, Items.Element: Identifiable {
196+
>: ViewGraphNodeChildren {
227197
/// The nodes for all current children of the ``ForEach`` view.
228198
var nodes: [AnyViewGraphNode<Child>] = []
229199

230200
/// The nodes for all current children of the ``ForEach`` view, queriable by their identifier.
231-
var nodeIdentifierMap: [Items.Element.ID: AnyViewGraphNode<Child>]
201+
var nodeIdentifierMap: [ID: AnyViewGraphNode<Child>]
232202

233203
/// The identifiers of all current children ``ForEach`` view in the order they are displayed.
234204
/// Can be used for checking if an element was moved or an element was inserted in front of it.
235-
var identifiers: OrderedSet<Items.Element.ID>
205+
var identifiers: OrderedSet<ID>
236206

237207
/// Changes queued during `dryRun` updates.
238208
var queuedChanges: [Change] = []
@@ -254,13 +224,14 @@ class ForEachViewChildren<
254224

255225
/// Gets a variable length view's children as view graph node children.
256226
init<Backend: AppBackend>(
257-
from view: ForEach<Items, Child>,
227+
from view: ForEach<Items, ID, Child>,
258228
backend: Backend,
229+
idKeyPath: KeyPath<Items.Element, ID>,
259230
snapshots: [ViewGraphSnapshotter.NodeSnapshot]?,
260231
environment: EnvironmentValues
261232
) {
262-
var nodeIdentifierMap = [Items.Element.ID: AnyViewGraphNode<Child>]()
263-
var identifiers = OrderedSet<Items.Element.ID>()
233+
var nodeIdentifierMap = [ID: AnyViewGraphNode<Child>]()
234+
var identifiers = OrderedSet<ID>()
264235
var viewNodes = [AnyViewGraphNode<Child>]()
265236

266237
for (index, element) in view.elements.enumerated() {
@@ -278,11 +249,113 @@ class ForEachViewChildren<
278249
let anyViewGraphNode = AnyViewGraphNode(viewGraphNode)
279250
viewNodes.append(anyViewGraphNode)
280251

281-
identifiers.append(element.id)
282-
nodeIdentifierMap[element.id] = anyViewGraphNode
252+
identifiers.append(element[keyPath: idKeyPath])
253+
nodeIdentifierMap[element[keyPath: idKeyPath]] = anyViewGraphNode
283254
}
284255
nodes = viewNodes
285256
self.identifiers = identifiers
286257
self.nodeIdentifierMap = nodeIdentifierMap
287258
}
288259
}
260+
261+
// MARK: - Alternative Initializers
262+
extension ForEach where Items.Element: Hashable, ID == Items.Element {
263+
@_disfavoredOverload
264+
public init(
265+
items elements: Items,
266+
_ child: @escaping (Items.Element) -> Child
267+
) {
268+
self.elements = elements
269+
self.child = child
270+
self.idKeyPath = \.self
271+
}
272+
}
273+
274+
extension ForEach where Child == [MenuItem], Items.Element: Hashable, ID == Items.Element {
275+
/// Creates a view that creates child views on demand based on a collection of data.
276+
@_disfavoredOverload
277+
public init(
278+
items elements: Items,
279+
@MenuItemsBuilder _ child: @escaping (Items.Element) -> [MenuItem]
280+
) {
281+
self.elements = elements
282+
self.child = child
283+
self.idKeyPath = \.self
284+
}
285+
}
286+
287+
extension ForEach where Child == [MenuItem] {
288+
/// Creates a view that creates child views on demand based on a collection of data.
289+
@_disfavoredOverload
290+
public init(
291+
_ elements: Items,
292+
id keyPath: KeyPath<Items.Element, ID>,
293+
@MenuItemsBuilder _ child: @escaping (Items.Element) -> [MenuItem]
294+
) {
295+
self.elements = elements
296+
self.child = child
297+
self.idKeyPath = keyPath
298+
}
299+
}
300+
301+
extension ForEach where Items == [Int], ID == Items.Element {
302+
/// Creates a view that creates child views on demand based on a given ClosedRange
303+
@_disfavoredOverload
304+
public init(
305+
_ range: ClosedRange<Int>,
306+
child: @escaping (Int) -> Child
307+
) {
308+
self.elements = Array(range)
309+
self.child = child
310+
self.idKeyPath = \.self
311+
}
312+
313+
/// Creates a view that creates child views on demand based on a given Range
314+
@_disfavoredOverload
315+
public init(
316+
_ range: Range<Int>,
317+
child: @escaping (Int) -> Child
318+
) {
319+
self.elements = Array(range)
320+
self.child = child
321+
self.idKeyPath = \.self
322+
}
323+
}
324+
325+
extension ForEach where Items == [Int] {
326+
/// Creates a view that creates child views on demand based on a given ClosedRange
327+
@_disfavoredOverload
328+
public init(
329+
_ range: ClosedRange<Int>,
330+
id keyPath: KeyPath<Items.Element, ID>,
331+
child: @escaping (Int) -> Child
332+
) {
333+
self.elements = Array(range)
334+
self.child = child
335+
self.idKeyPath = keyPath
336+
}
337+
338+
/// Creates a view that creates child views on demand based on a given Range
339+
@_disfavoredOverload
340+
public init(
341+
_ range: Range<Int>,
342+
id keyPath: KeyPath<Items.Element, ID>,
343+
child: @escaping (Int) -> Child
344+
) {
345+
self.elements = Array(range)
346+
self.child = child
347+
self.idKeyPath = keyPath
348+
}
349+
}
350+
351+
extension ForEach where Items.Element: Identifiable, ID == Items.Element.ID {
352+
/// Creates a view that creates child views on demand based on a collection of identifiable data.
353+
public init(
354+
_ elements: Items,
355+
child: @escaping (Items.Element) -> Child
356+
) {
357+
self.elements = elements
358+
self.child = child
359+
self.idKeyPath = \.id
360+
}
361+
}

0 commit comments

Comments
 (0)