I have a similar problem. My example is a bit more involved but arguably more generic. Hope it helps.
My TCA setup looks like this:
struct State: Equatable {
var child: ChildState?
// ...
}
One can get the child store by scoping the parent store:
let childStore = parentStore.scope { $0.child ?? ChildState() }
And the child view’s body
looks like this:
var body: some View {
WithViewStore(childStore) { viewStore in
// ...
}
}
Here is what happened:
- Some reducer sets
parent.child = nil
.
- This notifies every scoped store that the root state changes.
- Each scoped store then applies the scope function to get a new child state, and compares it with the previous one. More likely than not, we get a different child state. Some
objectWillChange
is called.
- The corresponding
WithViewStore
is marked dirty.
- The content closure in
WithViewStore
is called, and generates a new child view tree.
- SwiftUI then generates an animation from the old child view tree to the new child view tree, in addition to the dismissal animation for the whole child view tree.
My solution is to kill step 3. First, let's extend the child state with another property:
struct ChildState: Equatable {
var isBeingDismissed: Bool = false
}
Second, when scoping, let's do:
let childStore = parentStore.scope { x -> ChildState in
if let child = x.child {
return child
}
var child = ChildState()
child.isBeingDismissed = true
return true
}
Lastly, we can modify the WithViewStore
with a custom removeDuplicates
closure:
var body: some View {
WithViewStore(childStore) { old, new in
if old == new {
return true
}
// Key
if new.isBeingDismissed {
return true
}
return false
} content: { viewStore in
// ...
}
}
Here is how it works. The scoped child state can be nil
in two cases, when the view is first created and the view is being dismissed. In the first case, the old is nil
. In the second case, the new is nil
. The custom removeDuplicates
essentially does this: when a child state is being dismissed, we skip any update. So the only animation being played is the transition animation. The content stays the same.