Multiple NavigationLink with composable architecture #670
-
I create a screen that has two NavigationLink on a single screen and find something like bug so I would like to ask if there is a solution for this. That app has states for each parent and child, and count up int value in the child view. The following is a sample application I created. public struct MainState: Equatable {
var detailState = DetailState()
public init() { }
}
public enum MainAction: Equatable {
case detail(DetailAction)
}
public struct MainEnvironment { }
public let contentReducer = Reducer<MainState, MainAction, MainEnvironment>.combine(
Reducer<DetailState, DetailAction, DetailEnvironment> { state, action ,env in
switch action {
case .buttonTapped:
state.count = state.count + 1
return .none
}
}.pullback(state: \MainState.detailState, action: /MainAction.detail, environment: { _ in DetailEnvironment()}),
.init({ state, action, environment in
return .none
}))
struct MainView: View {
let store: Store<MainState, MainAction>
init() {
self.store = .init(initialState: .init(), reducer: contentReducer, environment: MainEnvironment())
}
var body: some View {
NavigationView{
WithViewStore(self.store) { viewStore in
VStack {
Text("count: \(viewStore.detailState.count)")
.padding()
NavigationLink(
destination: DetailView(store: self.store.scope(state: { $0.detailState}, action: MainAction.detail )),
label: {
Text("Detail")
})
NavigationLink(
destination: Text("Other"),
label: {
Text("Other")
}
)
}
}
}
}
}
// MARK: - DetailView
public struct DetailState: Equatable {
var count: Int = 0
public init() { }
}
public enum DetailAction: Equatable {
case buttonTapped
}
public struct DetailEnvironment {
}
struct DetailView: View {
var store: Store<DetailState, DetailAction>
var body: some View {
NavigationView {
WithViewStore(self.store) { viewStore in
VStack {
Text("count:\(viewStore.count)")
Button(action: {
viewStore.send(.buttonTapped)
}, label: {
Text("Increment")
})
}
}
}
}
} Is this a bug of some kind? I would like to know if there is any solution or workaround. |
Beta Was this translation helpful? Give feedback.
Replies: 3 comments 1 reply
-
@satoshin21 This is perhaps a SwiftUI bug that was fixed in Xcode 13, but it has to do with how much state is being observed. Right now The Composable Architecture comes with tools for focussing on just the state a view cares about via You can update var body: some View {
NavigationView{
VStack {
WithViewStore(self.store.scope(state: \.detailState.count)) { viewStore in
Text("count: \(viewStore.state)")
.padding()
}
NavigationLink(
destination: DetailView(store: self.store.scope(state: \.detailState, action: MainAction.detail )),
label: {
Text("Detail")
})
NavigationLink(
destination: Text("Other"),
label: {
Text("Other")
}
)
}
}
} Note that:
|
Beta Was this translation helpful? Give feedback.
-
This is indeed a SwiftUI bug introduced in iOS 14.5. I've been able to reproduce it in vanilla SwiftUI. |
Beta Was this translation helpful? Give feedback.
-
I face same issue, but for me I use selection and tag I didn't know how to do samething using NavigationLink that depend on section and tag |
Beta Was this translation helpful? Give feedback.
@satoshin21 This is perhaps a SwiftUI bug that was fixed in Xcode 13, but it has to do with how much state is being observed. Right now
MainView
is observing all of app state, and in earlier versions of SwiftUI, this could lead to recreatingNavigationLink
s on state updates (sinceWithViewStore
's content is re-computed, which will reinitialize theNavigationLink
each time), and this can pop you from a presented view.The Composable Architecture comes with tools for focussing on just the state a view cares about via
WithViewStore
andStore.scope
, you just have to be careful with any store you pass toWithViewStore
. This means trying not to observe child state in a view that also presents t…