Skip to content

Commit cfc9867

Browse files
committed
added node reusing bypass after discovering first duplicate
1 parent 58965eb commit cfc9867

File tree

3 files changed

+57
-36
lines changed

3 files changed

+57
-36
lines changed

Examples/Sources/GreetingGeneratorExample/GreetingGeneratorApp.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ struct GreetingGeneratorApp: App {
3939
.padding(.top, 20)
4040

4141
ScrollView {
42-
ForEach(greetings.reversed()[1...]) { greeting in
42+
ForEach(items: greetings.reversed()[1...]) { greeting in
4343
Text(greeting)
4444
}
4545
}

Examples/Sources/StressTestExample/StressTestApp.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ struct StressTestApp: App {
4747
}
4848
if let values = values[tab!] {
4949
ScrollView {
50-
ForEach(items: values) { value in
50+
ForEach(values, id: \.self) { value in
5151
Text(value)
5252
}
5353
}.frame(minWidth: 300)

Sources/SwiftCrossUI/Views/ForEach.swift

Lines changed: 55 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@ extension ForEach: TypeSafeView, View where Child: View {
9696

9797
var layoutableChildren: [LayoutSystem.LayoutableChild] = []
9898

99+
let oldNodes = children.nodes
99100
let oldMap = children.nodeIdentifierMap
100101
let oldIdentifiers = children.identifiers
101102
let identifiersStart = oldIdentifiers.startIndex
@@ -108,52 +109,63 @@ extension ForEach: TypeSafeView, View where Child: View {
108109
// still exists in the new one is reinserted to ensure that items are
109110
// rendered in the correct order.
110111
var requiresOngoingReinsertion = false
112+
var ongoingNodeReusingDisabled = false
113+
var inserted = false
111114

112115
for element in elements {
113116
let childContent = child(element)
114117
let node: AnyViewGraphNode<Child>
115118

116-
if let oldNode = oldMap[element[keyPath: idKeyPath]] {
117-
node = oldNode
118-
119-
// Checks if there is a preceding item that was not preceding in
120-
// the previous update. If such an item exists, it means that
121-
// the order of the collection has changed or that an item was
122-
// inserted somewhere in the middle, rather than simply appended.
123-
requiresOngoingReinsertion =
124-
requiresOngoingReinsertion
125-
|| {
126-
guard
127-
let ownOldIndex = oldIdentifiers.firstIndex(
128-
of: element[keyPath: idKeyPath])
129-
else { return false }
130-
131-
let subset = oldIdentifiers[identifiersStart..<ownOldIndex]
132-
return !children.identifiers.subtracting(subset).isEmpty
133-
}()
134-
135-
if requiresOngoingReinsertion {
136-
removeChild(oldNode.widget.into())
137-
addChild(oldNode.widget.into())
119+
(inserted, _) = children.identifiers.append(element[keyPath: idKeyPath])
120+
ongoingNodeReusingDisabled = ongoingNodeReusingDisabled || !inserted
121+
122+
if !ongoingNodeReusingDisabled {
123+
if let oldNode = oldMap[element[keyPath: idKeyPath]] {
124+
node = oldNode
125+
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.
130+
requiresOngoingReinsertion =
131+
requiresOngoingReinsertion
132+
|| {
133+
guard
134+
let ownOldIndex = oldIdentifiers.firstIndex(
135+
of: element[keyPath: idKeyPath])
136+
else { return false }
137+
138+
let subset = oldIdentifiers[identifiersStart..<ownOldIndex]
139+
return !children.identifiers.subtracting(subset).isEmpty
140+
}()
141+
142+
if requiresOngoingReinsertion {
143+
removeChild(oldNode.widget.into())
144+
addChild(oldNode.widget.into())
145+
}
146+
} else {
147+
// New Items need ongoing reinsertion to get
148+
// displayed at the correct locat ion.
149+
requiresOngoingReinsertion = true
150+
node = AnyViewGraphNode(
151+
for: childContent,
152+
backend: backend,
153+
environment: environment
154+
)
155+
addChild(node.widget.into())
138156
}
157+
children.nodeIdentifierMap[element[keyPath: idKeyPath]] = node
139158
} else {
140-
// New Items need ongoing reinsertion to get
141-
// displayed at the correct locat ion.
142-
requiresOngoingReinsertion = true
143159
node = AnyViewGraphNode(
144160
for: childContent,
145161
backend: backend,
146162
environment: environment
147163
)
148-
addChild(node.widget.into())
149164
}
150165

151-
children.nodeIdentifierMap[element[keyPath: idKeyPath]] = node
152-
children.identifiers.append(element[keyPath: idKeyPath])
153-
154166
children.nodes.append(node)
155167

156-
if children.isFirstUpdate {
168+
if children.isFirstUpdate, !ongoingNodeReusingDisabled {
157169
addChild(node.widget.into())
158170
}
159171

@@ -173,10 +185,19 @@ extension ForEach: TypeSafeView, View where Child: View {
173185

174186
children.isFirstUpdate = false
175187

176-
for removed in oldMap.filter({
177-
!children.identifiers.contains($0.key)
178-
}).values {
179-
removeChild(removed.widget.into())
188+
if !ongoingNodeReusingDisabled {
189+
for removed in oldMap.filter({
190+
!children.identifiers.contains($0.key)
191+
}).values {
192+
removeChild(removed.widget.into())
193+
}
194+
} else {
195+
for nodeToRemove in oldNodes {
196+
removeChild(nodeToRemove.widget.into())
197+
}
198+
for nodeToAdd in children.nodes {
199+
addChild(nodeToAdd.widget.into())
200+
}
180201
}
181202

182203
return LayoutSystem.updateStackLayout(

0 commit comments

Comments
 (0)