Skip to content

Instantly share code, notes, and snippets.

@lukeredpath
Last active March 30, 2024 02:30
Show Gist options
  • Save lukeredpath/2bf827a04a7d25b8482ff475c24ea5ec to your computer and use it in GitHub Desktop.
Save lukeredpath/2bf827a04a7d25b8482ff475c24ea5ec to your computer and use it in GitHub Desktop.

Revisions

  1. lukeredpath revised this gist Mar 30, 2024. 1 changed file with 12 additions and 3 deletions.
    15 changes: 12 additions & 3 deletions SharedStateDemoApp.swift
    Original file line number Diff line number Diff line change
    @@ -10,13 +10,13 @@ import SwiftUI

    // MARK: - Models

    struct User: Equatable, Hashable, Sendable {
    struct User: Equatable, Hashable, Sendable, Codable {
    let id: UUID
    let name: String
    }

    extension User {
    static let placeholder = User(id: .init(0), name: "")
    static var placeholder = User(id: .init(0), name: "")
    static let joe = User(id: .init(1), name: "Joe")
    static let bob = User(id: .init(2), name: "Bob")
    }
    @@ -27,6 +27,8 @@ extension User {
    struct AppFeature: Reducer {
    @ObservableState
    struct State: Equatable {
    @Shared(.user)
    var user
    var session = Session.State.loggedOut(.init())
    }

    @@ -44,6 +46,9 @@ struct AppFeature: Reducer {
    state.session = .loggedIn(.init(user: user))
    return .none
    case .session(.loggedIn(.logoutButtonTapped)):
    // If we want to reset our shared user state as soon as a
    // user logs out, we can do that here.
    // state.user = .placeholder
    state.session = .loggedOut(.init())
    return .none
    case .session:
    @@ -102,7 +107,11 @@ struct LoggedIn: Reducer {

    @Reducer
    struct LoggedOut: Reducer {
    struct State: Equatable {}
    @ObservableState
    struct State: Equatable {
    @Shared(.user)
    var user
    }

    enum Action {
    case loginButtonTapped(user: User)
  2. lukeredpath renamed this gist Mar 30, 2024. 1 changed file with 0 additions and 0 deletions.
    File renamed without changes.
  3. lukeredpath created this gist Mar 30, 2024.
    220 changes: 220 additions & 0 deletions SharedStateExample
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,220 @@
    //
    // SharedStateDemoApp.swift
    // SharedStateDemo
    //
    // Created by Luke Redpath on 29/03/2024.
    //

    import ComposableArchitecture
    import SwiftUI

    // MARK: - Models

    struct User: Equatable, Hashable, Sendable {
    let id: UUID
    let name: String
    }

    extension User {
    static let placeholder = User(id: .init(0), name: "")
    static let joe = User(id: .init(1), name: "Joe")
    static let bob = User(id: .init(2), name: "Bob")
    }

    // MARK: - Reducers

    @Reducer
    struct AppFeature: Reducer {
    @ObservableState
    struct State: Equatable {
    var session = Session.State.loggedOut(.init())
    }

    enum Action {
    case session(Session.Action)
    }

    var body: some ReducerOf<Self> {
    Scope(state: \.session, action: \.session) {
    Session.body
    }
    Reduce<State, Action> { state, action in
    switch action {
    case .session(.loggedOut(.loginButtonTapped(let user))):
    state.session = .loggedIn(.init(user: user))
    return .none
    case .session(.loggedIn(.logoutButtonTapped)):
    state.session = .loggedOut(.init())
    return .none
    case .session:
    return .none
    }
    }
    }

    @Reducer(state: .equatable)
    enum Session {
    case loggedIn(LoggedIn)
    case loggedOut(LoggedOut)
    }
    }

    @Reducer
    struct LoggedIn: Reducer {
    @ObservableState
    struct State: Equatable {
    @Shared(.user)
    var user: User

    @Presents
    var someLoggedInFeature: SomeLoggedInFeature.State?

    init(user: User) {
    // We want to update the shared user every time a new
    // user logs in to the app.
    self.user = user
    }
    }

    enum Action {
    case openSomeFeatureButtonTapped
    case logoutButtonTapped
    case someLoggedInFeature(PresentationAction<SomeLoggedInFeature.Action>)
    }

    var body: some ReducerOf<Self> {
    Reduce<State, Action> { state, action in
    switch action {
    case .openSomeFeatureButtonTapped:
    state.someLoggedInFeature = .init(user: state.$user)
    return .none
    case .logoutButtonTapped:
    return .none
    case .someLoggedInFeature:
    return .none
    }
    }
    .ifLet(\.$someLoggedInFeature, action: \.someLoggedInFeature) {
    SomeLoggedInFeature()
    }
    }
    }

    @Reducer
    struct LoggedOut: Reducer {
    struct State: Equatable {}

    enum Action {
    case loginButtonTapped(user: User)
    }
    }

    @Reducer
    struct SomeLoggedInFeature: Reducer {
    @ObservableState
    struct State: Equatable {
    @Shared(.user)
    var user: User
    }
    }

    // MARK: - Persistence

    extension PersistenceKey where Self == DefaultProvidingKey<InMemoryKey<User>> {
    static var user: Self {
    inMemory("user", defaultValue: .placeholder)
    }
    }

    // MARK: - App and Views

    @main
    struct SharedStateDemoApp: App {
    @Bindable
    var store = StoreOf<AppFeature>(initialState: AppFeature.State()) {
    AppFeature()._printChanges()
    }

    var body: some Scene {
    WindowGroup {
    switch store.scope(state: \.session, action: \.session).case {
    case let .loggedIn(store):
    LoggedInView(store: store)
    case let .loggedOut(store):
    LoggedOutView(store: store)
    }
    }
    }
    }

    struct LoggedInView: View {
    @Bindable
    var store: StoreOf<LoggedIn>

    var body: some View {
    NavigationStack {
    List {
    Section {
    Text("Logged in as \(store.user.name)")
    }
    Section {
    Button("Open some feature") {
    store.send(.openSomeFeatureButtonTapped)
    }
    }
    Section {
    Button("Log Out") {
    store.send(.logoutButtonTapped)
    }
    .foregroundStyle(.red)
    }
    }
    .navigationTitle("Logged In")
    .navigationBarTitleDisplayMode(.inline)
    .sheet(item: $store.scope(state: \.someLoggedInFeature, action: \.someLoggedInFeature)) {
    SomeFeatureView(store: $0)
    }
    }
    }
    }

    struct LoggedOutView: View {
    let store: StoreOf<LoggedOut>

    var body: some View {
    NavigationStack {
    List {
    Section {
    Button("Login as Joe") {
    store.send(.loginButtonTapped(user: .joe))
    }
    Button("Login as Bob") {
    store.send(.loginButtonTapped(user: .bob))
    }
    }
    }
    .navigationTitle("Logged Out")
    .navigationBarTitleDisplayMode(.inline)
    }
    }
    }

    struct SomeFeatureView: View {
    let store: StoreOf<SomeLoggedInFeature>

    @Environment(\.dismiss)
    private var dismiss

    var body: some View {
    NavigationStack {
    Text("Hello \(store.user.name)")
    .navigationTitle("Some Feature")
    .navigationBarTitleDisplayMode(.inline)
    .toolbar {
    ToolbarItem(placement: .primaryAction) {
    Button("Done") { dismiss() }
    }
    }
    }
    }
    }