@@ -83,6 +83,8 @@ extension ForEach: TypeSafeView, View where Child: View {
8383 children. queuedChanges = [ ]
8484 }
8585
86+ // Use the previous update Method when no keyPath is set on a
87+ // [Hashable] Collection to optionally keep the old behaviour.
8688 guard let idKeyPath else {
8789 return deprecatedUpdate (
8890 widget,
@@ -109,24 +111,32 @@ extension ForEach: TypeSafeView, View where Child: View {
109111 // still exists in the new one is reinserted to ensure that items are
110112 // rendered in the correct order.
111113 var requiresOngoingReinsertion = false
114+
115+ // Forces node recreation when enabled (expensive on large Collections).
116+ // Use only when idKeyPath yields non-unique values. Prefer Identifiable
117+ // or guaranteed unique, constant identifiers for optimal performance.
118+ // Node caching and diffing require unique, stable IDs.
112119 var ongoingNodeReusingDisabled = false
120+
121+ // Avoid reallocation
113122 var inserted = false
114123
115124 for element in elements {
116125 let childContent = child ( element)
117126 let node : AnyViewGraphNode < Child >
118127
128+ // Track duplicates: inserted=false if ID exists.
129+ // Disables node reuse if any duplicate gets found.
119130 ( inserted, _) = children. identifiers. append ( element [ keyPath: idKeyPath] )
120131 ongoingNodeReusingDisabled = ongoingNodeReusingDisabled || !inserted
121132
122133 if !ongoingNodeReusingDisabled {
123134 if let oldNode = oldMap [ element [ keyPath: idKeyPath] ] {
124135 node = oldNode
125136
126- // Checks if there is a preceding item that was not preceding in
127- // the previous update. If such an item exists, it means that
128- // the order of the collection has changed or that an item was
129- // inserted somewhere in the middle, rather than simply appended.
137+ // Detects reordering or mid-collection insertion:
138+ // Checks if there is a preceding item that was not
139+ // preceding in the previous update.
130140 requiresOngoingReinsertion =
131141 requiresOngoingReinsertion
132142 || {
@@ -139,20 +149,24 @@ extension ForEach: TypeSafeView, View where Child: View {
139149 return !children. identifiers. subtracting ( subset) . isEmpty
140150 } ( )
141151
142- if requiresOngoingReinsertion {
152+ // Removes node from its previous position and
153+ // re-adds it at the new correct one.
154+ if requiresOngoingReinsertion, !children. isFirstUpdate {
143155 removeChild ( oldNode. widget. into ( ) )
144156 addChild ( oldNode. widget. into ( ) )
145157 }
146158 } else {
147159 // New Items need ongoing reinsertion to get
148- // displayed at the correct locat ion .
160+ // displayed at the correct location .
149161 requiresOngoingReinsertion = true
150162 node = AnyViewGraphNode (
151163 for: childContent,
152164 backend: backend,
153165 environment: environment
154166 )
155- addChild ( node. widget. into ( ) )
167+ if !children. isFirstUpdate {
168+ addChild ( node. widget. into ( ) )
169+ }
156170 }
157171 children. nodeIdentifierMap [ element [ keyPath: idKeyPath] ] = node
158172 } else {
@@ -165,10 +179,6 @@ extension ForEach: TypeSafeView, View where Child: View {
165179
166180 children. nodes. append ( node)
167181
168- if children. isFirstUpdate, !ongoingNodeReusingDisabled {
169- addChild ( node. widget. into ( ) )
170- }
171-
172182 layoutableChildren. append (
173183 LayoutSystem . LayoutableChild (
174184 update: { proposedSize, environment, dryRun in
@@ -183,9 +193,11 @@ extension ForEach: TypeSafeView, View where Child: View {
183193 )
184194 }
185195
186- children. isFirstUpdate = false
187-
188- if !ongoingNodeReusingDisabled {
196+ if children. isFirstUpdate {
197+ for nodeToAdd in children. nodes {
198+ addChild ( nodeToAdd. widget. into ( ) )
199+ }
200+ } else if !ongoingNodeReusingDisabled {
189201 for removed in oldMap. filter ( {
190202 !children. identifiers. contains ( $0. key)
191203 } ) . values {
@@ -200,6 +212,8 @@ extension ForEach: TypeSafeView, View where Child: View {
200212 }
201213 }
202214
215+ children. isFirstUpdate = false
216+
203217 return LayoutSystem . updateStackLayout (
204218 container: widget,
205219 children: layoutableChildren,
@@ -434,7 +448,7 @@ extension ForEach where Child == [MenuItem], Items.Element: Hashable, ID == Item
434448 )
435449 @_disfavoredOverload
436450 public init (
437- _ elements: Items ,
451+ menuItems elements: Items ,
438452 @MenuItemsBuilder _ child: @escaping ( Items . Element ) -> [ MenuItem ]
439453 ) {
440454 self . elements = elements
@@ -447,7 +461,7 @@ extension ForEach where Child == [MenuItem] {
447461 /// Creates a view that creates child views on demand based on a collection of data.
448462 @_disfavoredOverload
449463 public init (
450- _ elements: Items ,
464+ menuItems elements: Items ,
451465 id keyPath: KeyPath < Items . Element , ID > ,
452466 @MenuItemsBuilder _ child: @escaping ( Items . Element ) -> [ MenuItem ]
453467 ) {
0 commit comments