Skip to content

dismiss() doesn't pop the screen on iOS 16 #3594

Open
@Neckk

Description

@Neckk

Description

Hey 👋

I have an issue with dismissing screens from reducers on iOS 16.

I use a tree based navigation in my project. The problem happens only on iOS 16.X and only when I drill down at least 2 times. I tried 2 approaches:

  • using @Dependency(\.dismiss) var dismiss in the current reducer
  • delegating dismissal to parent reducer, and then parent setting its state.destination to nil

Neither of them works for me. It looks like the reducer is removed from the navigation tree properly, but then NavigationStack fails to refresh and empties the current screen without pop. Here's a video demonstrating the problem:
https://github.com/user-attachments/assets/5b861536-e179-4184-b36b-b986050f2169

Funnily enough, it works properly if we drill down only once:
https://github.com/user-attachments/assets/2626e88a-0e3c-4dc9-9f9d-a5526c9ca6ce

Checklist

  • I have determined whether this bug is also reproducible in a vanilla SwiftUI project.
  • If possible, I've reproduced the issue using the main branch of this package.
  • This issue hasn't been addressed in an existing GitHub issue or discussion.

Expected behavior

When drilling down in a tree based navigation at least 2 times and then using dismiss() from reducer, it should pop the current screen from the NavigationStack.

Here is a recording of the same code working as expected on iOS 17:
https://github.com/user-attachments/assets/75b5227e-f762-4c10-8ecc-58d09ddf65f8

Actual behavior

When drilling down in a tree based navigation at least 2 times and then using dismiss() from reducer, it empties the view of latest screen. After using native Navigation bar back button from this state, it pops the screen, but the previous one is unresponsive. Only after popping again using native navigation bar it starts working properly again.

Reproducing project

Here's the code used in the recordings:

import ComposableArchitecture
import SwiftUI

@main
struct TestApp: App {
    var body: some Scene {
        WithPerceptionTracking {
            WindowGroup {
                NavigationStack {
                    TreeNavigationView(store: .init(initialState: .init(), reducer: {
                        TreeNavigation()
                    }))
                }
            }
        }
    }
}

struct TreeNavigationView: View {
    @Perception.Bindable var store: StoreOf<TreeNavigation>

    var body: some View {
        WithPerceptionTracking {
            Form {
                Button("Show nested") { store.send(.showNested) }
                Button("Dismiss") { store.send(.dismiss) }
            }
            .navigationDestination(item: $store.scope(state: \.destination?.nested, action: \.destination.nested)) { store in
                WithPerceptionTracking {
                    TreeNavigationView(store: store)
                }
            }
        }
    }
}

@Reducer
struct TreeNavigation {
    @Dependency(\.dismiss) var dismiss

    @Reducer(state: .equatable)
    enum Destination {
        case nested(TreeNavigation)
    }

    @ObservableState
    struct State: Equatable {
        @Presents var destination: Destination.State?
    }

    enum Action {
        case destination(PresentationAction<Destination.Action>)

        case showNested
        case dismiss
    }

    var body: some Reducer<State, Action> {
        Reduce { state, action in
            switch action {
            case .showNested:
                state.destination = .nested(.init())

               // Alternative approach using parent to nil its destination
//            case .destination(.presented(.nested(.dismiss))):
//                state.destination = nil
//            case .dismiss:
//                break

            case .dismiss:
                return .run { _ in await dismiss() }

            case .destination:
                break
            }

            return .none
        }
        .ifLet(\.$destination, action: \.destination)
    }
}

Available also in form of a complete mini project:
NavTest.zip

The Composable Architecture version information

1.17.1

Destination operating system

iOS 16.X

Xcode version information

Version 15.4 (15F31d)

Swift Compiler version information

swift-driver version: 1.90.11.1 Apple Swift version 5.10 (swiftlang-5.10.0.13 clang-1500.3.9.4)
Target: arm64-apple-macosx14.0

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working due to a bug in the library.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions