-
Notifications
You must be signed in to change notification settings - Fork 1.6k
Open
Labels
bugSomething isn't working due to a bug in the library.Something isn't working due to a bug in the library.
Description
Description
When using StackState-based navigation on iOS 16.0, edge swipe back-and-cancel causes the state to become inconsistent: after cancelling, the screen stays visible but its dismiss effect no longer works.
To Reproduce
- Build and run this minimal TCA sample (see code below)
- Tap 'Go to screen A' to push ScreenA
- On ScreenA, start an edge swipe pop, but cancel before finishing (do not complete the pop)
- Try tapping the "Dismiss" button: it no longer works.
Simulator.Screen.Recording.-.iPhone.X.-.2025-05-03.at.04.40.06.mp4
Minimal Sample
referred to Examples/CaseStudies/SwiftUICaseStudies/04-NavigationStack.swift
@Reducer
struct NavigationDemo {
@Reducer
enum Path {
case screenA(ScreenA)
}
@ObservableState
struct State: Equatable {
var path = StackState<Path.State>()
}
enum Action {
case path(StackActionOf<Path>)
}
var body: some Reducer<State, Action> {
Reduce { _, _ in .none }
.forEach(\.path, action: \.path)
}
}
extension NavigationDemo.Path.State: Equatable {}
struct NavigationDemoView: View {
@Perception.Bindable var store: StoreOf<NavigationDemo>
var body: some View {
WithPerceptionTracking {
NavigationStack(path: $store.scope(state: \.path, action: \.path)) {
Form {
Section {
NavigationLink(
"Go to screen A",
state: NavigationDemo.Path.State.screenA(ScreenA.State())
)
.navigationTitle("Root")
}
}
} destination: { store in
WithPerceptionTracking {
switch store.case {
case let .screenA(store):
ScreenAView(store: store)
}
}
}
}
}
}
@Reducer
struct ScreenA {
@ObservableState
struct State: Equatable {}
enum Action {
case dismissButtonTapped
}
@Dependency(\.dismiss) var dismiss
var body: some Reducer<State, Action> {
Reduce { state, action in
switch action {
case .dismissButtonTapped:
return .run { _ in
await self.dismiss()
}
}
}
}
}
struct ScreenAView: View {
let store: StoreOf<ScreenA>
var body: some View {
WithPerceptionTracking {
Form {
Section {
Button("Dismiss") {
store.send(.dismissButtonTapped)
}
}
Section {
NavigationLink(
"Go to screen A",
state: NavigationDemo.Path.State.screenA(ScreenA.State())
)
}
}
}
.navigationTitle("Screen A")
}
}Additional context
Doing the same with pure SwiftUI (without TCA) does NOT reproduce the issue,
so it appears to be specific to TCA's handling of StackState and navigation state recovery.
struct SwiftUINavigationDemoView: View {
enum Screen: Hashable {
case screenA
}
@State private var navigationPath: [Screen] = []
var body: some View {
NavigationStack(path: $navigationPath) {
Form {
Section {
NavigationLink("Go to screen A", value: Screen.screenA)
.navigationTitle("Root")
}
}
.navigationDestination(for: Screen.self) { screen in
switch screen {
case .screenA:
SwiftUIScreenAView(navigationPath: $navigationPath)
}
}
}
}
}
struct SwiftUIScreenAView: View {
@Binding var navigationPath: [SwiftUINavigationDemoView.Screen]
@Environment(\.dismiss) private var dismiss
var body: some View {
Form {
Section {
Button("Dismiss") {
dismiss()
}
}
Section {
NavigationLink(
"Go to screen A",
value: SwiftUINavigationDemoView.Screen.screenA
)
}
}
.navigationTitle("Screen A")
}
}Checklist
- I have determined whether this bug is also reproducible in a vanilla SwiftUI project.
- If possible, I've reproduced the issue using the
mainbranch of this package. - This issue hasn't been addressed in an existing GitHub issue or discussion.
Expected behavior
After cancelling the pop gesture, the screen's effects (e.g. dismiss effect) should keep working as before.
Actual behavior
the screen stays visible but its dismiss effect no longer works.
Reproducing project
The Composable Architecture version information
at least since 1.18.0 until dd34949
Destination operating system
Simulator 16.0, Device 16.0.3. Simulator 16.1 does not reproduce the bug
Xcode version information
Version 16.2 (16C5032a)
Swift Compiler version information
swift-driver version: 1.115.1 Apple Swift version 6.0.3 (swiftlang-6.0.3.1.10 clang-1600.0.30.1)uhooi, SNQ-2001 and aking618
Metadata
Metadata
Assignees
Labels
bugSomething isn't working due to a bug in the library.Something isn't working due to a bug in the library.