I am building a feature that uses has some state and a set of different pages it will render in a PageViewer
which allows users to swipe between pages:
struct AppState {
var activity: Activity
var pages: [Page]
var currentPage: Int
var pager: PagerState {
get { .init(pages: self.pages.count, currentPage: self.currentPage) }
set { (self.pages, self.currentPage) = (self.pages, newValue.currentPage) }
}
}
enum AppAction {
case activity(ActivityAction)
case pager(PagerAction)
case page(PageAction)
}
struct PagerState {
var pages: Int
var currentPage: Int
}
enum PagerAction {
case changePage(idx: Int)
}
The Page
type is an enum because there are multiple types of pages. This also means each page has different action types and its own reducer
enum Page {
case chartPage(ChartPage)
case mapPage(MapPage)
}
enum PageAction {
case chart(ChartPageAction)
case map(MapPageAction)
Now, the problem I have is that each page also needs access to the activity
state in their store, as they display data calculated from it. For example, the ChartPage
looks like this:
struct ChartPage {
var activity: Activity
}
I then render the pages as such... this could be improved a bit but it works for now:
struct AppViewView: View {
let store: Store<RecorderState, RecorderAction>
var pagerStore: Store<PagerState, PagerAction> {
store.scope(state: \.pager, action: { RecorderAction.pager($0) })
}
var body: some View {
Pager(store: pagerStore) {
ForEachStore(
store.scope(state: \.pages, action: RecorderAction.page),
content: PageView.init(store:))
}
}
}
struct PageView: View {
let store: Store<Page, PageAction>
var body: some View {
IfLetStore(store.scope(state: \.dataPage, action:PageAction.data),
then: { store in ChartPageView(store: store) }
)
IfLetStore(store.scope(state: \.mapPage, action: PageAction.map),
then: { store in MapPageView(store: store) }
)
}
}
The issue comes when I'm actually building my store. I need each page to have the activity
scoped from the outer state, but I'm not sure how to do that. This is in my SceneDelegate
:
let activity = Activity()
let store = Store(
initialState: AppState(
activity: activity,
pages: [
Page.dataPage(DataPage(activity: activity)),
Page.mapPage(MapPage(activity: Activity)),
],
currentPage: 0
),
reducer: appReducer,
environment: AppEnvironment()
)
As you can imagine, the activity
is a value type and therefore not shared between the AppState
, DataPage
, and MapPage
. The pages have to be constructed here, and ideally they would use store.scope
to get some smaller set of state than the whole activity even. I can't quite figure out how to do this.
It gets even more complicated when I want to allow users to add and remove pages dynamically from the array. Am I missing something fundamental here? Any suggestions how to do this better?
Thanks all