11import 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.
223192class 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