Instantly share code, notes, and snippets.
Last active
August 24, 2020 03:45
-
Star
3
(3)
You must be signed in to star a gist -
Fork
1
(1)
You must be signed in to fork a gist
-
Save ThomasHack/a646e9c59dd83414084d17dfe192d054 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// ContentView.swift | |
// ComposableTest | |
// | |
import ComposableArchitecture | |
import SwiftUI | |
struct ContentView: View { | |
var store: Store<Main.State, Main.Action> | |
enum ViewKind: Hashable { | |
case home, shared | |
} | |
@State var selection: ViewKind = .home | |
var body: some View { | |
WithViewStore(self.store) { viewStore in | |
TabView(selection: self.$selection, | |
content: { | |
HomeView(store: Main.store.home) | |
.tabItem { | |
Image(systemName: "house") | |
Text("Home") | |
} | |
.tag(ViewKind.home) | |
} | |
) | |
} | |
} | |
} | |
struct ContentView_Previews: PreviewProvider { | |
static var previews: some View { | |
ContentView(store: Main.store) | |
} | |
} | |
// MARK: - Main | |
enum Main { | |
struct State: Equatable { | |
var shared: Shared.State | |
var home: Home.State | |
var settings: Settings.State | |
var homeFeature: Home.HomeFeature { | |
get { Home.HomeFeature(home: self.home, shared: self.shared) } | |
set { | |
self.home = newValue.home | |
self.shared = newValue.shared | |
} | |
} | |
var settingsFeature: Settings.SettingsFeature { | |
get { Settings.SettingsFeature(settings: self.settings, shared: self.shared) } | |
set { | |
self.settings = newValue.settings | |
self.shared = newValue.shared | |
} | |
} | |
} | |
enum Action { | |
case shared(Shared.Action) | |
case home(Home.Action) | |
case settings(Settings.Action) | |
} | |
struct Environment { | |
let mainQueue: AnySchedulerOf<DispatchQueue> | |
} | |
static let reducer = Reducer<State, Action, Environment>.combine( | |
Reducer<State, Action, Environment>{ _, _, _ in | |
return .none | |
}, | |
Shared.reducer.pullback( | |
state: \State.shared, | |
action: /Action.shared, | |
environment: { $0 } | |
), | |
Home.reducer.pullback( | |
state: \State.homeFeature, | |
action: /Action.home, | |
environment: { $0 } | |
), | |
Settings.reducer.pullback( | |
state: \State.settingsFeature, | |
action: /Action.settings, | |
environment: { $0 } | |
) | |
) | |
static let store = Store( | |
initialState: State( | |
shared: Shared.initialState, | |
home: Home.initialState, | |
settings: Settings.initialState | |
), | |
reducer: reducer, | |
environment: Environment( | |
mainQueue: DispatchQueue.main.eraseToAnyScheduler() | |
) | |
) | |
} | |
extension Store where State == Main.State, Action == Main.Action { | |
var home: Store<Home.HomeFeature, Home.Action> { | |
scope(state: \.homeFeature, action: Main.Action.home) | |
} | |
var settings: Store<Settings.SettingsFeature, Settings.Action> { | |
scope(state: \.settingsFeature, action: Main.Action.settings) | |
} | |
} | |
// MARK: - Shared | |
enum Shared { | |
struct State: Equatable { | |
var sharedProperty: Bool = false | |
var showSettingsModal: Bool = false | |
} | |
enum Action { | |
case shared | |
case showSettingsModal | |
case hideSettingsModal | |
case toggleSettingsModal(Bool) | |
} | |
typealias Environment = Main.Environment | |
static let reducer = Reducer<Shared.State, Shared.Action, Environment> { state, action, environment in | |
switch action { | |
case .shared: | |
state.sharedProperty = false | |
case .showSettingsModal: | |
state.showSettingsModal = true | |
case .hideSettingsModal: | |
state.showSettingsModal = false | |
case .toggleSettingsModal(let show): | |
state.showSettingsModal = show | |
} | |
return .none | |
} | |
static let initialState = State( | |
sharedProperty: false | |
) | |
} | |
// MARK: - Home | |
enum Home { | |
@dynamicMemberLookup | |
struct HomeFeature: Equatable { | |
var home: Home.State | |
var shared: Shared.State | |
public subscript<T>(dynamicMember keyPath: WritableKeyPath<Home.State, T>) -> T { | |
get { home[keyPath: keyPath] } | |
set { home[keyPath: keyPath] = newValue } | |
} | |
public subscript<T>(dynamicMember keyPath: WritableKeyPath<Shared.State, T>) -> T { | |
get { shared[keyPath: keyPath] } | |
set { shared[keyPath: keyPath] = newValue } | |
} | |
} | |
struct State: Equatable { | |
var homeProperty: Bool = false | |
} | |
enum Action { | |
case home | |
case shared(Shared.Action) | |
} | |
typealias Environment = Main.Environment | |
static let reducer = Reducer<HomeFeature, Action, Environment>.combine( | |
Reducer { state, action, environment in | |
switch action { | |
case .home: | |
state.homeProperty.toggle() | |
return .none | |
case .shared: | |
break | |
} | |
return .none | |
}, | |
Shared.reducer.pullback( | |
state: \HomeFeature.shared, | |
action: /Action.shared, | |
environment: { $0 } | |
) | |
) | |
static let initialState = Home.State( | |
homeProperty: false | |
) | |
} | |
struct HomeView: View { | |
let store: Store<Home.HomeFeature, Home.Action> | |
var body: some View { | |
WithViewStore(self.store) { viewStore in | |
VStack(alignment: .center, spacing: 16) { | |
Button(action: { viewStore.send(.home) }) { | |
Text("Local Action \(viewStore.homeProperty ? "true" : "false")") | |
} | |
Button(action: { viewStore.send(.shared(.showSettingsModal)) }) { | |
Text("Shared Action \(viewStore.sharedProperty ? "true" : "false")") | |
} | |
}.sheet(isPresented: viewStore.binding( get: { $0.showSettingsModal }, send: Home.Action.shared(.hideSettingsModal))) { | |
SettingsView(store: Main.store.settings) | |
} | |
} | |
} | |
} | |
// MARK: - Settings | |
enum Settings { | |
@dynamicMemberLookup | |
struct SettingsFeature: Equatable { | |
var settings: Settings.State | |
var shared: Shared.State | |
public subscript<T>(dynamicMember keyPath: WritableKeyPath<Settings.State, T>) -> T { | |
get { settings[keyPath: keyPath] } | |
set { settings[keyPath: keyPath] = newValue } | |
} | |
public subscript<T>(dynamicMember keyPath: WritableKeyPath<Shared.State, T>) -> T { | |
get { shared[keyPath: keyPath] } | |
set { shared[keyPath: keyPath] = newValue } | |
} | |
} | |
struct State: Equatable { | |
var settingsProperty: Bool = false | |
} | |
enum Action { | |
case settings | |
case shared(Shared.Action) | |
} | |
typealias Environment = Main.Environment | |
static let reducer = Reducer<SettingsFeature, Action, Environment>.combine( | |
Reducer { state, action, _ in | |
switch action { | |
case .settings: | |
state.settingsProperty.toggle() | |
return .none | |
case .shared: | |
break | |
} | |
return .none | |
}, | |
Shared.reducer.pullback( | |
state: \SettingsFeature.shared, | |
action: /Action.shared, | |
environment: { $0 } | |
) | |
) | |
static let initialState = Settings.State( | |
settingsProperty: false | |
) | |
} | |
struct SettingsView: View { | |
let store: Store<Settings.SettingsFeature, Settings.Action> | |
var body: some View { | |
WithViewStore(self.store) { viewStore in | |
VStack(alignment: .center, spacing: 16) { | |
Button(action: { viewStore.send(.settings) }) { | |
Text("Local Action \(viewStore.settingsProperty ? "true" : "false")") | |
} | |
Button(action: { viewStore.send(Settings.Action.shared(.hideSettingsModal)) }) { | |
Text("Shared Action \(viewStore.sharedProperty ? "true" : "false")") | |
} | |
} | |
} | |
} | |
} | |
// MARK: - API | |
enum Api { | |
struct State: Equatable { | |
var connectivity: ConnectivityStatus = .disconnected | |
enum ConnectivityStatus { | |
case connected | |
case disconnected | |
} | |
} | |
enum Action { | |
case shared | |
} | |
typealias Environment = Main.Environment | |
static let reducer = Reducer<State, Action, Environment> { _, _, _ in | |
return .none | |
} | |
static let initialState = State() | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Thanks for sharing this! I took this as a base to start organising our code in a better way.
I added a protocol for the feature lookup:
That way a Feature state will unlock the getters/setters for free just by having the state and shared properties. Lastly, instead of combining the shared reducer with each feature reducer, why not let the feature reducer act on shared state since the feature provides a writable key path anyways? I think that reduces verbosity and it gives a feature reducer the flexibility to change a shared state in whatever way it needs. Thoughts?