From 1a8bccc62e4ce53ff83b0845ea965b0a3ce83976 Mon Sep 17 00:00:00 2001 From: Wendy Liga Date: Tue, 15 Jun 2021 01:10:08 +0700 Subject: [PATCH] Support DocC Xcode 13 (#591) * wip * finish * revert back code snippet identation to 5 * Update Sources/ComposableArchitecture/Effect.swift Co-authored-by: Brandon Williams <135203+mbrandonw@users.noreply.github.com> Co-authored-by: Brandon Williams <135203+mbrandonw@users.noreply.github.com> --- .../Debugging/ReducerDebugging.swift | 14 ++- Sources/ComposableArchitecture/Effect.swift | 52 +++++++--- .../Effects/Cancellation.swift | 4 +- .../Effects/Debouncing.swift | 4 +- .../Effects/Timer.swift | 46 +++++---- Sources/ComposableArchitecture/Reducer.swift | 98 ++++++++++++------- Sources/ComposableArchitecture/Store.swift | 25 ++--- .../SwiftUI/ActionSheet.swift | 10 ++ .../SwiftUI/Alert.swift | 12 ++- .../SwiftUI/Binding.swift | 38 +++++-- .../SwiftUI/ForEachStore.swift | 19 +++- .../SwiftUI/Identified.swift | 4 + .../SwiftUI/IdentifiedArray.swift | 13 ++- .../SwiftUI/IfLetStore.swift | 10 +- .../SwiftUI/TextState.swift | 22 +++-- .../SwiftUI/WithViewStore.swift | 6 +- .../TestSupport/FailingEffect.swift | 8 ++ .../TestSupport/TestStore.swift | 14 ++- .../UIKit/IfLetUIKit.swift | 2 + .../ComposableArchitecture/ViewStore.swift | 31 ++++-- 20 files changed, 300 insertions(+), 132 deletions(-) diff --git a/Sources/ComposableArchitecture/Debugging/ReducerDebugging.swift b/Sources/ComposableArchitecture/Debugging/ReducerDebugging.swift index 193af1d8c3..7aa7c50d90 100644 --- a/Sources/ComposableArchitecture/Debugging/ReducerDebugging.swift +++ b/Sources/ComposableArchitecture/Debugging/ReducerDebugging.swift @@ -1,16 +1,20 @@ import CasePaths import Dispatch -/// Determines how the string description of an action should be printed when using the `.debug()` +/// Determines how the string description of an action should be printed when using the ``Reducer/debug(prefix:state:action:environment:)`` /// higher-order reducer. public enum ActionFormat { /// Prints the action in a single line by only specifying the labels of the associated values: /// + /// ```swift /// Action.screenA(.row(index:, action: .textChanged(query:))) + /// ``` + /// case labelsOnly /// Prints the action in a multiline, pretty-printed format, including all the labels of /// any associated values, as well as the data held in the associated values: /// + /// ```swift /// Action.screenA( /// ScreenA.row( /// index: 1, @@ -19,6 +23,8 @@ public enum ActionFormat { /// ) /// ) /// ) + /// ``` + /// case prettyPrint } @@ -31,7 +37,7 @@ extension Reducer { /// - prefix: A string with which to prefix all debug messages. /// - toDebugEnvironment: A function that transforms an environment into a debug environment by /// describing a print function and a queue to print from. Defaults to a function that ignores - /// the environment and returns a default `DebugEnvironment` that uses Swift's `print` + /// the environment and returns a default ``DebugEnvironment`` that uses Swift's `print` /// function and a background queue. /// - Returns: A reducer that prints debug messages for all received actions. public func debug( @@ -58,7 +64,7 @@ extension Reducer { /// - prefix: A string with which to prefix all debug messages. /// - toDebugEnvironment: A function that transforms an environment into a debug environment by /// describing a print function and a queue to print from. Defaults to a function that ignores - /// the environment and returns a default `DebugEnvironment` that uses Swift's `print` + /// the environment and returns a default ``DebugEnvironment`` that uses Swift's `print` /// function and a background queue. /// - Returns: A reducer that prints debug messages for all received actions. public func debugActions( @@ -87,7 +93,7 @@ extension Reducer { /// - toLocalAction: A case path that filters actions that are printed. /// - toDebugEnvironment: A function that transforms an environment into a debug environment by /// describing a print function and a queue to print from. Defaults to a function that ignores - /// the environment and returns a default `DebugEnvironment` that uses Swift's `print` + /// the environment and returns a default ``DebugEnvironment`` that uses Swift's `print` /// function and a background queue. /// - Returns: A reducer that prints debug messages for all received actions. public func debug( diff --git a/Sources/ComposableArchitecture/Effect.swift b/Sources/ComposableArchitecture/Effect.swift index 19881bf5a0..7b54ea9d81 100644 --- a/Sources/ComposableArchitecture/Effect.swift +++ b/Sources/ComposableArchitecture/Effect.swift @@ -1,12 +1,12 @@ import Combine import Foundation -/// The `Effect` type encapsulates a unit of work that can be run in the outside world, and can feed -/// data back to the `Store`. It is the perfect place to do side effects, such as network requests, +/// The ``Effect`` type encapsulates a unit of work that can be run in the outside world, and can feed +/// data back to the ``Store``. It is the perfect place to do side effects, such as network requests, /// saving/loading from disk, creating timers, interacting with dependencies, and more. /// -/// Effects are returned from reducers so that the `Store` can perform the effects after the reducer -/// is done running. It is important to note that `Store` is not thread safe, and so all effects +/// Effects are returned from reducers so that the ``Store`` can perform the effects after the reducer +/// is done running. It is important to note that ``Store`` is not thread safe, and so all effects /// must receive values on the same thread, **and** if the store is being used to drive UI then it /// must receive values on the main thread. /// @@ -20,17 +20,21 @@ public struct Effect: Publisher { /// /// This initializer is useful for turning any publisher into an effect. For example: /// + /// ```swift /// Effect( /// NotificationCenter.default /// .publisher(for: UIApplication.userDidTakeScreenshotNotification) /// ) + /// ``` /// /// Alternatively, you can use the `.eraseToEffect()` method that is defined on the `Publisher` /// protocol: /// + /// ```swift /// NotificationCenter.default /// .publisher(for: UIApplication.userDidTakeScreenshotNotification) /// .eraseToEffect() + /// ``` /// /// - Parameter publisher: A publisher. public init(_ publisher: P) where P.Output == Output, P.Failure == Failure { @@ -74,28 +78,32 @@ public struct Effect: Publisher { /// Creates an effect that can supply a single value asynchronously in the future. /// /// This can be helpful for converting APIs that are callback-based into ones that deal with - /// `Effect`s. + /// ``Effect``s. /// /// For example, to create an effect that delivers an integer after waiting a second: /// + /// ```swift /// Effect.future { callback in /// DispatchQueue.main.asyncAfter(deadline: .now() + 1) { /// callback(.success(42)) /// } /// } + /// ``` /// /// Note that you can only deliver a single value to the `callback`. If you send more they will be /// discarded: /// + /// ```swift /// Effect.future { callback in /// DispatchQueue.main.asyncAfter(deadline: .now() + 1) { /// callback(.success(42)) /// callback(.success(1729)) // Will not be emitted by the effect /// } /// } + /// ``` /// - /// If you need to deliver more than one value to the effect, you should use the `Effect` - /// initializer that accepts a `Subscriber` value. + /// If you need to deliver more than one value to the effect, you should use the ``Effect`` + /// initializer that accepts a ``Subscriber`` value. /// /// - Parameter attemptToFulfill: A closure that takes a `callback` as an argument which can be /// used to feed it `Result` values. @@ -115,6 +123,7 @@ public struct Effect: Publisher { /// /// For example, to load a user from some JSON on the disk, one can wrap that work in an effect: /// + /// ```swift /// Effect.result { /// let fileUrl = URL( /// fileURLWithPath: NSSearchPathForDirectoriesInDomains( @@ -130,6 +139,7 @@ public struct Effect: Publisher { /// /// return result /// } + /// ``` /// /// - Parameter attemptToFulfill: A closure encapsulating some work to execute in the real world. /// - Returns: An effect. @@ -141,13 +151,14 @@ public struct Effect: Publisher { /// a completion. /// /// This initializer is useful for bridging callback APIs, delegate APIs, and manager APIs to the - /// `Effect` type. One can wrap those APIs in an Effect so that its events are sent through the + /// ``Effect`` type. One can wrap those APIs in an Effect so that its events are sent through the /// effect, which allows the reducer to handle them. /// /// For example, one can create an effect to ask for access to `MPMediaLibrary`. It can start by /// sending the current status immediately, and then if the current status is `notDetermined` it /// can request authorization, and once a status is received it can send that back to the effect: /// + /// ```swift /// Effect.run { subscriber in /// subscriber.send(MPMediaLibrary.authorizationStatus()) /// @@ -165,9 +176,10 @@ public struct Effect: Publisher { /// // have any. /// } /// } + /// ``` /// - /// - Parameter work: A closure that accepts a `Subscriber` value and returns a cancellable. When - /// the `Effect` is completed, the cancellable will be used to clean up any resources created + /// - Parameter work: A closure that accepts a ``Subscriber`` value and returns a cancellable. When + /// the ``Effect`` is completed, the cancellable will be used to clean up any resources created /// when the effect was started. public static func run( _ work: @escaping (Effect.Subscriber) -> Cancellable @@ -180,7 +192,7 @@ public struct Effect: Publisher { /// /// - Warning: Combine's `Publishers.Concatenate` operator, which this function uses, can leak /// when its suffix is a `Publishers.MergeMany` operator, which is used throughout the - /// Composable Architecture in functions like `Reducer.combine`. + /// Composable Architecture in functions like ``Reducer/combine(_:)-1ern2``. /// /// Feedback filed: /// @@ -195,7 +207,7 @@ public struct Effect: Publisher { /// /// - Warning: Combine's `Publishers.Concatenate` operator, which this function uses, can leak /// when its suffix is a `Publishers.MergeMany` operator, which is used throughout the - /// Composable Architecture in functions like `Reducer.combine`. + /// Composable Architecture in functions like `Reducer/combine(_:)-1ern2``. /// /// Feedback filed: /// @@ -269,6 +281,7 @@ extension Effect where Failure == Swift.Error { /// /// For example, to load a user from some JSON on the disk, one can wrap that work in an effect: /// + /// ```swift /// Effect.catching { /// let fileUrl = URL( /// fileURLWithPath: NSSearchPathForDirectoriesInDomains( @@ -280,6 +293,7 @@ extension Effect where Failure == Swift.Error { /// let data = try Data(contentsOf: fileUrl) /// return try JSONDecoder().decode(User.self, from: $0) /// } + /// ``` /// /// - Parameter work: A closure encapsulating some work to execute in the real world. /// - Returns: An effect. @@ -289,31 +303,35 @@ extension Effect where Failure == Swift.Error { } extension Publisher { - /// Turns any publisher into an `Effect`. + /// Turns any publisher into an ``Effect``. /// /// This can be useful for when you perform a chain of publisher transformations in a reducer, and /// you need to convert that publisher to an effect so that you can return it from the reducer: /// + /// ```swift /// case .buttonTapped: /// return fetchUser(id: 1) /// .filter(\.isAdmin) /// .eraseToEffect() + /// ``` /// /// - Returns: An effect that wraps `self`. public func eraseToEffect() -> Effect { Effect(self) } - /// Turns any publisher into an `Effect` that cannot fail by wrapping its output and failure in a + /// Turns any publisher into an ``Effect`` that cannot fail by wrapping its output and failure in a /// result. /// /// This can be useful when you are working with a failing API but want to deliver its data to an /// action that handles both success and failure. /// + /// ```swift /// case .buttonTapped: /// return fetchUser(id: 1) /// .catchToEffect() /// .map(ProfileAction.userResponse) + /// ``` /// /// - Returns: An effect that wraps `self`. public func catchToEffect() -> Effect, Never> { @@ -322,16 +340,18 @@ extension Publisher { .eraseToEffect() } - /// Turns any publisher into an `Effect` for any output and failure type by ignoring all output + /// Turns any publisher into an ``Effect`` for any output and failure type by ignoring all output /// and any failure. /// /// This is useful for times you want to fire off an effect but don't want to feed any data back /// into the system. It can automatically promote an effect to your reducer's domain. /// + /// ```swift /// case .buttonTapped: /// return analyticsClient.track("Button Tapped") /// .fireAndForget() - /// + /// ``` + /// /// - Parameters: /// - outputType: An output type. /// - failureType: A failure type. diff --git a/Sources/ComposableArchitecture/Effects/Cancellation.swift b/Sources/ComposableArchitecture/Effects/Cancellation.swift index 5a84e07e75..00b10e8584 100644 --- a/Sources/ComposableArchitecture/Effects/Cancellation.swift +++ b/Sources/ComposableArchitecture/Effects/Cancellation.swift @@ -5,10 +5,11 @@ extension Effect { /// Turns an effect into one that is capable of being canceled. /// /// To turn an effect into a cancellable one you must provide an identifier, which is used in - /// `Effect.cancel(id:)` to identify which in-flight effect should be canceled. Any hashable + /// ``Effect/cancel(id:)`` to identify which in-flight effect should be canceled. Any hashable /// value can be used for the identifier, such as a string, but you can add a bit of protection /// against typos by defining a new type that conforms to `Hashable`, such as an empty struct: /// + /// ```swift /// struct LoadUserId: Hashable {} /// /// case .reloadButtonTapped: @@ -20,6 +21,7 @@ extension Effect { /// case .cancelButtonTapped: /// // Cancel any in-flight requests to load the user /// return .cancel(id: LoadUserId()) + /// ``` /// /// - Parameters: /// - id: The effect's identifier. diff --git a/Sources/ComposableArchitecture/Effects/Debouncing.swift b/Sources/ComposableArchitecture/Effects/Debouncing.swift index 1e7651190a..d52387b787 100644 --- a/Sources/ComposableArchitecture/Effects/Debouncing.swift +++ b/Sources/ComposableArchitecture/Effects/Debouncing.swift @@ -9,13 +9,15 @@ extension Effect { /// protection against typos by defining a new type that conforms to `Hashable`, such as an empty /// struct: /// + /// ```swift /// case let .textChanged(text): /// struct SearchId: Hashable {} /// /// return environment.search(text) /// .map(Action.searchResponse) /// .debounce(id: SearchId(), for: 0.5, scheduler: environment.mainQueue) - /// + /// ``` + /// /// - Parameters: /// - id: The effect's identifier. /// - dueTime: The duration you want to debounce for. diff --git a/Sources/ComposableArchitecture/Effects/Timer.swift b/Sources/ComposableArchitecture/Effects/Timer.swift index 3e10143fdb..e91c5ca5c0 100644 --- a/Sources/ComposableArchitecture/Effects/Timer.swift +++ b/Sources/ComposableArchitecture/Effects/Timer.swift @@ -15,13 +15,14 @@ extension Effect where Failure == Never { /// we can see how effects emit. However, because `Timer.publish` takes a concrete `RunLoop` as /// its scheduler, we can't substitute in a `TestScheduler` during tests`. /// - /// That is why we provide the `Effect.timer` effect. It allows you to create a timer that works + /// That is why we provide the ``Effect/timer(id:every:tolerance:on:options:)`` effect. It allows you to create a timer that works /// with any scheduler, not just a run loop, which means you can use a `DispatchQueue` or /// `RunLoop` when running your live app, but use a `TestScheduler` in tests. /// /// To start and stop a timer in your feature you can create the timer effect from an action - /// and then use the `.cancel(id:)` effect to stop the timer: + /// and then use the ``Effect/cancel(id:)`` effect to stop the timer: /// + /// ```swift /// struct AppState { /// var count = 0 /// } @@ -49,34 +50,37 @@ extension Effect where Failure == Never { /// state.count += 1 /// return .none /// } + /// ``` /// /// Then to test the timer in this feature you can use a test scheduler to advance time: /// - /// func testTimer() { - /// let scheduler = DispatchQueue.test + /// ```swift + /// func testTimer() { + /// let scheduler = DispatchQueue.test /// - /// let store = TestStore( - /// initialState: .init(), - /// reducer: appReducer, - /// envirnoment: .init( - /// mainQueue: scheduler.eraseToAnyScheduler() + /// let store = TestStore( + /// initialState: .init(), + /// reducer: appReducer, + /// envirnoment: .init( + /// mainQueue: scheduler.eraseToAnyScheduler() + /// ) /// ) - /// ) /// - /// store.send(.startButtonTapped) + /// store.send(.startButtonTapped) /// - /// scheduler.advance(by: .seconds(1)) - /// store.receive(.timerTicked) { $0.count = 1 } + /// scheduler.advance(by: .seconds(1)) + /// store.receive(.timerTicked) { $0.count = 1 } /// - /// scheduler.advance(by: .seconds(5)) - /// store.receive(.timerTicked) { $0.count = 2 } - /// store.receive(.timerTicked) { $0.count = 3 } - /// store.receive(.timerTicked) { $0.count = 4 } - /// store.receive(.timerTicked) { $0.count = 5 } - /// store.receive(.timerTicked) { $0.count = 6 } + /// scheduler.advance(by: .seconds(5)) + /// store.receive(.timerTicked) { $0.count = 2 } + /// store.receive(.timerTicked) { $0.count = 3 } + /// store.receive(.timerTicked) { $0.count = 4 } + /// store.receive(.timerTicked) { $0.count = 5 } + /// store.receive(.timerTicked) { $0.count = 6 } /// - /// store.send(.stopButtonTapped) - /// } + /// store.send(.stopButtonTapped) + /// } + /// ``` /// /// - Note: This effect is only meant to be used with features built in the Composable /// Architecture, and returned from a reducer. If you want a testable alternative to diff --git a/Sources/ComposableArchitecture/Reducer.swift b/Sources/ComposableArchitecture/Reducer.swift index e31bee98ad..8560b33e56 100644 --- a/Sources/ComposableArchitecture/Reducer.swift +++ b/Sources/ComposableArchitecture/Reducer.swift @@ -2,19 +2,19 @@ import CasePaths import Combine /// A reducer describes how to evolve the current state of an application to the next state, given -/// an action, and describes what `Effect`s should be executed later by the store, if any. +/// an action, and describes what ``Effect``s should be executed later by the store, if any. /// /// Reducers have 3 generics: /// /// * `State`: A type that holds the current state of the application. /// * `Action`: A type that holds all possible actions that cause the state of the application to /// change. -/// * `Environment`: A type that holds all dependencies needed in order to produce `Effect`s, such +/// * `Environment`: A type that holds all dependencies needed in order to produce ``Effect``s, such /// as API clients, analytics clients, random number generators, etc. /// /// - Note: The thread on which effects output is important. An effect's output is immediately sent -/// back into the store, and `Store` is not thread safe. This means all effects must receive -/// values on the same thread, **and** if the `Store` is being used to drive UI then all output +/// back into the store, and ``Store`` is not thread safe. This means all effects must receive +/// values on the same thread, **and** if the ``Store`` is being used to drive UI then all output /// must be on the main thread. You can use the `Publisher` method `receive(on:)` for make the /// effect output its values on the thread of your choice. public struct Reducer { @@ -25,10 +25,10 @@ public struct Reducer { /// The reducer takes three arguments: state, action and environment. The state is `inout` so that /// you can make any changes to it directly inline. The reducer must return an effect, which /// typically would be constructed by using the dependencies inside the `environment` value. If - /// no effect needs to be executed, a `.none` effect can be returned. + /// no effect needs to be executed, a ``Effect/none`` effect can be returned. /// /// For example: - /// + /// ```swift /// struct MyState { var count = 0, text = "" } /// enum MyAction { case buttonTapped, textChanged(String) } /// struct MyEnvironment { var analyticsClient: AnalyticsClient } @@ -44,6 +44,7 @@ public struct Reducer { /// return .none /// } /// } + /// ``` /// /// - Parameter reducer: A function signature that takes state, action and /// environment. @@ -67,21 +68,22 @@ public struct Reducer { /// its state, it can make a difference if `reducerA` chooses to modify `reducerB`'s state /// _before_ or _after_ `reducerB` runs. /// - /// This is perhaps most easily seen when working with `optional` reducers, where the parent + /// This is perhaps most easily seen when working with ``Reducer/optional`` reducers, where the parent /// domain may listen to the child domain and `nil` out its state. If the parent reducer runs /// before the child reducer, then the child reducer will not be able to react to its own action. /// - /// Similar can be said for a `forEach` reducer. If the parent domain modifies the child - /// collection by moving, removing, or modifying an element before the `forEach` reducer runs, the - /// `forEach` reducer may perform its action against the wrong element, an element that no longer + /// Similar can be said for a ``Reducer/forEach(state:action:environment:breakpointOnNil:_:_:)-3ic87`` reducer. If the parent domain modifies the child + /// collection by moving, removing, or modifying an element before the ``Reducer/forEach(state:action:environment:breakpointOnNil:_:_:)-3ic87`` reducer runs, the + /// ``Reducer/forEach(state:action:environment:breakpointOnNil:_:_:)-3ic87`` reducer may perform its action against the wrong element, an element that no longer /// exists, or an element in an unexpected state. /// /// Running a parent reducer before a child reducer can be considered an application logic /// error, and can produce assertion failures. So you should almost always combine reducers in /// order from child to parent domain. /// - /// Here is an example of how you should combine an `optional` reducer with a parent domain: + /// Here is an example of how you should combine an ``Reducer/optional`` reducer with a parent domain: /// + /// ```swift /// let parentReducer = Reducer.combine( /// // Combined before parent so that it can react to `.dismiss` while state is non-`nil`. /// childReducer.optional().pullback( @@ -99,6 +101,7 @@ public struct Reducer { /// } /// }, /// ) + /// ``` /// /// - Parameter reducers: A list of reducers. /// - Returns: A single reducer. @@ -117,21 +120,22 @@ public struct Reducer { /// its state, it can make a difference if `reducerA` chooses to modify `reducerB`'s state /// _before_ or _after_ `reducerB` runs. /// - /// This is perhaps most easily seen when working with `optional` reducers, where the parent + /// This is perhaps most easily seen when working with ``Reducer/optional`` reducers, where the parent /// domain may listen to the child domain and `nil` out its state. If the parent reducer runs /// before the child reducer, then the child reducer will not be able to react to its own action. /// - /// Similar can be said for a `forEach` reducer. If the parent domain modifies the child - /// collection by moving, removing, or modifying an element before the `forEach` reducer runs, the - /// `forEach` reducer may perform its action against the wrong element, an element that no longer + /// Similar can be said for a ``Reducer/forEach(state:action:environment:breakpointOnNil:_:_:)-3ic87`` reducer. If the parent domain modifies the child + /// collection by moving, removing, or modifying an element before the ``Reducer/forEach(state:action:environment:breakpointOnNil:_:_:)-3ic87`` reducer runs, the + /// ``Reducer/forEach(state:action:environment:breakpointOnNil:_:_:)-3ic87`` reducer may perform its action against the wrong element, an element that no longer /// exists, or an element in an unexpected state. /// /// Running a parent reducer before a child reducer can be considered an application logic /// error, and can produce assertion failures. So you should almost always combine reducers in /// order from child to parent domain. /// - /// Here is an example of how you should combine an `optional` reducer with a parent domain: + /// Here is an example of how you should combine an ``Reducer/optional`` reducer with a parent domain: /// + /// ```swift /// let parentReducer = Reducer.combine( /// // Combined before parent so that it can react to `.dismiss` while state is non-`nil`. /// childReducer.optional().pullback( @@ -149,6 +153,7 @@ public struct Reducer { /// } /// }, /// ) + /// ``` /// /// - Parameter reducers: An array of reducers. /// - Returns: A single reducer. @@ -169,21 +174,22 @@ public struct Reducer { /// its state, it can make a difference if `reducerA` chooses to modify `reducerB`'s state /// _before_ or _after_ `reducerB` runs. /// - /// This is perhaps most easily seen when working with `optional` reducers, where the parent + /// This is perhaps most easily seen when working with ``Reducer/optional`` reducers, where the parent /// domain may listen to the child domain and `nil` out its state. If the parent reducer runs /// before the child reducer, then the child reducer will not be able to react to its own action. /// - /// Similar can be said for a `forEach` reducer. If the parent domain modifies the child - /// collection by moving, removing, or modifying an element before the `forEach` reducer runs, the - /// `forEach` reducer may perform its action against the wrong element, an element that no longer + /// Similar can be said for a ``Reducer/forEach(state:action:environment:breakpointOnNil:_:_:)-3ic87`` reducer. If the parent domain modifies the child + /// collection by moving, removing, or modifying an element before the ``Reducer/forEach(state:action:environment:breakpointOnNil:_:_:)-3ic87`` reducer runs, the + /// ``Reducer/forEach(state:action:environment:breakpointOnNil:_:_:)-3ic87`` reducer may perform its action against the wrong element, an element that no longer /// exists, or an element in an unexpected state. /// /// Running a parent reducer before a child reducer can be considered an application logic /// error, and can produce assertion failures. So you should almost always combine reducers in /// order from child to parent domain. /// - /// Here is an example of how you should combine an `optional` reducer with a parent domain: + /// Here is an example of how you should combine an ``Reducer/optional`` reducer with a parent domain: /// + /// ```swift /// let parentReducer: Reducer = /// // Run before parent so that it can react to `.dismiss` while state is non-`nil`. /// childReducer @@ -204,6 +210,7 @@ public struct Reducer { /// } /// } /// ) + /// ``` /// /// - Parameter other: Another reducer. /// - Returns: A single reducer. @@ -220,9 +227,10 @@ public struct Reducer { /// * A function that can transform the global environment into a local environment. /// /// This operation is important for breaking down large reducers into small ones. When used with - /// the `combine` operator you can define many reducers that work on small pieces of domain, and + /// the ``combine(_:)-1ern2`` operator you can define many reducers that work on small pieces of domain, and /// then _pull them back_ and _combine_ them into one big reducer that works on a large domain. /// + /// ```swift /// // Global domain that holds a local domain: /// struct AppState { var settings: SettingsState, /* rest of state */ } /// enum AppAction { case settings(SettingsAction), /* other actions */ } @@ -241,6 +249,7 @@ public struct Reducer { /// /// /* other reducers */ /// ) + /// ``` /// /// - Parameters: /// - toLocalState: A key path that can get/set `State` inside `GlobalState`. @@ -475,10 +484,11 @@ public struct Reducer { /// Transforms a reducer that works on non-optional state into one that works on optional state by /// only running the non-optional reducer when state is non-nil. /// - /// Often used in tandem with `pullback` to transform a reducer on a non-optional child domain + /// Often used in tandem with ``pullback(state:action:environment:)`` to transform a reducer on a non-optional child domain /// into a reducer that can be combined with a reducer on a parent domain that contains some /// optional child domain: /// + /// ```swift /// // Global domain that holds an optional local domain: /// struct AppState { var modal: ModalState? } /// enum AppAction { case modal(ModalAction) } @@ -498,6 +508,7 @@ public struct Reducer { /// ... /// } /// ) + /// ``` /// /// Take care when combining optional reducers into parent domains. An optional reducer cannot /// process actions in its domain when its state is `nil`. If a child action is sent to an @@ -507,6 +518,7 @@ public struct Reducer { /// * A parent reducer sets child state to `nil` when processing a child action and runs /// _before_ the child reducer: /// + /// ```swift /// let parentReducer = Reducer.combine( /// // When combining reducers, the parent reducer runs first /// Reducer { state, action, environment in @@ -529,10 +541,12 @@ public struct Reducer { /// // This action is never received here because child state is `nil` in the parent /// ... /// } + /// ``` /// /// To ensure that a child reducer can process any action that a parent may use to `nil` out /// its state, combine it _before_ the parent: /// + /// ```swift /// let parentReducer = Reducer.combine( /// // The child runs first /// childReducer.optional().pullback(...), @@ -541,9 +555,11 @@ public struct Reducer { /// ... /// } /// ) + /// ``` /// /// * A child effect feeds a child action back into the store when child state is `nil`: /// + /// ```swift /// let childReducer = Reducer< /// ChildState, ChildAction, ChildEnvironment /// > { state, action environment in @@ -559,11 +575,13 @@ public struct Reducer { /// ... /// } /// } + /// ``` /// /// It is perfectly reasonable to ignore the result of an effect when child state is `nil`, /// for example one-off effects that you don't want to cancel. However, many long-living /// effects _should_ be explicitly canceled when tearing down a child domain: /// + /// ```swift /// let childReducer = Reducer< /// ChildState, ChildAction, ChildEnvironment /// > { state, action environment in @@ -583,18 +601,22 @@ public struct Reducer { /// ... /// } /// } + /// ``` /// /// * A view store sends a child action when child state is `nil`: /// + /// ```swift /// WithViewStore(self.parentStore) { parentViewStore in /// // If child state is `nil`, it cannot process this action. /// Button("Child Action") { parentViewStore.send(.child(.action)) } /// ... /// } + /// ``` /// - /// Use `Store.scope` with `IfLetStore` or `Store.ifLet` to ensure that views can only send + /// Use ``Store/scope(state:action:)-9iai9`` with ``IfLetStore`` or ``Store/ifLet(then:else:)`` to ensure that views can only send /// child actions when the child domain is non-`nil`. /// + /// ```swift /// IfLetStore( /// self.parentStore.scope(state: { $0.child }, action: { .child($0) } /// ) { childStore in @@ -605,10 +627,11 @@ public struct Reducer { /// } /// ... /// } + /// ``` /// - /// - See also: `IfLetStore`, a SwiftUI helper for transforming a store on optional state into a + /// - See also: ``IfLetStore``, a SwiftUI helper for transforming a store on optional state into a /// store on non-optional state. - /// - See also: `Store.ifLet`, a UIKit helper for doing imperative work with a store on optional + /// - See also: ``Store/ifLet(then:else:)``, a UIKit helper for doing imperative work with a store on optional /// state. /// /// - Parameter breakpointOnNil: Raises `SIGTRAP` signal when an action is sent to the reducer @@ -656,9 +679,9 @@ public struct Reducer { } } - /// A version of `pullback` that transforms a reducer that works on an element into one that works + /// A version of ``pullback(state:action:environment:)`` that transforms a reducer that works on an element into one that works /// on a collection of elements. - /// + /// ```swift /// // Global domain that holds a collection of local domains: /// struct AppState { var todos: [Todo] } /// enum AppAction { case todo(index: Int, action: TodoAction) } @@ -678,9 +701,10 @@ public struct Reducer { /// ... /// } /// ) + /// ``` /// - /// Take care when combining `forEach` reducers into parent domains, as order matters. Always - /// combine `forEach` reducers _before_ parent reducers that can modify the collection. + /// Take care when combining ``forEach(state:action:environment:breakpointOnNil:_:_:)-3ic87`` reducers into parent domains, as order matters. Always + /// combine ``forEach(state:action:environment:breakpointOnNil:_:_:)-3ic87`` reducers _before_ parent reducers that can modify the collection. /// /// - Parameters: /// - toLocalState: A key path that can get/set an array of `State` elements inside. @@ -745,9 +769,10 @@ public struct Reducer { } } - /// A version of `pullback` that transforms a reducer that works on an element into one that works + /// A version of ``pullback(state:action:environment:)`` that transforms a reducer that works on an element into one that works /// on an identified array of elements. /// + /// ```swift /// // Global domain that holds a collection of local domains: /// struct AppState { var todos: IdentifiedArrayOf } /// enum AppAction { case todo(id: Todo.ID, action: TodoAction) } @@ -767,9 +792,10 @@ public struct Reducer { /// ... /// } /// ) + /// ``` /// - /// Take care when combining `forEach` reducers into parent domains, as order matters. Always - /// combine `forEach` reducers _before_ parent reducers that can modify the collection. + /// Take care when combining ``forEach(state:action:environment:breakpointOnNil:_:_:)-90ox5`` reducers into parent domains, as order matters. Always + /// combine ``forEach(state:action:environment:breakpointOnNil:_:_:)-90ox5`` reducers _before_ parent reducers that can modify the collection. /// /// - Parameters: /// - toLocalState: A key path that can get/set a collection of `State` elements inside @@ -836,11 +862,11 @@ public struct Reducer { } } - /// A version of `pullback` that transforms a reducer that works on an element into one that works + /// A version of ``pullback(state:action:environment:)`` that transforms a reducer that works on an element into one that works /// on a dictionary of element values. /// - /// Take care when combining `forEach` reducers into parent domains, as order matters. Always - /// combine `forEach` reducers _before_ parent reducers that can modify the dictionary. + /// Take care when combining ``forEach(state:action:environment:breakpointOnNil:_:_:)-xv1z`` reducers into parent domains, as order matters. Always + /// combine ``forEach(state:action:environment:breakpointOnNil:_:_:)-xv1z`` reducers _before_ parent reducers that can modify the dictionary. /// /// - Parameters: /// - toLocalState: A key path that can get/set a dictionary of `State` values inside diff --git a/Sources/ComposableArchitecture/Store.swift b/Sources/ComposableArchitecture/Store.swift index 15c6ea9460..b988e0ae26 100644 --- a/Sources/ComposableArchitecture/Store.swift +++ b/Sources/ComposableArchitecture/Store.swift @@ -5,7 +5,7 @@ import Foundation /// around to views that need to interact with the application. /// /// You will typically construct a single one of these at the root of your application, and then use -/// the `scope` method to derive more focused stores that can be passed to subviews. +/// the ``scope(state:action:)-9iai9`` method to derive more focused stores that can be passed to subviews. public final class Store { var state: CurrentValueSubject var effectCancellables: [UUID: AnyCancellable] = [:] @@ -34,7 +34,7 @@ public final class Store { /// /// This can be useful for deriving new stores to hand to child views in an application. For /// example: - /// + /// ```swift /// // Application state made from local states. /// struct AppState { var login: LoginState, ... } /// struct AppAction { case login(LoginAction), ... } @@ -53,7 +53,7 @@ public final class Store { /// action: { AppAction.login($0) } /// ) /// ) - /// + /// ``` /// Scoping in this fashion allows you to better modularize your application. In this case, /// `LoginView` could be extracted to a module that has no access to `AppState` or `AppAction`. /// @@ -63,7 +63,7 @@ public final class Store { /// For example, the above login domain could model a two screen login flow: a login form followed /// by a two-factor authentication screen. The second screen's domain might be nested in the /// first: - /// + /// ```swift /// struct LoginState: Equatable { /// var email = "" /// var password = "" @@ -77,15 +77,15 @@ public final class Store { /// case passwordChanged(String) /// case twoFactorAuth(TwoFactorAuthAction) /// } - /// + /// ``` /// The login view holds onto a store of this domain: - /// + /// ```swift /// struct LoginView: View { /// let store: Store /// /// var body: some View { ... } /// } - /// + /// ``` /// If its body were to use a view store of the same domain, this would introduce a number of /// problems: /// @@ -104,7 +104,7 @@ public final class Store { /// /// To avoid these issues, one can introduce a view-specific domain that slices off the subset of /// state and actions that a view cares about: - /// + /// ```swift /// extension LoginView { /// struct State: Equatable { /// var email: String @@ -117,10 +117,10 @@ public final class Store { /// case passwordChanged(String) /// } /// } - /// + /// ``` /// One can also introduce a couple helpers that transform feature state into view state and /// transform view actions into feature actions. - /// + /// ```swift /// extension LoginState { /// var view: LoginView.State { /// .init(email: self.email, password: self.password) @@ -139,10 +139,11 @@ public final class Store { /// } /// } /// } + /// ``` /// /// With these helpers defined, `LoginView` can now scope its store's feature domain into its view /// domain: - /// + /// ```swift /// var body: some View { /// WithViewStore( /// self.store.scope(state: { $0.view }, action: { $0.feature }) @@ -150,7 +151,7 @@ public final class Store { /// ... /// } /// } - /// + /// ``` /// This view store is now incapable of reading any state but view state (and will not recompute /// when non-view state changes), and is incapable of sending any actions but view actions. /// diff --git a/Sources/ComposableArchitecture/SwiftUI/ActionSheet.swift b/Sources/ComposableArchitecture/SwiftUI/ActionSheet.swift index 15598f0e25..50c6b90e53 100644 --- a/Sources/ComposableArchitecture/SwiftUI/ActionSheet.swift +++ b/Sources/ComposableArchitecture/SwiftUI/ActionSheet.swift @@ -13,6 +13,7 @@ import SwiftUI /// /// To use this API, you model all the action sheet actions in your domain's action enum: /// +/// ```swift /// enum AppAction: Equatable { /// case cancelTapped /// case deleteTapped @@ -21,19 +22,23 @@ import SwiftUI /// /// // Your other actions /// } +/// ``` /// /// And you model the state for showing the action sheet in your domain's state, and it can start /// off in a `nil` state: /// +/// ```swift /// struct AppState: Equatable { /// var actionSheet: ActionSheetState? /// /// // Your other state /// } +/// ``` /// /// Then, in the reducer you can construct an `ActionSheetState` value to represent the action /// sheet you want to show to the user: /// +/// ```swift /// let appReducer = Reducer { state, action, env in /// switch action /// case .cancelTapped: @@ -60,15 +65,18 @@ import SwiftUI /// return .none /// } /// } +/// ``` /// /// And then, in your view you can use the `.actionSheet(_:send:dismiss:)` method on `View` in order /// to present the action sheet in a way that works best with the Composable Architecture: /// +/// ```swift /// Button("Info") { viewStore.send(.infoTapped) } /// .actionSheet( /// self.store.scope(state: \.actionSheet), /// dismiss: .cancelTapped /// ) +/// ``` /// /// This makes your reducer in complete control of when the action sheet is shown or dismissed, and /// makes it so that any choice made in the action sheet is automatically fed back into the reducer @@ -76,6 +84,7 @@ import SwiftUI /// /// Even better, you can instantly write tests that your action sheet behavior works as expected: /// +/// ```swift /// let store = TestStore( /// initialState: AppState(), /// reducer: appReducer, @@ -96,6 +105,7 @@ import SwiftUI /// $0.actionSheet = nil /// // Also verify that favoriting logic executed correctly /// } +/// ``` /// @available(iOS 13, *) @available(macCatalyst 13, *) diff --git a/Sources/ComposableArchitecture/SwiftUI/Alert.swift b/Sources/ComposableArchitecture/SwiftUI/Alert.swift index 65df601696..23d2eb2754 100644 --- a/Sources/ComposableArchitecture/SwiftUI/Alert.swift +++ b/Sources/ComposableArchitecture/SwiftUI/Alert.swift @@ -13,6 +13,7 @@ import SwiftUI /// /// To use this API, you model all the alert actions in your domain's action enum: /// +/// ```swift /// enum AppAction: Equatable { /// case cancelTapped /// case confirmTapped @@ -20,19 +21,23 @@ import SwiftUI /// /// // Your other actions /// } +/// ``` /// /// And you model the state for showing the alert in your domain's state, and it can start off /// `nil`: /// +/// ```swift /// struct AppState: Equatable { /// var alert: AlertState? /// /// // Your other state /// } +/// ``` /// -/// Then, in the reducer you can construct an `AlertState` value to represent the alert you want +/// Then, in the reducer you can construct an ``AlertState`` value to represent the alert you want /// to show to the user: /// +/// ```swift /// let appReducer = Reducer { state, action, env in /// switch action /// case .cancelTapped: @@ -53,15 +58,18 @@ import SwiftUI /// return .none /// } /// } +/// ``` /// /// And then, in your view you can use the `.alert(_:send:dismiss:)` method on `View` in order /// to present the alert in a way that works best with the Composable Architecture: /// +/// ```swift /// Button("Delete") { viewStore.send(.deleteTapped) } /// .alert( /// self.store.scope(state: \.alert), /// dismiss: .cancelTapped /// ) +/// ``` /// /// This makes your reducer in complete control of when the alert is shown or dismissed, and makes /// it so that any choice made in the alert is automatically fed back into the reducer so that you @@ -69,6 +77,7 @@ import SwiftUI /// /// Even better, you can instantly write tests that your alert behavior works as expected: /// +/// ```swift /// let store = TestStore( /// initialState: AppState(), /// reducer: appReducer, @@ -87,6 +96,7 @@ import SwiftUI /// $0.alert = nil /// // Also verify that delete logic executed correctly /// } +/// ``` /// public struct AlertState { public let id = UUID() diff --git a/Sources/ComposableArchitecture/SwiftUI/Binding.swift b/Sources/ComposableArchitecture/SwiftUI/Binding.swift index 18367f42a3..92babefcb1 100644 --- a/Sources/ComposableArchitecture/SwiftUI/Binding.swift +++ b/Sources/ComposableArchitecture/SwiftUI/Binding.swift @@ -8,6 +8,7 @@ import SwiftUI /// /// For example, a settings screen may model its state with the following struct: /// +/// ```swift /// struct SettingsState { /// var digest = Digest.daily /// var displayName = "" @@ -16,11 +17,13 @@ import SwiftUI /// var sendEmailNotifications = false /// var sendMobileNotifications = false /// } +/// ``` /// /// Each of these fields should be editable, and in the Composable Architecture this means that each /// field requires a corresponding action that can be sent to the store. Typically this comes in the /// form of an enum with a case per field: /// +/// ```swift /// enum SettingsAction { /// case digestChanged(Digest) /// case displayNameChanged(String) @@ -29,10 +32,12 @@ import SwiftUI /// case sendEmailNotificationsChanged(Bool) /// case sendMobileNotificationsChanged(Bool) /// } +/// ``` /// /// And we're not even done yet. In the reducer we must now handle each action, which simply /// replaces the state at each field with a new value: /// +/// ```swift /// let settingsReducer = Reducer< /// SettingsState, SettingsAction, SettingsEnvironment /// > { state, action, environment in @@ -62,19 +67,23 @@ import SwiftUI /// return .none /// } /// } +/// ``` /// /// This is a _lot_ of boilerplate for something that should be simple. Luckily, we can dramatically -/// eliminate this boilerplate using `BindingAction`. First, we can collapse all of these -/// field-mutating actions into a single case that holds a `BindingAction` generic over the +/// eliminate this boilerplate using ``BindingAction``. First, we can collapse all of these +/// field-mutating actions into a single case that holds a ``BindingAction`` generic over the /// reducer's root `SettingsState`: /// +/// ```swift /// enum SettingsAction { /// case binding(BindingAction) /// } +/// ``` /// /// And then, we can simplify the settings reducer by allowing the `binding` method to handle these /// field mutations for us: /// +/// ```swift /// let settingsReducer = Reducer< /// SettingsState, SettingsAction, SettingsEnvironment /// > { @@ -84,30 +93,36 @@ import SwiftUI /// } /// } /// .binding(action: /SettingsAction.binding) +/// ``` /// /// Binding actions are constructed and sent to the store by providing a writable key path from root /// state to the field being mutated. There is even a view store helper that simplifies this work. /// You can derive a binding by specifying the key path and binding action case: /// +/// ```swift /// TextField( /// "Display name", /// text: viewStore.binding(keyPath: \.displayName, send: SettingsAction.binding) /// ) +/// ``` /// /// Should you need to layer additional functionality over these bindings, your reducer can pattern /// match the action for a given key path: /// +/// ```swift /// case .binding(\.displayName): /// // Validate display name /// /// case .binding(\.enableNotifications): /// // Return an authorization request effect +/// ``` /// /// Binding actions can also be tested in much the same way regular actions are tested. Rather than /// send a specific action describing how a binding changed, such as `displayNameChanged("Blob")`, -/// you will send a `.binding` action that describes which key path is being set to what value, such +/// you will send a ``Reducer/binding(action:)`` action that describes which key path is being set to what value, such /// as `.binding(.set(\.displayName, "Blob"))`: /// +/// ```swift /// let store = TestStore( /// initialState: SettingsState(), /// reducer: settingsReducer, @@ -120,6 +135,7 @@ import SwiftUI /// store.send(.binding(.set(\.protectMyPosts, true))) { /// $0.protectMyPosts = true /// ) +/// ``` /// public struct BindingAction: Equatable { public let keyPath: PartialKeyPath @@ -177,28 +193,32 @@ public struct BindingAction: Equatable { } extension Reducer { - /// Returns a reducer that applies `BindingAction` mutations to `State` before running this + /// Returns a reducer that applies ``BindingAction`` mutations to `State` before running this /// reducer's logic. /// - /// For example, a settings screen may gather its binding actions into a single `BindingAction` + /// For example, a settings screen may gather its binding actions into a single ``BindingAction`` /// case: /// + /// ```swift /// enum SettingsAction { /// ... /// case binding(BindingAction) /// } + /// ``` /// /// The reducer can then be enhanced to automatically handle these mutations for you by tacking on - /// the `binding` method: + /// the ``binding(action:)`` method: /// + /// ```swift /// let settingsReducer = Reducer>) -> Self { Self { state, action, environment in @@ -211,10 +231,11 @@ extension Reducer { extension ViewStore { /// Derives a binding from the store that mutates state at the given writable key path by wrapping - /// a `BindingAction` with the store's action type. + /// a ``BindingAction`` with the store's action type. /// /// For example, a text field binding can be created like this: /// + /// ```swift /// struct State { var text = "" } /// enum Action { case binding(BindingAction) } /// @@ -222,6 +243,7 @@ extension ViewStore { /// "Enter text", /// text: viewStore.binding(keyPath: \.text, Action.binding) /// ) + /// ``` /// /// - Parameters: /// - keyPath: A writable key path from the view store's state to a mutable field diff --git a/Sources/ComposableArchitecture/SwiftUI/ForEachStore.swift b/Sources/ComposableArchitecture/SwiftUI/ForEachStore.swift index 97d7f423c0..9283eab22d 100644 --- a/Sources/ComposableArchitecture/SwiftUI/ForEachStore.swift +++ b/Sources/ComposableArchitecture/SwiftUI/ForEachStore.swift @@ -3,12 +3,13 @@ import SwiftUI /// A Composable Architecture-friendly wrapper around `ForEach` that simplifies working with /// collections of state. /// -/// `ForEachStore` loops over a store's collection with a store scoped to the domain of each +/// ``ForEachStore`` loops over a store's collection with a store scoped to the domain of each /// element. This allows you to extract and modularize an element's view and avoid concerns around /// collection index math and parent-child store communication. /// /// For example, a todos app may define the domain and logic associated with an individual todo: /// +/// ```swift /// struct TodoState: Equatable, Identifiable { /// let id: UUID /// var description = "" @@ -20,42 +21,54 @@ import SwiftUI /// } /// struct TodoEnvironment {} /// let todoReducer = Reducer /// var body: some View { ... } /// } +/// ``` /// /// For a parent domain to work with a collection of todos, it can hold onto this collection in /// state: /// +/// ```swift /// struct AppState: Equatable { /// var todos: IdentifiedArrayOf = [] /// } +/// ``` /// /// Define a case to handle actions sent to the child domain: /// +/// ```swift /// enum AppAction { /// case todo(id: TodoState.ID, action: TodoAction) /// } +/// ``` /// -/// Enhance its reducer using `forEach`: +/// Enhance its reducer using ``Reducer/forEach(state:action:environment:breakpointOnNil:_:_:)-3ic87``: /// +/// ```swift /// let appReducer = todoReducer.forEach( /// state: \.todos, /// action: /AppAction.todo(id:action:), /// environment: { _ in TodoEnvironment() } /// ) +/// ``` /// -/// And finally render a list of `TodoView`s using `ForEachStore`: +/// And finally render a list of `TodoView`s using ``ForEachStore``: /// +/// ```swift /// ForEachStore( /// self.store.scope(state: \.todos, AppAction.todo(id:action:)) /// ) { todoStore in /// TodoView(store: todoStore) /// } +/// ``` +/// public struct ForEachStore: DynamicViewContent where Data: Collection, ID: Hashable, Content: View { public let data: Data diff --git a/Sources/ComposableArchitecture/SwiftUI/Identified.swift b/Sources/ComposableArchitecture/SwiftUI/Identified.swift index 7c7ebfb6a7..3e50a26e41 100644 --- a/Sources/ComposableArchitecture/SwiftUI/Identified.swift +++ b/Sources/ComposableArchitecture/SwiftUI/Identified.swift @@ -17,7 +17,9 @@ public struct Identified: Identifiable where ID: Hashable { /// Initializes an identified value from a given value and a function that can return a hashable /// identifier from the value. /// + /// ```swift /// Identified(uuid, id: \.self) + /// ``` /// /// - Parameters: /// - value: A value. @@ -30,7 +32,9 @@ public struct Identified: Identifiable where ID: Hashable { /// Initializes an identified value from a given value and a function that can return a hashable /// identifier from the value. /// + /// ```swift /// Identified(uuid, id: \.self) + /// ``` /// /// - Parameters: /// - value: A value. diff --git a/Sources/ComposableArchitecture/SwiftUI/IdentifiedArray.swift b/Sources/ComposableArchitecture/SwiftUI/IdentifiedArray.swift index 7d12824470..4ede00d861 100644 --- a/Sources/ComposableArchitecture/SwiftUI/IdentifiedArray.swift +++ b/Sources/ComposableArchitecture/SwiftUI/IdentifiedArray.swift @@ -1,19 +1,22 @@ import Foundation /// An array of elements that can be identified by a given key path. -/// -/// A useful container of state that is intended to interface with `SwiftUI.ForEach`. For example, +///` +/// A useful container of state that is intended to interface with `SwiftUI/ForEach`. For example, /// your application may model a counter in an identifiable fashion: /// +/// ```swift /// struct CounterState: Identifiable { /// let id: UUID /// var count = 0 /// } /// enum CounterAction { case incr, decr } /// let counterReducer = Reducer { ... } +/// ``` /// -/// This domain can be pulled back to a larger domain with the `forEach` method: +/// This domain can be pulled back to a larger domain with the ``Reducer/forEach(state:action:environment:breakpointOnNil:_:_:)-90ox5`` method: /// +/// ```swift /// struct AppState { var counters = IdentifiedArrayOf() } /// enum AppAction { case counter(id: UUID, action: CounterAction) } /// let appReducer = counterReducer.forEach( @@ -21,9 +24,11 @@ import Foundation /// action: /AppAction.counter(id:action:), /// environment: { $0 } /// ) +/// ``` /// /// And then SwiftUI can work with this array of identified elements in a list view: /// +/// ```swift /// struct AppView: View { /// let store: Store /// @@ -36,6 +41,8 @@ import Foundation /// } /// } /// } +/// ``` +/// public struct IdentifiedArray: MutableCollection, RandomAccessCollection where ID: Hashable { /// A key path to a value that identifies an element. diff --git a/Sources/ComposableArchitecture/SwiftUI/IfLetStore.swift b/Sources/ComposableArchitecture/SwiftUI/IfLetStore.swift index 5241b676cf..3018741bb4 100644 --- a/Sources/ComposableArchitecture/SwiftUI/IfLetStore.swift +++ b/Sources/ComposableArchitecture/SwiftUI/IfLetStore.swift @@ -2,19 +2,22 @@ import SwiftUI /// A view that safely unwraps a store of optional state in order to show one of two views. /// -/// When the underlying state is non-`nil`, the `then` closure will be performed with a `Store` that +/// When the underlying state is non-`nil`, the `then` closure will be performed with a ``Store`` that /// holds onto non-optional state, and otherwise the `else` closure will be performed. /// /// This is useful for deciding between two views to show depending on an optional piece of state: /// +/// ```swift /// IfLetStore( /// store.scope(state: \SearchState.results, action: SearchAction.results), /// then: SearchResultsView.init(store:), /// else: { Text("Loading search results...") } /// ) +/// ``` /// /// And for performing navigation when a piece of state becomes non-`nil`: /// +/// ```swift /// NavigationLink( /// destination: IfLetStore( /// self.store.scope(state: \.detail, action: AppAction.detail), @@ -27,12 +30,13 @@ import SwiftUI /// ) { /// Text("Start!") /// } +/// ``` /// public struct IfLetStore: View where Content: View { private let content: (ViewStore) -> Content private let store: Store - /// Initializes an `IfLetStore` view that computes content depending on if a store of optional + /// Initializes an ``IfLetStore`` view that computes content depending on if a store of optional /// state is `nil` or non-`nil`. /// /// - Parameters: @@ -55,7 +59,7 @@ public struct IfLetStore: View where Content: View { } } - /// Initializes an `IfLetStore` view that computes content depending on if a store of optional + /// Initializes an ``IfLetStore`` view that computes content depending on if a store of optional /// state is `nil` or non-`nil`. /// /// - Parameters: diff --git a/Sources/ComposableArchitecture/SwiftUI/TextState.swift b/Sources/ComposableArchitecture/SwiftUI/TextState.swift index 8015744bea..d4cc9aa886 100644 --- a/Sources/ComposableArchitecture/SwiftUI/TextState.swift +++ b/Sources/ComposableArchitecture/SwiftUI/TextState.swift @@ -7,42 +7,50 @@ import SwiftUI /// `Equatable`, their `==` do not return `true` when used with seemingly equal values. If we were /// to naively store these values in state, our tests may begin to fail. /// -/// `TextState` solves this problem by providing an interface similar to `SwiftUI.Text` that can be +/// ``TextState`` solves this problem by providing an interface similar to `SwiftUI.Text` that can be /// held in state and asserted against. /// /// Let's say you wanted to hold some dynamic, styled text content in your app state. You could use -/// `TextState`: +/// ``TextState``: /// +/// ```swift /// struct AppState: Equatable { /// var label: TextState /// } +/// ``` /// /// Your reducer can then assign a value to this state using an API similar to that of /// `SwiftUI.Text`. /// +/// ```swift /// state.label = TextState("Hello, ") + TextState(name).bold() + TextState("!") +/// ``` /// /// And your view store can render it directly: /// +/// ```swift /// var body: some View { /// WithViewStore(self.store) { viewStore in /// viewStore.label /// } /// } +/// ``` /// /// Certain SwiftUI APIs, like alerts and action sheets, take `Text` values and, not views. To -/// convert `TextState` to `SwiftUI.Text` for this purpose, you can use the `Text` initializer: +/// convert ``TextState`` to `SwiftUI.Text` for this purpose, you can use the `Text` initializer: /// +/// ```swift /// Alert(title: Text(viewStore.label)) +/// ``` /// /// The Composable Architecture comes with a few convenience APIs for alerts and action sheets that -/// wrap `TextState` under the hood. See `AlertState` and `ActionState` accordingly. +/// wrap ``TextState`` under the hood. See ``AlertState`` and `ActionState` accordingly. /// /// In the future, should `SwiftUI.Text` and `SwiftUI.LocalizedStringKey` reliably conform to -/// `Equatable`, `TextState` may be deprecated. +/// `Equatable`, ``TextState`` may be deprecated. /// -/// - Note: `TextState` does not support _all_ `LocalizedStringKey` permutations at this time -/// (interpolated `SwiftUI.Image`s, for example. `TextState` also uses reflection to determine +/// - Note: ``TextState`` does not support _all_ `LocalizedStringKey` permutations at this time +/// (interpolated `SwiftUI.Image`s, for example. ``TextState`` also uses reflection to determine /// `LocalizedStringKey` equatability, so be mindful of edge cases. public struct TextState: Equatable, Hashable { fileprivate var modifiers: [Modifier] = [] diff --git a/Sources/ComposableArchitecture/SwiftUI/WithViewStore.swift b/Sources/ComposableArchitecture/SwiftUI/WithViewStore.swift index 8610d6d58b..b302a51b3f 100644 --- a/Sources/ComposableArchitecture/SwiftUI/WithViewStore.swift +++ b/Sources/ComposableArchitecture/SwiftUI/WithViewStore.swift @@ -7,14 +7,14 @@ import SwiftUI /// Due to a bug in SwiftUI, there are times that use of this view can interfere with some core /// views provided by SwiftUI. The known problematic views are: /// -/// * If a `GeometryReader` or `ScrollViewReader` is used inside a `WithViewStore` it will not +/// * If a `GeometryReader` or `ScrollViewReader` is used inside a ``WithViewStore`` it will not /// receive state updates correctly. To work around you either need to reorder the views so that -/// the `GeometryReader` or `ScrollViewReader` wraps `WithViewStore`, or, if that is not +/// the `GeometryReader` or `ScrollViewReader` wraps ``WithViewStore``, or, if that is not /// possible, then you must hold onto an explicit /// `@ObservedObject var viewStore: ViewStore` in your view in lieu of using this /// helper (see [here](https://gist.github.com/mbrandonw/cc5da3d487bcf7c4f21c27019a440d18)). /// * If you create a `Stepper` via the `Stepper.init(onIncrement:onDecrement:label:)` initializer -/// inside a `WithViewStore` it will behave erratically. To work around you should use the +/// inside a ``WithViewStore`` it will behave erratically. To work around you should use the /// initializer that takes a binding (see /// [here](https://gist.github.com/mbrandonw/dee2ceac2c316a1619cfdf1dc7945f66)). public struct WithViewStore { diff --git a/Sources/ComposableArchitecture/TestSupport/FailingEffect.swift b/Sources/ComposableArchitecture/TestSupport/FailingEffect.swift index 1336fc2ff8..7dcf61fcff 100644 --- a/Sources/ComposableArchitecture/TestSupport/FailingEffect.swift +++ b/Sources/ComposableArchitecture/TestSupport/FailingEffect.swift @@ -10,6 +10,7 @@ /// For example, let's say we have a very simple counter application, where a user can increment /// and decrement a number. The state and actions are simple enough: /// + /// ```swift /// struct CounterState: Equatable { /// var count = 0 /// } @@ -18,18 +19,22 @@ /// case decrementButtonTapped /// case incrementButtonTapped /// } + /// ``` /// /// Let's throw in a side effect. If the user attempts to decrement the counter below zero, the /// application should refuse and play an alert sound instead. /// /// We can model playing a sound in the environment with an effect: /// + /// ```swift /// struct CounterEnvironment { /// let playAlertSound: () -> Effect /// } + /// ``` /// /// Now that we've defined the domain, we can describe the logic in a reducer: /// + /// ```swift /// let counterReducer = Reducer< /// CounterState, CounterAction, CounterEnvironment /// > { state, action, environment in @@ -48,11 +53,13 @@ /// return .non /// } /// } + /// ``` /// /// Let's say we want to write a test for the increment path. We can see in the reducer that it /// should never play an alert, so we can configure the environment with an effect that will /// fail if it ever executes: /// + /// ```swift /// func testIncrement() { /// let store = TestStore( /// initialState: CounterState(count: 0) @@ -66,6 +73,7 @@ /// $0.count = 1 /// } /// } + /// ``` /// /// By using a `.failing` effect in our environment we have strengthened the assertion and made /// the test easier to understand at the same time. We can see, without consulting the reducer diff --git a/Sources/ComposableArchitecture/TestSupport/TestStore.swift b/Sources/ComposableArchitecture/TestSupport/TestStore.swift index 73e0205343..6cb627c932 100644 --- a/Sources/ComposableArchitecture/TestSupport/TestStore.swift +++ b/Sources/ComposableArchitecture/TestSupport/TestStore.swift @@ -38,6 +38,7 @@ /// /// For example, given a simple counter reducer: /// + /// ```swift /// struct CounterState { /// var count = 0 /// } @@ -58,9 +59,11 @@ /// return .none /// } /// } + /// ``` /// /// One can assert against its behavior over time: /// + /// ```swift /// class CounterTests: XCTestCase { /// func testCounter() { /// let store = TestStore( @@ -73,14 +76,16 @@ /// } /// } /// } + /// ``` /// /// Note that in the trailing closure of `.send(.incrementButtonTapped)` we are given a single /// mutable value of the state before the action was sent, and it is our job to mutate the value /// to match the state after the action was sent. In this case the `count` field changes to `1`. /// /// For a more complex example, consider the following bare-bones search feature that uses the - /// `.debounce` operator to wait for the user to stop typing before making a network request: + /// ``Effect/debounce(id:for:scheduler:options:)`` operator to wait for the user to stop typing before making a network request: /// + /// ```swift /// struct SearchState: Equatable { /// var query = "" /// var results: [String] = [] @@ -112,9 +117,11 @@ /// return .none /// } /// } + /// ```swift /// /// It can be fully tested by controlling the environment's scheduler and effect: /// + /// ```swift /// // Create a test dispatch scheduler to control the timing of effects /// let scheduler = DispatchQueue.test /// @@ -153,6 +160,7 @@ /// // Assert that state updates accordingly /// $0.results = ["Composable Architecture"] /// } + /// ``` /// /// This test is proving that the debounced network requests are correctly canceled when we do not /// wait longer than the 0.5 seconds, because if it wasn't and it delivered an action when we did @@ -618,7 +626,7 @@ /// A step that captures a sub-sequence of steps. /// - /// - Parameter steps: An array of `Step` + /// - Parameter steps: An array of ``TestStore/Step`` /// - Returns: A step that captures a sub-sequence of steps. public static func sequence( _ steps: [Step], @@ -630,7 +638,7 @@ /// A step that captures a sub-sequence of steps. /// - /// - Parameter steps: A variadic list of `Step` + /// - Parameter steps: A variadic list of ``TestStore/Step`` /// - Returns: A step that captures a sub-sequence of steps. public static func sequence( _ steps: Step..., diff --git a/Sources/ComposableArchitecture/UIKit/IfLetUIKit.swift b/Sources/ComposableArchitecture/UIKit/IfLetUIKit.swift index 48fd83e017..9e1a52cf0c 100644 --- a/Sources/ComposableArchitecture/UIKit/IfLetUIKit.swift +++ b/Sources/ComposableArchitecture/UIKit/IfLetUIKit.swift @@ -12,6 +12,7 @@ extension Store { /// to navigate to can be held as an optional value in the parent, and when that value goes from /// `nil` to non-`nil`, or non-`nil` to `nil`, you can update the navigation stack accordingly: /// + /// ```swift /// class ParentViewController: UIViewController { /// let store: Store /// var cancellables: Set = [] @@ -35,6 +36,7 @@ extension Store { /// .store(in: &self.cancellables) /// } /// } + /// ``` /// /// - Parameters: /// - unwrap: A function that is called with a store of non-optional state when the store's diff --git a/Sources/ComposableArchitecture/ViewStore.swift b/Sources/ComposableArchitecture/ViewStore.swift index eb60e0743f..409b712e5e 100644 --- a/Sources/ComposableArchitecture/ViewStore.swift +++ b/Sources/ComposableArchitecture/ViewStore.swift @@ -1,14 +1,15 @@ import Combine import SwiftUI -/// A `ViewStore` is an object that can observe state changes and send actions. They are most +/// A ``ViewStore`` is an object that can observe state changes and send actions. They are most /// commonly used in views, such as SwiftUI views, UIView or UIViewController, but they can be /// used anywhere it makes sense to observe state and send actions. /// -/// In SwiftUI applications, a `ViewStore` is accessed most commonly using the `WithViewStore` view. +/// In SwiftUI applications, a ``ViewStore`` is accessed most commonly using the ``WithViewStore`` view. /// It can be initialized with a store and a closure that is handed a view store and must return a /// view to be rendered: /// +/// ```swift /// var body: some View { /// WithViewStore(self.store) { viewStore in /// VStack { @@ -17,10 +18,11 @@ import SwiftUI /// } /// } /// } -/// -/// In UIKit applications a `ViewStore` can be created from a `Store` and then subscribed to for +/// ``` +/// In UIKit applications a ``ViewStore`` can be created from a ``Store`` and then subscribed to for /// state updates: /// +/// ```swift /// let store: Store /// let viewStore: ViewStore /// @@ -40,6 +42,7 @@ import SwiftUI /// @objc func incrementButtonTapped() { /// self.viewStore.send(.incrementButtonTapped) /// } +/// ``` /// @dynamicMemberLookup public final class ViewStore: ObservableObject { @@ -85,10 +88,10 @@ public final class ViewStore: ObservableObject { /// Sends an action to the store. /// - /// `ViewStore` is not thread safe and you should only send actions to it from the main thread. + /// ``ViewStore`` is not thread safe and you should only send actions to it from the main thread. /// If you are wanting to send actions on background threads due to the fact that the reducer /// is performing computationally expensive work, then a better way to handle this is to wrap - /// that work in an `Effect` that is performed on a background thread so that the result can + /// that work in an ``Effect`` that is performed on a background thread so that the result can /// be fed back into the store. /// /// - Parameter action: An action. @@ -100,11 +103,12 @@ public final class ViewStore: ObservableObject { /// actions to the store. /// /// The method is useful for dealing with SwiftUI components that work with two-way `Binding`s - /// since the `Store` does not allow directly writing its state; it only allows reading state and + /// since the ``Store`` does not allow directly writing its state; it only allows reading state and /// sending actions. /// /// For example, a text field binding can be created like this: /// + /// ```swift /// struct State { var name = "" } /// enum Action { case nameChanged(String) } /// @@ -115,6 +119,7 @@ public final class ViewStore: ObservableObject { /// send: { Action.nameChanged($0) } /// ) /// ) + /// ``` /// /// - Parameters: /// - get: A function to get the state for the binding from the view @@ -144,11 +149,12 @@ public final class ViewStore: ObservableObject { /// actions to the store. /// /// The method is useful for dealing with SwiftUI components that work with two-way `Binding`s - /// since the `Store` does not allow directly writing its state; it only allows reading state and + /// since the ``Store`` does not allow directly writing its state; it only allows reading state and /// sending actions. /// /// For example, an alert binding can be dealt with like this: /// + /// ```swift /// struct State { var alert: String? } /// enum Action { case alertDismissed } /// @@ -158,6 +164,7 @@ public final class ViewStore: ObservableObject { /// send: .alertDismissed /// ) /// ) { alert in Alert(title: Text(alert.message)) } + /// ``` /// /// - Parameters: /// - get: A function to get the state for the binding from the view store's full state. @@ -174,11 +181,12 @@ public final class ViewStore: ObservableObject { /// actions to the store. /// /// The method is useful for dealing with SwiftUI components that work with two-way `Binding`s - /// since the `Store` does not allow directly writing its state; it only allows reading state and + /// since the ``Store`` does not allow directly writing its state; it only allows reading state and /// sending actions. /// /// For example, a text field binding can be created like this: /// + /// ```swift /// typealias State = String /// enum Action { case nameChanged(String) } /// @@ -188,6 +196,7 @@ public final class ViewStore: ObservableObject { /// send: { Action.nameChanged($0) } /// ) /// ) + /// ``` /// /// - Parameters: /// - localStateToViewAction: A function that transforms the binding's value @@ -203,11 +212,12 @@ public final class ViewStore: ObservableObject { /// actions to the store. /// /// The method is useful for dealing with SwiftUI components that work with two-way `Binding`s - /// since the `Store` does not allow directly writing its state; it only allows reading state and + /// since the ``Store`` does not allow directly writing its state; it only allows reading state and /// sending actions. /// /// For example, an alert binding can be dealt with like this: /// + /// ```swift /// typealias State = String /// enum Action { case alertDismissed } /// @@ -216,6 +226,7 @@ public final class ViewStore: ObservableObject { /// send: .alertDismissed /// ) /// ) { title in Alert(title: Text(title)) } + /// ``` /// /// - Parameters: /// - action: The action to send when the binding is written to.