From 2695aab46ed3ba76038fd83eed2507df6997bb9f Mon Sep 17 00:00:00 2001 From: Ben Kreeger Date: Mon, 24 Nov 2025 16:43:12 -0600 Subject: [PATCH] Add some docstrings and clean some things up --- Meow.xcodeproj/project.pbxproj | 8 -------- .../xcshareddata/xcschemes/App.xcscheme | 7 +++++++ Meow/Sources/Facts/FactsFeature.swift | 20 ++++++++++++++++--- Meow/Sources/Facts/FactsView.swift | 9 ++++++++- Meow/Sources/Root/RootFeature.swift | 11 +++++++--- Meow/Sources/Root/RootView.swift | 2 ++ 6 files changed, 42 insertions(+), 15 deletions(-) diff --git a/Meow.xcodeproj/project.pbxproj b/Meow.xcodeproj/project.pbxproj index f4850c3..a093f83 100644 --- a/Meow.xcodeproj/project.pbxproj +++ b/Meow.xcodeproj/project.pbxproj @@ -85,7 +85,6 @@ C3BB763D2ED0C38800D56534 /* App */, C3BB764B2ED0C38900D56534 /* AppTests */, C3BB76552ED0C38900D56534 /* AppUITests */, - C3C75C592ED2C672000FD10A /* Frameworks */, C3BB763C2ED0C38800D56534 /* Products */, ); sourceTree = ""; @@ -100,13 +99,6 @@ name = Products; sourceTree = ""; }; - C3C75C592ED2C672000FD10A /* Frameworks */ = { - isa = PBXGroup; - children = ( - ); - name = Frameworks; - sourceTree = ""; - }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ diff --git a/Meow.xcodeproj/xcshareddata/xcschemes/App.xcscheme b/Meow.xcodeproj/xcshareddata/xcschemes/App.xcscheme index 13c98c6..7b449f1 100644 --- a/Meow.xcodeproj/xcshareddata/xcschemes/App.xcscheme +++ b/Meow.xcodeproj/xcshareddata/xcschemes/App.xcscheme @@ -82,6 +82,13 @@ ReferencedContainer = "container:Meow.xcodeproj"> + + + + { Reduce { state, action in switch action { case .viewAppeared: - state.mode = .loading + switch state.mode { + case .loaded: + return .none + default: + state.mode = .loading + return .send(.fetchNeeded) + } + case .pulledToRefresh: + return .send(.fetchNeeded) + case .fetchNeeded: let client = clientFactory.createClient(baseURL: state.baseURL) return .run { send in do { diff --git a/Meow/Sources/Facts/FactsView.swift b/Meow/Sources/Facts/FactsView.swift index 1f94e14..f6f3c7e 100644 --- a/Meow/Sources/Facts/FactsView.swift +++ b/Meow/Sources/Facts/FactsView.swift @@ -7,6 +7,8 @@ import SwiftUI import Foundation import ComposableArchitecture +/// A view that displays either a progress spinner, a list of cat facts provided by an underlying feature reducer, or +/// an error message, depending on the state. Supports pull-to-refresh. public struct FactsView: View { let store: StoreOf @@ -29,6 +31,11 @@ public struct FactsView: View { Text(error.localizedDescription) } } + .refreshable { + // Tie the lifecycle of the PTR to the effect. + // https://github.com/pointfreeco/swift-composable-architecture/discussions/2542 + await Task { await store.send(.pulledToRefresh).finish() }.value + } .onAppear { store.send(.viewAppeared) } @@ -36,7 +43,7 @@ public struct FactsView: View { } #Preview { - FactsView(store: .init(initialState: FactsFeature.State()) { + FactsView(store: .init(initialState: FactsFeature.State(mode: .loaded(facts: ["One", "Two", "Three"]))) { FactsFeature() }) } diff --git a/Meow/Sources/Root/RootFeature.swift b/Meow/Sources/Root/RootFeature.swift index 7772221..2bd5503 100644 --- a/Meow/Sources/Root/RootFeature.swift +++ b/Meow/Sources/Root/RootFeature.swift @@ -8,6 +8,9 @@ import Facts import CatFactsKit import ComposableArchitecture +/// Handles interaction logic for the root of the application. Doesn't otherwise do much itself but it does respond to +/// a `viewAppeared` action so that it can show how one might change the state that drives _another_ feature (in this +/// example, `FactsFeature`). @Reducer public struct RootFeature { @ObservableState @@ -26,6 +29,11 @@ public struct RootFeature { public init() { } public var body: some ReducerOf { + // This fun DSL lets me provide a concrete Feature reducer when the Root feature is asked for a scoped store + Scope(state: \.facts, action: \.facts) { + FactsFeature() + } + // And then this is everything that's provided by the root. Reduce { state, action in switch action { case .viewAppeared: @@ -35,8 +43,5 @@ public struct RootFeature { return .none } } - Scope(state: \.facts, action: \.facts) { - FactsFeature() - } } } diff --git a/Meow/Sources/Root/RootView.swift b/Meow/Sources/Root/RootView.swift index a7375da..c7b2713 100644 --- a/Meow/Sources/Root/RootView.swift +++ b/Meow/Sources/Root/RootView.swift @@ -7,6 +7,7 @@ import SwiftUI import Facts import ComposableArchitecture +/// The root view of the entire app. public struct RootView: View { @Bindable var store: StoreOf @@ -16,6 +17,7 @@ public struct RootView: View { public var body: some View { NavigationStack { + // Super basic here; I want to show a FactsView scoped to just that feature's `State` and `Action`s. FactsView(store: store.scope(state: \.facts, action: \.facts)) }.onAppear { store.send(.viewAppeared)