Replies: 6 comments
-
It looks like you’re missing WithPerceptionTracking inside your navigation stack’s destination closure, so iOS 16 cannot detect changes to the state inside the presented view. |
Beta Was this translation helpful? Give feedback.
-
Thank you for your response. Based on your suggestion, I wrapped the navigation stack's destination closure with WithPerceptionTracking. However, there have been no changes. I tried a different approach. I replaced NavigationStack with NavigationStackStore, and then it worked correctly on iOS16. I think there might be a slight difference between NavigationStack and NavigationStackStore.
|
Beta Was this translation helpful? Give feedback.
-
@tsudo-kiroru the purple warnings are your clue that you're definitely missing a |
Beta Was this translation helpful? Give feedback.
-
@lukeredpath
However, the navigation feature does not work correctly on iOS 16. There seems to be a difference between iOS 16 and iOS 17(As same as capturing movie). |
Beta Was this translation helpful? Give feedback.
-
Hi @tsudo-kiroru, I believe this is just a problem with vanilla SwiftUI in general. The following code snippet works fine in iOS 17, but does not work in iOS 16: class Model: ObservableObject {
@Published var values: [Int] = []
}
struct ParentView: View {
@StateObject var model = Model()
var body: some View {
Form {
Button("Open") {
model.values.append(1)
}
.sheet(
isPresented: Binding(
get: { !model.values.isEmpty },
set: { if !$0 { model.values = [] } }
)
) {
NavigationStack(path: $model.values) {
EmptyView()
.navigationDestination(for: Int.self) { int in
Form {
Button("Push") {
model.values.append(.random(in: 0...1_000))
}
}
}
}
}
}
}
} Since this is not a TCA issue I am going to convert it to a discussion, but please feel free to continue the conversation over there. |
Beta Was this translation helpful? Give feedback.
-
I can reproduce this bug. So there is no doubt this is a issue with Vanilla SwiftUI. That's make sense why you close this issue. import SwiftUI
import ComposableArchitecture
enum RootTab: Int, Hashable, CaseIterable {
case home = 0
case settings = 1
}
struct RootView: View {
@State var store = Store(initialState: RootStore.State()) {
RootStore()
}
@State var selection = RootTab.home
var body: some View {
WithPerceptionTracking {
TabView(selection: $selection) {
VStack {
Text("Home")
Button("push nav") {
store.send(.pushNav)
}
}
.tabItem { Text("home") }
.tag(RootTab.home)
Text("Settings")
.tabItem { Text("settings") }
.tag(RootTab.settings)
}
.onChange(of: selection) { newValue in
store.send(.selectTab(newValue))
selection = newValue
}
.fullScreenCover(
isPresented: $store.presenting.sending(\.onPresent)
) {
NavigationStackStore(self.store.scope(state: \.path, action: \.path)) {
EmptyView()
} destination: { store in
SwitchStore(store) {
switch $0 {
case .demo:
CaseLet(/RootStore.Path.State.demo,
action: RootStore.Path.Action.demo) { _store in
DemoView(store: _store)
}
}
}
}
}
}
.alert(store: self.store.scope(state: \.$alert, action: \.alert))
}
}
@Reducer
struct RootStore {
@ObservableState
struct State: Equatable {
@Presents var alert: AlertState<Action.Alert>?
var count = 0
var path = StackState<Path.State>()
var presenting: Bool {
return path.count > 0
}
}
enum Action {
case alert(PresentationAction<Alert>)
@CasePathable
enum Alert {
case none
}
case selectTab(RootTab)
case pushNav
case path(StackAction<Path.State, Path.Action>)
case onPresent(Bool)
}
var body: some Reducer<State, Action> {
Reduce { state, action in
switch action {
case .pushNav:
state.path.append(.demo(.init()))
return .none
case .selectTab(_):
return .none
case let .path(action):
switch action {
case .element(id: _, action: .demo(.push(let num))):
state.path.append(.demo(.init(count: num)))
default: break
}
return .none
default:
return .none
}
}
.forEach(\.path, action: \.path)
}
@Reducer(state: .equatable)
enum Path {
case demo(DemoStore)
}
}
struct DemoView: View {
@State var store = Store(initialState: DemoStore.State()) {
DemoStore()
}
var body: some View {
WithPerceptionTracking {
Form {
Section {
Text("\(store.count)")
}
Section {
Button("Push") {
store.send(.onTapButton)
}
Button("Dismiss") {
store.send(.dismissButtonTapped)
}
}
}
.navigationTitle("Demo \(store.count)")
}
}
}
@Reducer
struct DemoStore {
@Dependency(\.dismiss) var dismiss
@ObservableState
struct State: Equatable {
var count = 0
}
enum Action: Equatable {
case onTapButton
case push(Int)
case dismissButtonTapped
}
var body: some Reducer<State, Action> {
Reduce { state, action in
switch action {
case .onTapButton:
return .send(.push(state.count + 1))
case .push:
return .none
case .dismissButtonTapped:
return .run { _ in
await self.dismiss()
}
}
}
}
} |
Beta Was this translation helpful? Give feedback.
-
Description
I created a simple Navigation Application using TCA.
https://github.com/kiroru/tca_ver_verify
This application works well on iOS17, but does not work correctly on iOS16.
Please check attached movie.
compilation_capture.mov
Runtime warning is shown below on iOS16.
I created the same app overwritten by TCA v1.6.0 to know what is going on. I'm surprised that v1.6.0 is correctly works. I doubt that TCA does not track perceptible state to do with NavigationStack on iOS 16.
Regards.
Checklist
main
branch of this package.Expected behavior
NavigationStack is not pushed.
Actual behavior
NavigationStack should be pushed.
Steps to reproduce
The Composable Architecture version information
1.9.2
Destination operating system
iOS 16
Xcode version information
15.3
Swift Compiler version information
No response
Beta Was this translation helpful? Give feedback.
All reactions