TCA-Shared-Reducer
This is a toy project using TCA to demonstrate sharing a common store through SwiftUI’s environment.
Use case:
-
In above screenshots we have common player control view, that is displayed to all tabs using
.safeAreaInset
view modifier to display at the bottom edge of the view. -
This view is having common state (and so common actions) but the this view’s height does not propogate via
NavigationView
/NavigationStack
as an additional safe area. Therefore, app is displaying these views as a seperate view to each tab item view and not aboveTabView
itself.+ @Environment(\.playbackControlStore) private var playbackControlStore ... CaseLet(/TabFeature.State.listen, action: TabFeature.Action.listen) { listenStore in NavigationStack { ListenView(store: listenStore) .safeAreaInset(edge: .bottom) { + PlaybackControlMiniView(store: playbackControlStore) } } .tabItem { Tab.listen.label } } CaseLet(/TabFeature.State.search, action: TabFeature.Action.search) { searchStore in NavigationStack { SearchView(store: searchStore) .safeAreaInset(edge: .bottom) { + PlaybackControlMiniView(store: playbackControlStore) } } .tabItem { Tab.search.label } }
-
To derive the
playbackControlStore
which is in theAppRootFeature
, app uses SwiftUI’s environment to pass down to children views using below snippet.enum PlaybackControlStoreKey: EnvironmentKey { static var defaultValue: StoreOf<PlaybackControlFeature> = .init(initialState: .init(), reducer: PlaybackControlFeature()) } extension EnvironmentValues { var playbackControlStore: StoreOf<PlaybackControlFeature> { get { self[PlaybackControlStoreKey.self] } set { self[PlaybackControlStoreKey.self] = newValue } } }
-
App injects the the
playbackControlStore
at theAppRootView
‘s body as below snippet.init(store: StoreOf<AppRootFeature>) { self.store = store + self.playbackControlStore = store.scope(state: \.playbackControl, action: AppRootFeature.Action.playbackControl) } var body: some View { WithViewStore(store, observe: {$0}) { viewStore in TabView { ... } .sheet(item: viewStore.binding(\.$destination)) { destination in switch destination { case .expanded: PlaybackControlExpandedView(store: playbackControlStore) } } + .environment(\.playbackControlStore, playbackControlStore) } }