Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
3 tasks done
Neckk opened this issue Feb 11, 2025 · 1 comment
Open
3 tasks done

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

Neckk opened this issue Feb 11, 2025 · 1 comment
Labels
bug Something isn't working due to a bug in the library.

Comments

@Neckk
Copy link

Neckk commented Feb 11, 2025

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
@Neckk Neckk added the bug Something isn't working due to a bug in the library. label Feb 11, 2025
@iampatbrown
Copy link
Contributor

iampatbrown commented Mar 1, 2025

Hey @Neckk,

Thanks for providing so much info :) I have a feeling this is due to an underlying SwiftUI issue. You can reproduce something similar in vanilla SwiftUI with the following:

import SwiftUI

@main
struct TestApp: App {
  var body: some Scene {
    WindowGroup {
      NavigationStack {
        TreeNavigationView()
      }
    }
  }
}

struct TreeNavigationView: View {
  @State var isPresented = false
  var dismiss: () -> Void = {}
  var body: some View {
    Form {
      Button("Show nested") { isPresented = true }
      Button("Dismiss") { dismiss() }
    }
    .navigationDestination(isPresented: $isPresented) {
      TreeNavigationView { isPresented = false }
    }
  }
}

There were a few navigation improvements that came with iOS 16.4 which I believe resolved the above issue. This seems to be the case with your demo project as well.

Are you able check iOS 16.4 to see if the issue is still there?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working due to a bug in the library.
Projects
None yet
Development

No branches or pull requests

2 participants