Hey ,
I am currently trying to refactor my .sheet related code so it will get more readable.
At the moment I'm using an enum and a separate state to drive the sheet like so:
// AppState
struct AppState: Equatable {
enum Sheet: String, Identifiable {
case one
case two
var id: String {
return self.rawValue
}
}
var sheet: Sheet?
var one: One.State?
var two: Two.State?
}
// Sheet Modifier
.sheet(item: viewStore.binding(get: \.sheet, send: AppAction.setSheet)) { item in
Group {
switch item {
case .one:
IfLetStore(
store.scope(
state: \.one,
action: AppAction.oneViewAction
),
then: One.init(store:)
)
case .two:
IfLetStore(
store.scope(
state: \.twoState,
action: AppAction.twoAction
),
then: Two.init(store:)
)
}
}
}
// Reducer
case let .setOne(state: oneState):
state.oneState = oneState
state.sheet = .one
return .none
case let .setTwoState(state: twoState):
state.twoState = twoState
state.item = .two
return .none
case let .setSheet(item):
switch item {
case .none:
state.sheet = nil
state.oneState = nil
state.twoState = nil
return .none
case .one:
return Effect(value: AppAction.setOneState(state: .init()))
case .two:
return Effect(value: AppAction.setTwoState(state: .init()))
}
This approach works like a charm but it has some downsides to it. You need to handle state and sheet with care and set them to the right values. Because state and presentation are independent and loosely coupled it feels really error prone. This does not seem to be the way that it's supposed to be implemented.
My Idea was to improve this by adding the state as an associated value to the enum that drives the sheet. This looks much nicer to me because using an enum like this, for sheets, is also the way I am used to do it when using plain SwiftUI.
Unfortunately I am not really sure how to get this running.
This is what I came up with
import ComposableArchitecture
import SwiftUI
let mainReducer = Reducer<AppView.State, AppView.Action, AppView.Environment> { state, action, env in
switch action {
case let .setSheet(sheet):
state.sheet = sheet
return .none
default:
return .none
}
}
struct AppView: View {
struct State: Equatable {
enum Sheet: Equatable, Identifiable {
case one(SheetOne.State)
case two(SheetTwo.State)
var id: Int {
switch self {
case .one:
return 1
case .two:
return 2
}
}
}
var sheet: Sheet?
}
enum Action {
case setSheet(AppView.State.Sheet?)
// Navigation
case sheetOneAction(SheetOne.Action)
case sheetTwoAction(SheetTwo.Action)
}
struct Environment {}
let store: Store<AppView.State, AppView.Action>
var body: some View {
WithViewStore(store) { viewStore in
Text("Test")
.sheet(item: /** ???? **//) { sheet in
switch sheet {
case let .one(state):
IfLetStore(
store.scope(
state: \AppView.State.Sheet.one,
action: AppView.Action.sheetOneAction
),
then: /** ???? **/
)
case let .two(state):
IfLetStore(
store.scope(
state: \AppView.State.Sheet.two,
action: AppView.Action.sheetTwoAction
),
then: /** ???? **/
)
}
}
}
}
}
struct SheetOne: View {
struct State: Equatable {}
enum Action {}
struct Environment {}
let store: Store<SheetOne.State, SheetOne.Action>
var body: some View {
WithViewStore(store) { viewStore in
Text("Test")
}
}
}
struct SheetTwo: View {
struct State: Equatable {}
enum Action {}
struct Environment {}
let store: Store<SheetTwo.State, SheetTwo.Action>
var body: some View {
WithViewStore(store) { viewStore in
Text("Test")
}
}
}
Is it somehow possible to drive a .sheet using just an enum like this?