Problem in nested navigation #1639
-
Currently trying to develop registration process. As I tap the blue button located below, it should navigate to next view. However, for some reason, as I try to navigate to third screen, it then comes back to second screen. I am wondering whether it is a swiftui, tca bug, or whether have I done something wrong... Expectation: NameView -> GenderView -> BirthdayView Here are some code blocks: NameCore logic is structured as below struct NameRegister: ReducerProtocol {
struct State: Equatable {
@BindableState var name: String = ""
var isConfirmButtonDisabled: Bool = true
var genderRegisterState: GenderRegister.State?
@BindableState var isNavigationActive: Bool = false
}
enum Action: BindableAction, Equatable {
case binding(BindingAction<State>)
case confirmButtonTapped
case genderRegisterAction(GenderRegister.Action)
}
var body: some ReducerProtocol<State, Action> {
CombineReducers {
BindingReducer()
Reduce { state, action in
switch action {
case .confirmButtonTapped:
state.isNavigationActive = true
state.genderRegisterState = GenderRegister.State()
return .none
case .binding(\.$name):
state.isConfirmButtonDisabled = self.isInvalidName(checking: state)
return .none
case .binding:
return .none
case .genderRegisterAction:
return .none
}
}
}
.ifLet(
\.genderRegisterState,
action: /Action.genderRegisterAction
) {
GenderRegister()
}
}
.
.
.
} NavigationLink is structured as below: struct NameView: View {
var body: some View {
.
.
.
NavigationLink(
destination: IfLetStore(
self.store.scope(
state: \.genderRegisterState,
action: { .genderRegisterAction($0) }
),
then: { store in
GenderRegisterView(store: store) }
),
isActive: viewStore.binding(\.$isNavigationActive),
label: {
ConfirmButton(
title: .init(ProjectString.Signup.confirmButtonTitle),
disabled: viewStore.isConfirmButtonDisabled,
action: {
viewStore.send(.confirmButtonTapped)
})
.adaptiveVPadding(64)
})
.disabled(viewStore.isConfirmButtonDisabled)
}
} The GenderView & Core is sturctured exactly the same struct GenderRegister: ReducerProtocol {
var isConfirmButtonDisabled: Bool = true
var birthdayRegisterState: BirthdayRegister.State?
@BindableState var isNavigationActive: Bool = false
.
.
.
// same body as that of NameCore
} However, it fails to navigate to BirthdayView.... I would like to get some clue to fix the navigate back issue. Thank you beforehand for your time and effort. |
Beta Was this translation helpful? Give feedback.
Replies: 3 comments 15 replies
-
Hey @inwoodev! The usual suspect with navigation glitches in SwiftUI is too much state being observed by your
|
Beta Was this translation helpful? Give feedback.
-
I am writing this comment to provide some hints to those who may be having similar problem. I created struct NameRegisterView: View {
let store: StoreOf<NameRegister>
struct ViewState: Equatable {
var progress: Float
var isConfirmButtonDisabled: Bool
@BindableState var name: String
@BindableState var isNavigationActive: Bool
init(
state: NameRegister.State
) {
self.progress = state.progress
self.isConfirmButtonDisabled = state.isConfirmButtonDisabled
self._name = state.$name
self._isNavigationActive = state.$isNavigationActive
}
}
enum ViewAction: BindableAction, Equatable {
case binding(BindingAction<ViewState>)
case confirmButtonTapped
case genderRegisterAction(GenderRegister.Action)
}
var body: some View {
WithViewStore(
self.store,
observe: ViewState.init,
send: NameRegister.Action.init
) { viewStore in
VStack {
RegisterProgressView(progress: viewStore.progress)
.adaptiveHeight(37)
Text(CommonStrings.Signup.askName)
.cimilleBoldFont(.headline1)
.padding(.trailing)
.adaptiveHeight(76)
UnderlineTextField(
text: viewStore.binding(\.$name)
)
NavigationLink(
destination: GenderRegisterView(
store: self.store.scope(
state: \.genderRegisterState,
action: { .genderRegisterAction($0) }
)
),
isActive: viewStore.binding(\.$isNavigationActive),
label: {
ConfirmButton(
title: .init(CimilleStrings.Signup.confirmButtonTitle),
disabled: viewStore.isConfirmButtonDisabled,
action: {
viewStore.send(.confirmButtonTapped)
})
.adaptiveVPadding(64)
})
.disabled(viewStore.isConfirmButtonDisabled)
Spacer()
}
.adaptiveHPadding(25)
}
}
} To map ViewAction, I extended the extension NameRegister.Action {
init(action: NameRegisterView.ViewAction) {
switch action {
case .binding(let bindingAction):
self = .binding(bindingAction.pullback(\NameRegister.State.bindableView))
case .confirmButtonTapped:
self = .confirmButtonTapped
case .genderRegisterAction(let action):
self = .genderRegisterAction(action)
}
}
} To adapt bindings I created a computed Property extension NameRegister.State {
var bindableView: NameRegisterView.ViewState {
get { .init(state: self) }
set {
// Bindable Action만을 처리하기 위해
self.name = newValue.name
self.isNavigationActive = newValue.isNavigationActive
}
}
} However, this approach seems a bit strange since the mapping above made it impossible to reach a specific binding case action: struct NameRegister: ReducerProtocol {
struct State: Equatable {
var progress: Float = 12.5
@BindableState var name: String = ""
var isConfirmButtonDisabled: Bool = true
var genderRegisterState = GenderRegister.State()
@BindableState var isNavigationActive: Bool = false
}
enum Action: BindableAction, Equatable {
case binding(BindingAction<State>)
case confirmButtonTapped
case genderRegisterAction(GenderRegister.Action)
}
var body: some ReducerProtocol<State, Action> {
BindingReducer()
Scope(
state: \.genderRegisterState,
action: /Action.genderRegisterAction
) {
GenderRegister()
}
Reduce { state, action in
switch action {
case .confirmButtonTapped:
state.isNavigationActive = true
return .none
// the case below is not reachable for some reason
case .binding(\.$name):
state.isConfirmButtonDisabled = self.isInvalidName(checking: state)
return .none
// therefore I had to call isInvalid method below to check the validity of name
case .binding:
state.isConfirmButtonDisabled = self.isInvalidName(checking: state)
return .none
case .genderRegisterAction:
return .none
}
}
}
private func isInvalidName(checking state: State) -> Bool {
return state.name.isEmpty
}
} |
Beta Was this translation helpful? Give feedback.
-
I also had this same problem. I was able to fix it by just making a ViewState, I didn't need a ViewAction.
Don't know if anyone has an idea why? This doesn't work as well |
Beta Was this translation helpful? Give feedback.
Hey @inwoodev! The usual suspect with navigation glitches in SwiftUI is too much state being observed by your
ViewStore
.I'm not seeing your
ViewStore
declaration, but it is likely that you're observing the whole domain's state. You should probably carve-out aViewState
where you expose only what's strictly required to render your view. You can check the Performance article aboutViewState
s for more informations.A few additional comments:
ViewStore
observing too much state higher in the hierarchy.ViewState
. See #769 for example.