Hi I want help with Navigation Binding
When using the ForEachStore how should I toggle the value at AppState?
struct AppState: Equatable {
var contacts: [Contact] = []
var editMode: EditMode = .inactive
var selectedContactID = ""
}
import Contacts
struct Contact: Equatable {
var value: CNContact = .init() // CNContact is from Contacts framework
var isFavourite = false
}
enum ContactAction: Equatable {
case nameChanged(String)
case contactTapped
case starTapped
}
My AppView will utilize a ForEachView(self.store) and initialize the ContactView
struct ContentView: View {
let store: Store<AppState, AppAction>
var body: some View {
NavigationView {
WithViewStore(self.store) { viewStore in
List {
ForEachStore(self.store.scope(state: \.contacts, action: AppAction.contact(index:action:))) { contactStore in
ContactView(store: contactStore)
}}} }}
}
struct ContactView: View {
let store: Store<Contact, ContactAction>
var body: some View {
WithViewStore(self.store) { viewStore in
NavigationLink(
destination: Text(viewStore.value.givenName),
isActive: viewStore.binding(
/// Need help with the binding. It should be
// AppState.selectedContactID == viewStore.value.id
get: (String) -> LocalState,
send: ContactAction.contactTapped)
) {
EmptyView()
} }
}
Do you plan on driving navigation from state programmatically? E.g. deep-linking? If not, you should be able to use the bindingless NavigationLink APIs and let SwiftUI handle it for you:
NavigationLink(
destination: Text(viewStore.value.givenName)
) {
EmptyView() // label here
}
If you do want to drive navigation with state programmatically, it's a little trickier. We have a couple examples of lists of navigation links in the demos that ship with the repo (example 1, example 2), but they may be slightly more complicated in that they are working with optional sub-state for the row that is selected.
If you can get away with not using the binding-based NavigationLink API, though, I'd recommend it. SwiftUI currently has a few bugs around programmatic navigation that can make it quite tricky:
iPhone-based navigation needs to apply the .navigationViewStyle(StackNavigationViewStyle()) modifier to the navigation view in order to get the right binding behavior when drilling multiple layers into a navigation stack.
It's not currently possible to deep link several layers into a navigation stack all at once. Instead you need the UI to tick once between each layer, which is also pretty tricky to manage.
We're hoping that these things get fixed in the upcoming betas.
typealias LocalState1 = Int
typealias LocalState2 = Array<Int>
struct LocalView: View {
let store1: Store<LocalState1, LocalAction>
let store2: Store<LocalState2, LocalAction>
}
A ForEachStore returns a Store<LocalState1, LocalAction>
What is a good way to convert to Store<(LocalState1, LocalState2), LocalAction>, which is used at the LocalView's initialization
Hi @stephencelis
I checked in the examples, searched for every store.scope and found
0 Examples of scoping multiple states in TCA Github
1 Example of scoping multiple states in the PrimeTime app (2x iOS and macOS file)
Also, we could use an example of using UIVIewControllerRepresentable for binding between SwiftUI and UIKit, which makes it easy to adopt for starters
You just made my day! Again! .navigationViewStyle(StackNavigationViewStyle()) seems to fix most of my problems with navigation in SwiftUI. I am aware it's not really a declarative UI (because of the lack of deterministic state restoration possibility), but at least I can use it for basic navigation. Without StackNavigationViewStyle it just deactivates links for no reason and crashing.