Skip to content

Commit a9535ea

Browse files
author
Oguz Yuksel
committed
When PresentationState storage is copied (COW), clear effectNavigationIDPath.
This signals that presentation was moved to a new parent context. Detect this as parentContextChanged and recreate effect.
1 parent f8158b9 commit a9535ea

File tree

2 files changed

+34
-95
lines changed

2 files changed

+34
-95
lines changed

[email protected]

Lines changed: 0 additions & 92 deletions
This file was deleted.

Sources/ComposableArchitecture/Reducer/Reducers/PresentationReducer.swift

Lines changed: 34 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,8 @@ public struct PresentationState<State> {
5757

5858
private var storage: Storage
5959
@usableFromInline var presentedID: NavigationIDPath?
60+
// Tracks navigation path when effect was created. Cleared on COW to detect parent context changes.
61+
@usableFromInline var effectNavigationIDPath: NavigationIDPath? = nil
6062

6163
public init(wrappedValue: State?) {
6264
self.storage = Storage(state: wrappedValue)
@@ -67,6 +69,7 @@ public struct PresentationState<State> {
6769
set {
6870
if !isKnownUniquelyReferenced(&self.storage) {
6971
self.storage = Storage(state: newValue)
72+
self.effectNavigationIDPath = nil // Clear on COW to trigger effect recreation
7073
} else {
7174
self.storage.state = newValue
7275
}
@@ -586,6 +589,9 @@ public struct _PresentationReducer<Base: Reducer, Destination: Reducer>: Reducer
586589
public func reduce(into state: inout Base.State, action: Base.Action) -> Effect<Base.Action> {
587590
let initialPresentationState = state[keyPath: self.toPresentationState]
588591
let presentationAction = self.toPresentationAction.extract(from: action)
592+
593+
// Capture the navigation ID BEFORE any state changes (like StackReducer does with idsBefore)
594+
let navigationIDBeforeReduce = initialPresentationState.wrappedValue.map(self.navigationIDPath(for:))
589595

590596
let destinationEffects: Effect<Base.Action>
591597
let baseEffects: Effect<Base.Action>
@@ -657,9 +663,24 @@ public struct _PresentationReducer<Base: Reducer, Destination: Reducer>: Reducer
657663
baseEffects = self.base.reduce(into: &state, action: action)
658664
}
659665

666+
let navigationIDAfterReduce = state[keyPath: self.toPresentationState].wrappedValue.map(self.navigationIDPath(for:))
667+
let storedEffectPath = initialPresentationState.effectNavigationIDPath
668+
669+
// Detect parent context change (e.g., enum case transition via COW)
670+
let parentContextChanged: Bool
671+
if let current = navigationIDAfterReduce {
672+
if let stored = storedEffectPath {
673+
parentContextChanged = stored != current
674+
} else {
675+
parentContextChanged = true // nil path + existing presentation = COW occurred
676+
}
677+
} else {
678+
parentContextChanged = false
679+
}
680+
let navigationContextChanged = navigationIDBeforeReduce != navigationIDAfterReduce
681+
660682
let presentationIdentityChanged =
661-
initialPresentationState.presentedID
662-
!= state[keyPath: self.toPresentationState].wrappedValue.map(self.navigationIDPath(for:))
683+
initialPresentationState.presentedID != navigationIDAfterReduce
663684

664685
let dismissEffects: Effect<Base.Action>
665686
if presentationIdentityChanged,
@@ -676,14 +697,24 @@ public struct _PresentationReducer<Base: Reducer, Destination: Reducer>: Reducer
676697

677698
if presentationIdentityChanged, state[keyPath: self.toPresentationState].wrappedValue == nil {
678699
state[keyPath: self.toPresentationState].presentedID = nil
700+
state[keyPath: self.toPresentationState].effectNavigationIDPath = nil
679701
}
680702

681703
let presentEffects: Effect<Base.Action>
682-
if presentationIdentityChanged || state[keyPath: self.toPresentationState].presentedID == nil,
704+
// Recreate Empty effect if:
705+
// - Parent context changed (e.g., enum case transition)
706+
// - Navigation context changed (normal navigation)
707+
// - Presentation identity changed
708+
// - First time presenting (presentedID is nil)
709+
if (parentContextChanged
710+
|| navigationContextChanged
711+
|| presentationIdentityChanged
712+
|| state[keyPath: self.toPresentationState].presentedID == nil),
683713
let presentationState = state[keyPath: self.toPresentationState].wrappedValue,
684714
!isEphemeral(presentationState)
685715
{
686716
let presentationDestinationID = self.navigationIDPath(for: presentationState)
717+
state[keyPath: self.toPresentationState].effectNavigationIDPath = presentationDestinationID
687718
state[keyPath: self.toPresentationState].presentedID = presentationDestinationID
688719
presentEffects = .concatenate(
689720
.publisher { Empty(completeImmediately: false) }

0 commit comments

Comments
 (0)