mirror of
https://github.com/pointfreeco/swift-composable-architecture.git
synced 2025-12-20 09:11:33 +01:00
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>
This commit is contained in:
@@ -1,16 +1,20 @@
|
|||||||
import CasePaths
|
import CasePaths
|
||||||
import Dispatch
|
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.
|
/// higher-order reducer.
|
||||||
public enum ActionFormat {
|
public enum ActionFormat {
|
||||||
/// Prints the action in a single line by only specifying the labels of the associated values:
|
/// Prints the action in a single line by only specifying the labels of the associated values:
|
||||||
///
|
///
|
||||||
|
/// ```swift
|
||||||
/// Action.screenA(.row(index:, action: .textChanged(query:)))
|
/// Action.screenA(.row(index:, action: .textChanged(query:)))
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
case labelsOnly
|
case labelsOnly
|
||||||
/// Prints the action in a multiline, pretty-printed format, including all the labels of
|
/// 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:
|
/// any associated values, as well as the data held in the associated values:
|
||||||
///
|
///
|
||||||
|
/// ```swift
|
||||||
/// Action.screenA(
|
/// Action.screenA(
|
||||||
/// ScreenA.row(
|
/// ScreenA.row(
|
||||||
/// index: 1,
|
/// index: 1,
|
||||||
@@ -19,6 +23,8 @@ public enum ActionFormat {
|
|||||||
/// )
|
/// )
|
||||||
/// )
|
/// )
|
||||||
/// )
|
/// )
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
case prettyPrint
|
case prettyPrint
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -31,7 +37,7 @@ extension Reducer {
|
|||||||
/// - prefix: A string with which to prefix all debug messages.
|
/// - prefix: A string with which to prefix all debug messages.
|
||||||
/// - toDebugEnvironment: A function that transforms an environment into a debug environment by
|
/// - 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
|
/// 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.
|
/// function and a background queue.
|
||||||
/// - Returns: A reducer that prints debug messages for all received actions.
|
/// - Returns: A reducer that prints debug messages for all received actions.
|
||||||
public func debug(
|
public func debug(
|
||||||
@@ -58,7 +64,7 @@ extension Reducer {
|
|||||||
/// - prefix: A string with which to prefix all debug messages.
|
/// - prefix: A string with which to prefix all debug messages.
|
||||||
/// - toDebugEnvironment: A function that transforms an environment into a debug environment by
|
/// - 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
|
/// 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.
|
/// function and a background queue.
|
||||||
/// - Returns: A reducer that prints debug messages for all received actions.
|
/// - Returns: A reducer that prints debug messages for all received actions.
|
||||||
public func debugActions(
|
public func debugActions(
|
||||||
@@ -87,7 +93,7 @@ extension Reducer {
|
|||||||
/// - toLocalAction: A case path that filters actions that are printed.
|
/// - toLocalAction: A case path that filters actions that are printed.
|
||||||
/// - toDebugEnvironment: A function that transforms an environment into a debug environment by
|
/// - 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
|
/// 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.
|
/// function and a background queue.
|
||||||
/// - Returns: A reducer that prints debug messages for all received actions.
|
/// - Returns: A reducer that prints debug messages for all received actions.
|
||||||
public func debug<LocalState, LocalAction>(
|
public func debug<LocalState, LocalAction>(
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
import Combine
|
import Combine
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
/// The `Effect` type encapsulates a unit of work that can be run in the outside world, and can feed
|
/// 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,
|
/// 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.
|
/// 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
|
/// 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
|
/// 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 same thread, **and** if the store is being used to drive UI then it
|
||||||
/// must receive values on the main thread.
|
/// must receive values on the main thread.
|
||||||
///
|
///
|
||||||
@@ -20,17 +20,21 @@ public struct Effect<Output, Failure: Error>: Publisher {
|
|||||||
///
|
///
|
||||||
/// This initializer is useful for turning any publisher into an effect. For example:
|
/// This initializer is useful for turning any publisher into an effect. For example:
|
||||||
///
|
///
|
||||||
|
/// ```swift
|
||||||
/// Effect(
|
/// Effect(
|
||||||
/// NotificationCenter.default
|
/// NotificationCenter.default
|
||||||
/// .publisher(for: UIApplication.userDidTakeScreenshotNotification)
|
/// .publisher(for: UIApplication.userDidTakeScreenshotNotification)
|
||||||
/// )
|
/// )
|
||||||
|
/// ```
|
||||||
///
|
///
|
||||||
/// Alternatively, you can use the `.eraseToEffect()` method that is defined on the `Publisher`
|
/// Alternatively, you can use the `.eraseToEffect()` method that is defined on the `Publisher`
|
||||||
/// protocol:
|
/// protocol:
|
||||||
///
|
///
|
||||||
|
/// ```swift
|
||||||
/// NotificationCenter.default
|
/// NotificationCenter.default
|
||||||
/// .publisher(for: UIApplication.userDidTakeScreenshotNotification)
|
/// .publisher(for: UIApplication.userDidTakeScreenshotNotification)
|
||||||
/// .eraseToEffect()
|
/// .eraseToEffect()
|
||||||
|
/// ```
|
||||||
///
|
///
|
||||||
/// - Parameter publisher: A publisher.
|
/// - Parameter publisher: A publisher.
|
||||||
public init<P: Publisher>(_ publisher: P) where P.Output == Output, P.Failure == Failure {
|
public init<P: Publisher>(_ publisher: P) where P.Output == Output, P.Failure == Failure {
|
||||||
@@ -74,28 +78,32 @@ public struct Effect<Output, Failure: Error>: Publisher {
|
|||||||
/// Creates an effect that can supply a single value asynchronously in the future.
|
/// 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
|
/// 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:
|
/// For example, to create an effect that delivers an integer after waiting a second:
|
||||||
///
|
///
|
||||||
|
/// ```swift
|
||||||
/// Effect<Int, Never>.future { callback in
|
/// Effect<Int, Never>.future { callback in
|
||||||
/// DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
|
/// DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
|
||||||
/// callback(.success(42))
|
/// callback(.success(42))
|
||||||
/// }
|
/// }
|
||||||
/// }
|
/// }
|
||||||
|
/// ```
|
||||||
///
|
///
|
||||||
/// Note that you can only deliver a single value to the `callback`. If you send more they will be
|
/// Note that you can only deliver a single value to the `callback`. If you send more they will be
|
||||||
/// discarded:
|
/// discarded:
|
||||||
///
|
///
|
||||||
|
/// ```swift
|
||||||
/// Effect<Int, Never>.future { callback in
|
/// Effect<Int, Never>.future { callback in
|
||||||
/// DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
|
/// DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
|
||||||
/// callback(.success(42))
|
/// callback(.success(42))
|
||||||
/// callback(.success(1729)) // Will not be emitted by the effect
|
/// 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`
|
/// If you need to deliver more than one value to the effect, you should use the ``Effect``
|
||||||
/// initializer that accepts a `Subscriber` value.
|
/// initializer that accepts a ``Subscriber`` value.
|
||||||
///
|
///
|
||||||
/// - Parameter attemptToFulfill: A closure that takes a `callback` as an argument which can be
|
/// - Parameter attemptToFulfill: A closure that takes a `callback` as an argument which can be
|
||||||
/// used to feed it `Result<Output, Failure>` values.
|
/// used to feed it `Result<Output, Failure>` values.
|
||||||
@@ -115,6 +123,7 @@ public struct Effect<Output, Failure: Error>: Publisher {
|
|||||||
///
|
///
|
||||||
/// For example, to load a user from some JSON on the disk, one can wrap that work in an effect:
|
/// For example, to load a user from some JSON on the disk, one can wrap that work in an effect:
|
||||||
///
|
///
|
||||||
|
/// ```swift
|
||||||
/// Effect<User, Error>.result {
|
/// Effect<User, Error>.result {
|
||||||
/// let fileUrl = URL(
|
/// let fileUrl = URL(
|
||||||
/// fileURLWithPath: NSSearchPathForDirectoriesInDomains(
|
/// fileURLWithPath: NSSearchPathForDirectoriesInDomains(
|
||||||
@@ -130,6 +139,7 @@ public struct Effect<Output, Failure: Error>: Publisher {
|
|||||||
///
|
///
|
||||||
/// return result
|
/// return result
|
||||||
/// }
|
/// }
|
||||||
|
/// ```
|
||||||
///
|
///
|
||||||
/// - Parameter attemptToFulfill: A closure encapsulating some work to execute in the real world.
|
/// - Parameter attemptToFulfill: A closure encapsulating some work to execute in the real world.
|
||||||
/// - Returns: An effect.
|
/// - Returns: An effect.
|
||||||
@@ -141,13 +151,14 @@ public struct Effect<Output, Failure: Error>: Publisher {
|
|||||||
/// a completion.
|
/// a completion.
|
||||||
///
|
///
|
||||||
/// This initializer is useful for bridging callback APIs, delegate APIs, and manager APIs to the
|
/// 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.
|
/// 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
|
/// 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
|
/// 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:
|
/// can request authorization, and once a status is received it can send that back to the effect:
|
||||||
///
|
///
|
||||||
|
/// ```swift
|
||||||
/// Effect.run { subscriber in
|
/// Effect.run { subscriber in
|
||||||
/// subscriber.send(MPMediaLibrary.authorizationStatus())
|
/// subscriber.send(MPMediaLibrary.authorizationStatus())
|
||||||
///
|
///
|
||||||
@@ -165,9 +176,10 @@ public struct Effect<Output, Failure: Error>: Publisher {
|
|||||||
/// // have any.
|
/// // have any.
|
||||||
/// }
|
/// }
|
||||||
/// }
|
/// }
|
||||||
|
/// ```
|
||||||
///
|
///
|
||||||
/// - Parameter work: A closure that accepts a `Subscriber` value and returns a cancellable. When
|
/// - 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
|
/// the ``Effect`` is completed, the cancellable will be used to clean up any resources created
|
||||||
/// when the effect was started.
|
/// when the effect was started.
|
||||||
public static func run(
|
public static func run(
|
||||||
_ work: @escaping (Effect.Subscriber) -> Cancellable
|
_ work: @escaping (Effect.Subscriber) -> Cancellable
|
||||||
@@ -180,7 +192,7 @@ public struct Effect<Output, Failure: Error>: Publisher {
|
|||||||
///
|
///
|
||||||
/// - Warning: Combine's `Publishers.Concatenate` operator, which this function uses, can leak
|
/// - 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
|
/// 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: <https://gist.github.com/mbrandonw/611c8352e1bd1c22461bd505e320ab58>
|
/// Feedback filed: <https://gist.github.com/mbrandonw/611c8352e1bd1c22461bd505e320ab58>
|
||||||
///
|
///
|
||||||
@@ -195,7 +207,7 @@ public struct Effect<Output, Failure: Error>: Publisher {
|
|||||||
///
|
///
|
||||||
/// - Warning: Combine's `Publishers.Concatenate` operator, which this function uses, can leak
|
/// - 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
|
/// 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: <https://gist.github.com/mbrandonw/611c8352e1bd1c22461bd505e320ab58>
|
/// Feedback filed: <https://gist.github.com/mbrandonw/611c8352e1bd1c22461bd505e320ab58>
|
||||||
///
|
///
|
||||||
@@ -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:
|
/// For example, to load a user from some JSON on the disk, one can wrap that work in an effect:
|
||||||
///
|
///
|
||||||
|
/// ```swift
|
||||||
/// Effect<User, Error>.catching {
|
/// Effect<User, Error>.catching {
|
||||||
/// let fileUrl = URL(
|
/// let fileUrl = URL(
|
||||||
/// fileURLWithPath: NSSearchPathForDirectoriesInDomains(
|
/// fileURLWithPath: NSSearchPathForDirectoriesInDomains(
|
||||||
@@ -280,6 +293,7 @@ extension Effect where Failure == Swift.Error {
|
|||||||
/// let data = try Data(contentsOf: fileUrl)
|
/// let data = try Data(contentsOf: fileUrl)
|
||||||
/// return try JSONDecoder().decode(User.self, from: $0)
|
/// return try JSONDecoder().decode(User.self, from: $0)
|
||||||
/// }
|
/// }
|
||||||
|
/// ```
|
||||||
///
|
///
|
||||||
/// - Parameter work: A closure encapsulating some work to execute in the real world.
|
/// - Parameter work: A closure encapsulating some work to execute in the real world.
|
||||||
/// - Returns: An effect.
|
/// - Returns: An effect.
|
||||||
@@ -289,31 +303,35 @@ extension Effect where Failure == Swift.Error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
extension Publisher {
|
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
|
/// 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:
|
/// you need to convert that publisher to an effect so that you can return it from the reducer:
|
||||||
///
|
///
|
||||||
|
/// ```swift
|
||||||
/// case .buttonTapped:
|
/// case .buttonTapped:
|
||||||
/// return fetchUser(id: 1)
|
/// return fetchUser(id: 1)
|
||||||
/// .filter(\.isAdmin)
|
/// .filter(\.isAdmin)
|
||||||
/// .eraseToEffect()
|
/// .eraseToEffect()
|
||||||
|
/// ```
|
||||||
///
|
///
|
||||||
/// - Returns: An effect that wraps `self`.
|
/// - Returns: An effect that wraps `self`.
|
||||||
public func eraseToEffect() -> Effect<Output, Failure> {
|
public func eraseToEffect() -> Effect<Output, Failure> {
|
||||||
Effect(self)
|
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.
|
/// result.
|
||||||
///
|
///
|
||||||
/// This can be useful when you are working with a failing API but want to deliver its data to an
|
/// 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.
|
/// action that handles both success and failure.
|
||||||
///
|
///
|
||||||
|
/// ```swift
|
||||||
/// case .buttonTapped:
|
/// case .buttonTapped:
|
||||||
/// return fetchUser(id: 1)
|
/// return fetchUser(id: 1)
|
||||||
/// .catchToEffect()
|
/// .catchToEffect()
|
||||||
/// .map(ProfileAction.userResponse)
|
/// .map(ProfileAction.userResponse)
|
||||||
|
/// ```
|
||||||
///
|
///
|
||||||
/// - Returns: An effect that wraps `self`.
|
/// - Returns: An effect that wraps `self`.
|
||||||
public func catchToEffect() -> Effect<Result<Output, Failure>, Never> {
|
public func catchToEffect() -> Effect<Result<Output, Failure>, Never> {
|
||||||
@@ -322,16 +340,18 @@ extension Publisher {
|
|||||||
.eraseToEffect()
|
.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.
|
/// and any failure.
|
||||||
///
|
///
|
||||||
/// This is useful for times you want to fire off an effect but don't want to feed any data back
|
/// 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.
|
/// into the system. It can automatically promote an effect to your reducer's domain.
|
||||||
///
|
///
|
||||||
|
/// ```swift
|
||||||
/// case .buttonTapped:
|
/// case .buttonTapped:
|
||||||
/// return analyticsClient.track("Button Tapped")
|
/// return analyticsClient.track("Button Tapped")
|
||||||
/// .fireAndForget()
|
/// .fireAndForget()
|
||||||
///
|
/// ```
|
||||||
|
///
|
||||||
/// - Parameters:
|
/// - Parameters:
|
||||||
/// - outputType: An output type.
|
/// - outputType: An output type.
|
||||||
/// - failureType: A failure type.
|
/// - failureType: A failure type.
|
||||||
|
|||||||
@@ -5,10 +5,11 @@ extension Effect {
|
|||||||
/// Turns an effect into one that is capable of being canceled.
|
/// 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
|
/// 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
|
/// 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:
|
/// against typos by defining a new type that conforms to `Hashable`, such as an empty struct:
|
||||||
///
|
///
|
||||||
|
/// ```swift
|
||||||
/// struct LoadUserId: Hashable {}
|
/// struct LoadUserId: Hashable {}
|
||||||
///
|
///
|
||||||
/// case .reloadButtonTapped:
|
/// case .reloadButtonTapped:
|
||||||
@@ -20,6 +21,7 @@ extension Effect {
|
|||||||
/// case .cancelButtonTapped:
|
/// case .cancelButtonTapped:
|
||||||
/// // Cancel any in-flight requests to load the user
|
/// // Cancel any in-flight requests to load the user
|
||||||
/// return .cancel(id: LoadUserId())
|
/// return .cancel(id: LoadUserId())
|
||||||
|
/// ```
|
||||||
///
|
///
|
||||||
/// - Parameters:
|
/// - Parameters:
|
||||||
/// - id: The effect's identifier.
|
/// - id: The effect's identifier.
|
||||||
|
|||||||
@@ -9,13 +9,15 @@ extension Effect {
|
|||||||
/// protection against typos by defining a new type that conforms to `Hashable`, such as an empty
|
/// protection against typos by defining a new type that conforms to `Hashable`, such as an empty
|
||||||
/// struct:
|
/// struct:
|
||||||
///
|
///
|
||||||
|
/// ```swift
|
||||||
/// case let .textChanged(text):
|
/// case let .textChanged(text):
|
||||||
/// struct SearchId: Hashable {}
|
/// struct SearchId: Hashable {}
|
||||||
///
|
///
|
||||||
/// return environment.search(text)
|
/// return environment.search(text)
|
||||||
/// .map(Action.searchResponse)
|
/// .map(Action.searchResponse)
|
||||||
/// .debounce(id: SearchId(), for: 0.5, scheduler: environment.mainQueue)
|
/// .debounce(id: SearchId(), for: 0.5, scheduler: environment.mainQueue)
|
||||||
///
|
/// ```
|
||||||
|
///
|
||||||
/// - Parameters:
|
/// - Parameters:
|
||||||
/// - id: The effect's identifier.
|
/// - id: The effect's identifier.
|
||||||
/// - dueTime: The duration you want to debounce for.
|
/// - dueTime: The duration you want to debounce for.
|
||||||
|
|||||||
@@ -15,13 +15,14 @@ extension Effect where Failure == Never {
|
|||||||
/// we can see how effects emit. However, because `Timer.publish` takes a concrete `RunLoop` as
|
/// 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`.
|
/// 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
|
/// 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.
|
/// `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
|
/// 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 {
|
/// struct AppState {
|
||||||
/// var count = 0
|
/// var count = 0
|
||||||
/// }
|
/// }
|
||||||
@@ -49,34 +50,37 @@ extension Effect where Failure == Never {
|
|||||||
/// state.count += 1
|
/// state.count += 1
|
||||||
/// return .none
|
/// return .none
|
||||||
/// }
|
/// }
|
||||||
|
/// ```
|
||||||
///
|
///
|
||||||
/// Then to test the timer in this feature you can use a test scheduler to advance time:
|
/// Then to test the timer in this feature you can use a test scheduler to advance time:
|
||||||
///
|
///
|
||||||
/// func testTimer() {
|
/// ```swift
|
||||||
/// let scheduler = DispatchQueue.test
|
/// func testTimer() {
|
||||||
|
/// let scheduler = DispatchQueue.test
|
||||||
///
|
///
|
||||||
/// let store = TestStore(
|
/// let store = TestStore(
|
||||||
/// initialState: .init(),
|
/// initialState: .init(),
|
||||||
/// reducer: appReducer,
|
/// reducer: appReducer,
|
||||||
/// envirnoment: .init(
|
/// envirnoment: .init(
|
||||||
/// mainQueue: scheduler.eraseToAnyScheduler()
|
/// mainQueue: scheduler.eraseToAnyScheduler()
|
||||||
|
/// )
|
||||||
/// )
|
/// )
|
||||||
/// )
|
|
||||||
///
|
///
|
||||||
/// store.send(.startButtonTapped)
|
/// store.send(.startButtonTapped)
|
||||||
///
|
///
|
||||||
/// scheduler.advance(by: .seconds(1))
|
/// scheduler.advance(by: .seconds(1))
|
||||||
/// store.receive(.timerTicked) { $0.count = 1 }
|
/// store.receive(.timerTicked) { $0.count = 1 }
|
||||||
///
|
///
|
||||||
/// scheduler.advance(by: .seconds(5))
|
/// scheduler.advance(by: .seconds(5))
|
||||||
/// store.receive(.timerTicked) { $0.count = 2 }
|
/// store.receive(.timerTicked) { $0.count = 2 }
|
||||||
/// store.receive(.timerTicked) { $0.count = 3 }
|
/// store.receive(.timerTicked) { $0.count = 3 }
|
||||||
/// store.receive(.timerTicked) { $0.count = 4 }
|
/// store.receive(.timerTicked) { $0.count = 4 }
|
||||||
/// store.receive(.timerTicked) { $0.count = 5 }
|
/// store.receive(.timerTicked) { $0.count = 5 }
|
||||||
/// store.receive(.timerTicked) { $0.count = 6 }
|
/// 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
|
/// - 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
|
/// Architecture, and returned from a reducer. If you want a testable alternative to
|
||||||
|
|||||||
@@ -2,19 +2,19 @@ import CasePaths
|
|||||||
import Combine
|
import Combine
|
||||||
|
|
||||||
/// A reducer describes how to evolve the current state of an application to the next state, given
|
/// 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:
|
/// Reducers have 3 generics:
|
||||||
///
|
///
|
||||||
/// * `State`: A type that holds the current state of the application.
|
/// * `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
|
/// * `Action`: A type that holds all possible actions that cause the state of the application to
|
||||||
/// change.
|
/// 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.
|
/// 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
|
/// - 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
|
/// 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
|
/// 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
|
/// 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.
|
/// effect output its values on the thread of your choice.
|
||||||
public struct Reducer<State, Action, Environment> {
|
public struct Reducer<State, Action, Environment> {
|
||||||
@@ -25,10 +25,10 @@ public struct Reducer<State, Action, Environment> {
|
|||||||
/// The reducer takes three arguments: state, action and environment. The state is `inout` so that
|
/// 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
|
/// 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
|
/// 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:
|
/// For example:
|
||||||
///
|
/// ```swift
|
||||||
/// struct MyState { var count = 0, text = "" }
|
/// struct MyState { var count = 0, text = "" }
|
||||||
/// enum MyAction { case buttonTapped, textChanged(String) }
|
/// enum MyAction { case buttonTapped, textChanged(String) }
|
||||||
/// struct MyEnvironment { var analyticsClient: AnalyticsClient }
|
/// struct MyEnvironment { var analyticsClient: AnalyticsClient }
|
||||||
@@ -44,6 +44,7 @@ public struct Reducer<State, Action, Environment> {
|
|||||||
/// return .none
|
/// return .none
|
||||||
/// }
|
/// }
|
||||||
/// }
|
/// }
|
||||||
|
/// ```
|
||||||
///
|
///
|
||||||
/// - Parameter reducer: A function signature that takes state, action and
|
/// - Parameter reducer: A function signature that takes state, action and
|
||||||
/// environment.
|
/// environment.
|
||||||
@@ -67,21 +68,22 @@ public struct Reducer<State, Action, Environment> {
|
|||||||
/// its state, it can make a difference if `reducerA` chooses to modify `reducerB`'s state
|
/// its state, it can make a difference if `reducerA` chooses to modify `reducerB`'s state
|
||||||
/// _before_ or _after_ `reducerB` runs.
|
/// _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
|
/// 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.
|
/// 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
|
/// 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 `forEach` reducer runs, the
|
/// collection by moving, removing, or modifying an element before the ``Reducer/forEach(state:action:environment:breakpointOnNil:_:_:)-3ic87`` reducer runs, the
|
||||||
/// `forEach` reducer may perform its action against the wrong element, an element that no longer
|
/// ``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.
|
/// exists, or an element in an unexpected state.
|
||||||
///
|
///
|
||||||
/// Running a parent reducer before a child reducer can be considered an application logic
|
/// 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
|
/// error, and can produce assertion failures. So you should almost always combine reducers in
|
||||||
/// order from child to parent domain.
|
/// 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<ParentState, ParentAction, ParentEnvironment>.combine(
|
/// let parentReducer = Reducer<ParentState, ParentAction, ParentEnvironment>.combine(
|
||||||
/// // Combined before parent so that it can react to `.dismiss` while state is non-`nil`.
|
/// // Combined before parent so that it can react to `.dismiss` while state is non-`nil`.
|
||||||
/// childReducer.optional().pullback(
|
/// childReducer.optional().pullback(
|
||||||
@@ -99,6 +101,7 @@ public struct Reducer<State, Action, Environment> {
|
|||||||
/// }
|
/// }
|
||||||
/// },
|
/// },
|
||||||
/// )
|
/// )
|
||||||
|
/// ```
|
||||||
///
|
///
|
||||||
/// - Parameter reducers: A list of reducers.
|
/// - Parameter reducers: A list of reducers.
|
||||||
/// - Returns: A single reducer.
|
/// - Returns: A single reducer.
|
||||||
@@ -117,21 +120,22 @@ public struct Reducer<State, Action, Environment> {
|
|||||||
/// its state, it can make a difference if `reducerA` chooses to modify `reducerB`'s state
|
/// its state, it can make a difference if `reducerA` chooses to modify `reducerB`'s state
|
||||||
/// _before_ or _after_ `reducerB` runs.
|
/// _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
|
/// 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.
|
/// 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
|
/// 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 `forEach` reducer runs, the
|
/// collection by moving, removing, or modifying an element before the ``Reducer/forEach(state:action:environment:breakpointOnNil:_:_:)-3ic87`` reducer runs, the
|
||||||
/// `forEach` reducer may perform its action against the wrong element, an element that no longer
|
/// ``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.
|
/// exists, or an element in an unexpected state.
|
||||||
///
|
///
|
||||||
/// Running a parent reducer before a child reducer can be considered an application logic
|
/// 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
|
/// error, and can produce assertion failures. So you should almost always combine reducers in
|
||||||
/// order from child to parent domain.
|
/// 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<ParentState, ParentAction, ParentEnvironment>.combine(
|
/// let parentReducer = Reducer<ParentState, ParentAction, ParentEnvironment>.combine(
|
||||||
/// // Combined before parent so that it can react to `.dismiss` while state is non-`nil`.
|
/// // Combined before parent so that it can react to `.dismiss` while state is non-`nil`.
|
||||||
/// childReducer.optional().pullback(
|
/// childReducer.optional().pullback(
|
||||||
@@ -149,6 +153,7 @@ public struct Reducer<State, Action, Environment> {
|
|||||||
/// }
|
/// }
|
||||||
/// },
|
/// },
|
||||||
/// )
|
/// )
|
||||||
|
/// ```
|
||||||
///
|
///
|
||||||
/// - Parameter reducers: An array of reducers.
|
/// - Parameter reducers: An array of reducers.
|
||||||
/// - Returns: A single reducer.
|
/// - Returns: A single reducer.
|
||||||
@@ -169,21 +174,22 @@ public struct Reducer<State, Action, Environment> {
|
|||||||
/// its state, it can make a difference if `reducerA` chooses to modify `reducerB`'s state
|
/// its state, it can make a difference if `reducerA` chooses to modify `reducerB`'s state
|
||||||
/// _before_ or _after_ `reducerB` runs.
|
/// _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
|
/// 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.
|
/// 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
|
/// 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 `forEach` reducer runs, the
|
/// collection by moving, removing, or modifying an element before the ``Reducer/forEach(state:action:environment:breakpointOnNil:_:_:)-3ic87`` reducer runs, the
|
||||||
/// `forEach` reducer may perform its action against the wrong element, an element that no longer
|
/// ``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.
|
/// exists, or an element in an unexpected state.
|
||||||
///
|
///
|
||||||
/// Running a parent reducer before a child reducer can be considered an application logic
|
/// 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
|
/// error, and can produce assertion failures. So you should almost always combine reducers in
|
||||||
/// order from child to parent domain.
|
/// 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<ParentState, ParentAction, ParentEnvironment> =
|
/// let parentReducer: Reducer<ParentState, ParentAction, ParentEnvironment> =
|
||||||
/// // Run before parent so that it can react to `.dismiss` while state is non-`nil`.
|
/// // Run before parent so that it can react to `.dismiss` while state is non-`nil`.
|
||||||
/// childReducer
|
/// childReducer
|
||||||
@@ -204,6 +210,7 @@ public struct Reducer<State, Action, Environment> {
|
|||||||
/// }
|
/// }
|
||||||
/// }
|
/// }
|
||||||
/// )
|
/// )
|
||||||
|
/// ```
|
||||||
///
|
///
|
||||||
/// - Parameter other: Another reducer.
|
/// - Parameter other: Another reducer.
|
||||||
/// - Returns: A single reducer.
|
/// - Returns: A single reducer.
|
||||||
@@ -220,9 +227,10 @@ public struct Reducer<State, Action, Environment> {
|
|||||||
/// * A function that can transform the global environment into a local environment.
|
/// * 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
|
/// 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.
|
/// 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:
|
/// // Global domain that holds a local domain:
|
||||||
/// struct AppState { var settings: SettingsState, /* rest of state */ }
|
/// struct AppState { var settings: SettingsState, /* rest of state */ }
|
||||||
/// enum AppAction { case settings(SettingsAction), /* other actions */ }
|
/// enum AppAction { case settings(SettingsAction), /* other actions */ }
|
||||||
@@ -241,6 +249,7 @@ public struct Reducer<State, Action, Environment> {
|
|||||||
///
|
///
|
||||||
/// /* other reducers */
|
/// /* other reducers */
|
||||||
/// )
|
/// )
|
||||||
|
/// ```
|
||||||
///
|
///
|
||||||
/// - Parameters:
|
/// - Parameters:
|
||||||
/// - toLocalState: A key path that can get/set `State` inside `GlobalState`.
|
/// - toLocalState: A key path that can get/set `State` inside `GlobalState`.
|
||||||
@@ -475,10 +484,11 @@ public struct Reducer<State, Action, Environment> {
|
|||||||
/// Transforms a reducer that works on non-optional state into one that works on optional state by
|
/// 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.
|
/// 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
|
/// into a reducer that can be combined with a reducer on a parent domain that contains some
|
||||||
/// optional child domain:
|
/// optional child domain:
|
||||||
///
|
///
|
||||||
|
/// ```swift
|
||||||
/// // Global domain that holds an optional local domain:
|
/// // Global domain that holds an optional local domain:
|
||||||
/// struct AppState { var modal: ModalState? }
|
/// struct AppState { var modal: ModalState? }
|
||||||
/// enum AppAction { case modal(ModalAction) }
|
/// enum AppAction { case modal(ModalAction) }
|
||||||
@@ -498,6 +508,7 @@ public struct Reducer<State, Action, Environment> {
|
|||||||
/// ...
|
/// ...
|
||||||
/// }
|
/// }
|
||||||
/// )
|
/// )
|
||||||
|
/// ```
|
||||||
///
|
///
|
||||||
/// Take care when combining optional reducers into parent domains. An optional reducer cannot
|
/// 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
|
/// 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<State, Action, Environment> {
|
|||||||
/// * A parent reducer sets child state to `nil` when processing a child action and runs
|
/// * A parent reducer sets child state to `nil` when processing a child action and runs
|
||||||
/// _before_ the child reducer:
|
/// _before_ the child reducer:
|
||||||
///
|
///
|
||||||
|
/// ```swift
|
||||||
/// let parentReducer = Reducer<ParentState, ParentAction, ParentEnvironment>.combine(
|
/// let parentReducer = Reducer<ParentState, ParentAction, ParentEnvironment>.combine(
|
||||||
/// // When combining reducers, the parent reducer runs first
|
/// // When combining reducers, the parent reducer runs first
|
||||||
/// Reducer { state, action, environment in
|
/// Reducer { state, action, environment in
|
||||||
@@ -529,10 +541,12 @@ public struct Reducer<State, Action, Environment> {
|
|||||||
/// // This action is never received here because child state is `nil` in the parent
|
/// // 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
|
/// 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:
|
/// its state, combine it _before_ the parent:
|
||||||
///
|
///
|
||||||
|
/// ```swift
|
||||||
/// let parentReducer = Reducer<ParentState, ParentAction, ParentEnvironment>.combine(
|
/// let parentReducer = Reducer<ParentState, ParentAction, ParentEnvironment>.combine(
|
||||||
/// // The child runs first
|
/// // The child runs first
|
||||||
/// childReducer.optional().pullback(...),
|
/// childReducer.optional().pullback(...),
|
||||||
@@ -541,9 +555,11 @@ public struct Reducer<State, Action, Environment> {
|
|||||||
/// ...
|
/// ...
|
||||||
/// }
|
/// }
|
||||||
/// )
|
/// )
|
||||||
|
/// ```
|
||||||
///
|
///
|
||||||
/// * A child effect feeds a child action back into the store when child state is `nil`:
|
/// * A child effect feeds a child action back into the store when child state is `nil`:
|
||||||
///
|
///
|
||||||
|
/// ```swift
|
||||||
/// let childReducer = Reducer<
|
/// let childReducer = Reducer<
|
||||||
/// ChildState, ChildAction, ChildEnvironment
|
/// ChildState, ChildAction, ChildEnvironment
|
||||||
/// > { state, action environment in
|
/// > { state, action environment in
|
||||||
@@ -559,11 +575,13 @@ public struct Reducer<State, Action, Environment> {
|
|||||||
/// ...
|
/// ...
|
||||||
/// }
|
/// }
|
||||||
/// }
|
/// }
|
||||||
|
/// ```
|
||||||
///
|
///
|
||||||
/// It is perfectly reasonable to ignore the result of an effect when child state is `nil`,
|
/// 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
|
/// 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:
|
/// effects _should_ be explicitly canceled when tearing down a child domain:
|
||||||
///
|
///
|
||||||
|
/// ```swift
|
||||||
/// let childReducer = Reducer<
|
/// let childReducer = Reducer<
|
||||||
/// ChildState, ChildAction, ChildEnvironment
|
/// ChildState, ChildAction, ChildEnvironment
|
||||||
/// > { state, action environment in
|
/// > { state, action environment in
|
||||||
@@ -583,18 +601,22 @@ public struct Reducer<State, Action, Environment> {
|
|||||||
/// ...
|
/// ...
|
||||||
/// }
|
/// }
|
||||||
/// }
|
/// }
|
||||||
|
/// ```
|
||||||
///
|
///
|
||||||
/// * A view store sends a child action when child state is `nil`:
|
/// * A view store sends a child action when child state is `nil`:
|
||||||
///
|
///
|
||||||
|
/// ```swift
|
||||||
/// WithViewStore(self.parentStore) { parentViewStore in
|
/// WithViewStore(self.parentStore) { parentViewStore in
|
||||||
/// // If child state is `nil`, it cannot process this action.
|
/// // If child state is `nil`, it cannot process this action.
|
||||||
/// Button("Child Action") { parentViewStore.send(.child(.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`.
|
/// child actions when the child domain is non-`nil`.
|
||||||
///
|
///
|
||||||
|
/// ```swift
|
||||||
/// IfLetStore(
|
/// IfLetStore(
|
||||||
/// self.parentStore.scope(state: { $0.child }, action: { .child($0) }
|
/// self.parentStore.scope(state: { $0.child }, action: { .child($0) }
|
||||||
/// ) { childStore in
|
/// ) { childStore in
|
||||||
@@ -605,10 +627,11 @@ public struct Reducer<State, Action, Environment> {
|
|||||||
/// }
|
/// }
|
||||||
/// ...
|
/// ...
|
||||||
/// }
|
/// }
|
||||||
|
/// ```
|
||||||
///
|
///
|
||||||
/// - 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.
|
/// 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.
|
/// state.
|
||||||
///
|
///
|
||||||
/// - Parameter breakpointOnNil: Raises `SIGTRAP` signal when an action is sent to the reducer
|
/// - Parameter breakpointOnNil: Raises `SIGTRAP` signal when an action is sent to the reducer
|
||||||
@@ -656,9 +679,9 @@ public struct Reducer<State, Action, Environment> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 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.
|
/// on a collection of elements.
|
||||||
///
|
/// ```swift
|
||||||
/// // Global domain that holds a collection of local domains:
|
/// // Global domain that holds a collection of local domains:
|
||||||
/// struct AppState { var todos: [Todo] }
|
/// struct AppState { var todos: [Todo] }
|
||||||
/// enum AppAction { case todo(index: Int, action: TodoAction) }
|
/// enum AppAction { case todo(index: Int, action: TodoAction) }
|
||||||
@@ -678,9 +701,10 @@ public struct Reducer<State, Action, Environment> {
|
|||||||
/// ...
|
/// ...
|
||||||
/// }
|
/// }
|
||||||
/// )
|
/// )
|
||||||
|
/// ```
|
||||||
///
|
///
|
||||||
/// Take care when combining `forEach` reducers into parent domains, as order matters. Always
|
/// Take care when combining ``forEach(state:action:environment:breakpointOnNil:_:_:)-3ic87`` reducers into parent domains, as order matters. Always
|
||||||
/// combine `forEach` reducers _before_ parent reducers that can modify the collection.
|
/// combine ``forEach(state:action:environment:breakpointOnNil:_:_:)-3ic87`` reducers _before_ parent reducers that can modify the collection.
|
||||||
///
|
///
|
||||||
/// - Parameters:
|
/// - Parameters:
|
||||||
/// - toLocalState: A key path that can get/set an array of `State` elements inside.
|
/// - toLocalState: A key path that can get/set an array of `State` elements inside.
|
||||||
@@ -745,9 +769,10 @@ public struct Reducer<State, Action, Environment> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 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.
|
/// on an identified array of elements.
|
||||||
///
|
///
|
||||||
|
/// ```swift
|
||||||
/// // Global domain that holds a collection of local domains:
|
/// // Global domain that holds a collection of local domains:
|
||||||
/// struct AppState { var todos: IdentifiedArrayOf<Todo> }
|
/// struct AppState { var todos: IdentifiedArrayOf<Todo> }
|
||||||
/// enum AppAction { case todo(id: Todo.ID, action: TodoAction) }
|
/// enum AppAction { case todo(id: Todo.ID, action: TodoAction) }
|
||||||
@@ -767,9 +792,10 @@ public struct Reducer<State, Action, Environment> {
|
|||||||
/// ...
|
/// ...
|
||||||
/// }
|
/// }
|
||||||
/// )
|
/// )
|
||||||
|
/// ```
|
||||||
///
|
///
|
||||||
/// Take care when combining `forEach` reducers into parent domains, as order matters. Always
|
/// Take care when combining ``forEach(state:action:environment:breakpointOnNil:_:_:)-90ox5`` reducers into parent domains, as order matters. Always
|
||||||
/// combine `forEach` reducers _before_ parent reducers that can modify the collection.
|
/// combine ``forEach(state:action:environment:breakpointOnNil:_:_:)-90ox5`` reducers _before_ parent reducers that can modify the collection.
|
||||||
///
|
///
|
||||||
/// - Parameters:
|
/// - Parameters:
|
||||||
/// - toLocalState: A key path that can get/set a collection of `State` elements inside
|
/// - toLocalState: A key path that can get/set a collection of `State` elements inside
|
||||||
@@ -836,11 +862,11 @@ public struct Reducer<State, Action, Environment> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 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.
|
/// on a dictionary of element values.
|
||||||
///
|
///
|
||||||
/// Take care when combining `forEach` reducers into parent domains, as order matters. Always
|
/// Take care when combining ``forEach(state:action:environment:breakpointOnNil:_:_:)-xv1z`` reducers into parent domains, as order matters. Always
|
||||||
/// combine `forEach` reducers _before_ parent reducers that can modify the dictionary.
|
/// combine ``forEach(state:action:environment:breakpointOnNil:_:_:)-xv1z`` reducers _before_ parent reducers that can modify the dictionary.
|
||||||
///
|
///
|
||||||
/// - Parameters:
|
/// - Parameters:
|
||||||
/// - toLocalState: A key path that can get/set a dictionary of `State` values inside
|
/// - toLocalState: A key path that can get/set a dictionary of `State` values inside
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import Foundation
|
|||||||
/// around to views that need to interact with the application.
|
/// 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
|
/// 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<State, Action> {
|
public final class Store<State, Action> {
|
||||||
var state: CurrentValueSubject<State, Never>
|
var state: CurrentValueSubject<State, Never>
|
||||||
var effectCancellables: [UUID: AnyCancellable] = [:]
|
var effectCancellables: [UUID: AnyCancellable] = [:]
|
||||||
@@ -34,7 +34,7 @@ public final class Store<State, Action> {
|
|||||||
///
|
///
|
||||||
/// This can be useful for deriving new stores to hand to child views in an application. For
|
/// This can be useful for deriving new stores to hand to child views in an application. For
|
||||||
/// example:
|
/// example:
|
||||||
///
|
/// ```swift
|
||||||
/// // Application state made from local states.
|
/// // Application state made from local states.
|
||||||
/// struct AppState { var login: LoginState, ... }
|
/// struct AppState { var login: LoginState, ... }
|
||||||
/// struct AppAction { case login(LoginAction), ... }
|
/// struct AppAction { case login(LoginAction), ... }
|
||||||
@@ -53,7 +53,7 @@ public final class Store<State, Action> {
|
|||||||
/// action: { AppAction.login($0) }
|
/// action: { AppAction.login($0) }
|
||||||
/// )
|
/// )
|
||||||
/// )
|
/// )
|
||||||
///
|
/// ```
|
||||||
/// Scoping in this fashion allows you to better modularize your application. In this case,
|
/// 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`.
|
/// `LoginView` could be extracted to a module that has no access to `AppState` or `AppAction`.
|
||||||
///
|
///
|
||||||
@@ -63,7 +63,7 @@ public final class Store<State, Action> {
|
|||||||
/// For example, the above login domain could model a two screen login flow: a login form followed
|
/// 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
|
/// by a two-factor authentication screen. The second screen's domain might be nested in the
|
||||||
/// first:
|
/// first:
|
||||||
///
|
/// ```swift
|
||||||
/// struct LoginState: Equatable {
|
/// struct LoginState: Equatable {
|
||||||
/// var email = ""
|
/// var email = ""
|
||||||
/// var password = ""
|
/// var password = ""
|
||||||
@@ -77,15 +77,15 @@ public final class Store<State, Action> {
|
|||||||
/// case passwordChanged(String)
|
/// case passwordChanged(String)
|
||||||
/// case twoFactorAuth(TwoFactorAuthAction)
|
/// case twoFactorAuth(TwoFactorAuthAction)
|
||||||
/// }
|
/// }
|
||||||
///
|
/// ```
|
||||||
/// The login view holds onto a store of this domain:
|
/// The login view holds onto a store of this domain:
|
||||||
///
|
/// ```swift
|
||||||
/// struct LoginView: View {
|
/// struct LoginView: View {
|
||||||
/// let store: Store<LoginState, LoginAction>
|
/// let store: Store<LoginState, LoginAction>
|
||||||
///
|
///
|
||||||
/// var body: some View { ... }
|
/// var body: some View { ... }
|
||||||
/// }
|
/// }
|
||||||
///
|
/// ```
|
||||||
/// If its body were to use a view store of the same domain, this would introduce a number of
|
/// If its body were to use a view store of the same domain, this would introduce a number of
|
||||||
/// problems:
|
/// problems:
|
||||||
///
|
///
|
||||||
@@ -104,7 +104,7 @@ public final class Store<State, Action> {
|
|||||||
///
|
///
|
||||||
/// To avoid these issues, one can introduce a view-specific domain that slices off the subset of
|
/// 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:
|
/// state and actions that a view cares about:
|
||||||
///
|
/// ```swift
|
||||||
/// extension LoginView {
|
/// extension LoginView {
|
||||||
/// struct State: Equatable {
|
/// struct State: Equatable {
|
||||||
/// var email: String
|
/// var email: String
|
||||||
@@ -117,10 +117,10 @@ public final class Store<State, Action> {
|
|||||||
/// case passwordChanged(String)
|
/// case passwordChanged(String)
|
||||||
/// }
|
/// }
|
||||||
/// }
|
/// }
|
||||||
///
|
/// ```
|
||||||
/// One can also introduce a couple helpers that transform feature state into view state and
|
/// One can also introduce a couple helpers that transform feature state into view state and
|
||||||
/// transform view actions into feature actions.
|
/// transform view actions into feature actions.
|
||||||
///
|
/// ```swift
|
||||||
/// extension LoginState {
|
/// extension LoginState {
|
||||||
/// var view: LoginView.State {
|
/// var view: LoginView.State {
|
||||||
/// .init(email: self.email, password: self.password)
|
/// .init(email: self.email, password: self.password)
|
||||||
@@ -139,10 +139,11 @@ public final class Store<State, Action> {
|
|||||||
/// }
|
/// }
|
||||||
/// }
|
/// }
|
||||||
/// }
|
/// }
|
||||||
|
/// ```
|
||||||
///
|
///
|
||||||
/// With these helpers defined, `LoginView` can now scope its store's feature domain into its view
|
/// With these helpers defined, `LoginView` can now scope its store's feature domain into its view
|
||||||
/// domain:
|
/// domain:
|
||||||
///
|
/// ```swift
|
||||||
/// var body: some View {
|
/// var body: some View {
|
||||||
/// WithViewStore(
|
/// WithViewStore(
|
||||||
/// self.store.scope(state: { $0.view }, action: { $0.feature })
|
/// self.store.scope(state: { $0.view }, action: { $0.feature })
|
||||||
@@ -150,7 +151,7 @@ public final class Store<State, Action> {
|
|||||||
/// ...
|
/// ...
|
||||||
/// }
|
/// }
|
||||||
/// }
|
/// }
|
||||||
///
|
/// ```
|
||||||
/// This view store is now incapable of reading any state but view state (and will not recompute
|
/// 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.
|
/// when non-view state changes), and is incapable of sending any actions but view actions.
|
||||||
///
|
///
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import SwiftUI
|
|||||||
///
|
///
|
||||||
/// To use this API, you model all the action sheet actions in your domain's action enum:
|
/// To use this API, you model all the action sheet actions in your domain's action enum:
|
||||||
///
|
///
|
||||||
|
/// ```swift
|
||||||
/// enum AppAction: Equatable {
|
/// enum AppAction: Equatable {
|
||||||
/// case cancelTapped
|
/// case cancelTapped
|
||||||
/// case deleteTapped
|
/// case deleteTapped
|
||||||
@@ -21,19 +22,23 @@ import SwiftUI
|
|||||||
///
|
///
|
||||||
/// // Your other actions
|
/// // Your other actions
|
||||||
/// }
|
/// }
|
||||||
|
/// ```
|
||||||
///
|
///
|
||||||
/// And you model the state for showing the action sheet in your domain's state, and it can start
|
/// And you model the state for showing the action sheet in your domain's state, and it can start
|
||||||
/// off in a `nil` state:
|
/// off in a `nil` state:
|
||||||
///
|
///
|
||||||
|
/// ```swift
|
||||||
/// struct AppState: Equatable {
|
/// struct AppState: Equatable {
|
||||||
/// var actionSheet: ActionSheetState<AppAction>?
|
/// var actionSheet: ActionSheetState<AppAction>?
|
||||||
///
|
///
|
||||||
/// // Your other state
|
/// // Your other state
|
||||||
/// }
|
/// }
|
||||||
|
/// ```
|
||||||
///
|
///
|
||||||
/// Then, in the reducer you can construct an `ActionSheetState` value to represent the action
|
/// Then, in the reducer you can construct an `ActionSheetState` value to represent the action
|
||||||
/// sheet you want to show to the user:
|
/// sheet you want to show to the user:
|
||||||
///
|
///
|
||||||
|
/// ```swift
|
||||||
/// let appReducer = Reducer<AppState, AppAction, AppEnvironment> { state, action, env in
|
/// let appReducer = Reducer<AppState, AppAction, AppEnvironment> { state, action, env in
|
||||||
/// switch action
|
/// switch action
|
||||||
/// case .cancelTapped:
|
/// case .cancelTapped:
|
||||||
@@ -60,15 +65,18 @@ import SwiftUI
|
|||||||
/// return .none
|
/// return .none
|
||||||
/// }
|
/// }
|
||||||
/// }
|
/// }
|
||||||
|
/// ```
|
||||||
///
|
///
|
||||||
/// And then, in your view you can use the `.actionSheet(_:send:dismiss:)` method on `View` in order
|
/// 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:
|
/// to present the action sheet in a way that works best with the Composable Architecture:
|
||||||
///
|
///
|
||||||
|
/// ```swift
|
||||||
/// Button("Info") { viewStore.send(.infoTapped) }
|
/// Button("Info") { viewStore.send(.infoTapped) }
|
||||||
/// .actionSheet(
|
/// .actionSheet(
|
||||||
/// self.store.scope(state: \.actionSheet),
|
/// self.store.scope(state: \.actionSheet),
|
||||||
/// dismiss: .cancelTapped
|
/// dismiss: .cancelTapped
|
||||||
/// )
|
/// )
|
||||||
|
/// ```
|
||||||
///
|
///
|
||||||
/// This makes your reducer in complete control of when the action sheet is shown or dismissed, and
|
/// 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
|
/// 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:
|
/// Even better, you can instantly write tests that your action sheet behavior works as expected:
|
||||||
///
|
///
|
||||||
|
/// ```swift
|
||||||
/// let store = TestStore(
|
/// let store = TestStore(
|
||||||
/// initialState: AppState(),
|
/// initialState: AppState(),
|
||||||
/// reducer: appReducer,
|
/// reducer: appReducer,
|
||||||
@@ -96,6 +105,7 @@ import SwiftUI
|
|||||||
/// $0.actionSheet = nil
|
/// $0.actionSheet = nil
|
||||||
/// // Also verify that favoriting logic executed correctly
|
/// // Also verify that favoriting logic executed correctly
|
||||||
/// }
|
/// }
|
||||||
|
/// ```
|
||||||
///
|
///
|
||||||
@available(iOS 13, *)
|
@available(iOS 13, *)
|
||||||
@available(macCatalyst 13, *)
|
@available(macCatalyst 13, *)
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import SwiftUI
|
|||||||
///
|
///
|
||||||
/// To use this API, you model all the alert actions in your domain's action enum:
|
/// To use this API, you model all the alert actions in your domain's action enum:
|
||||||
///
|
///
|
||||||
|
/// ```swift
|
||||||
/// enum AppAction: Equatable {
|
/// enum AppAction: Equatable {
|
||||||
/// case cancelTapped
|
/// case cancelTapped
|
||||||
/// case confirmTapped
|
/// case confirmTapped
|
||||||
@@ -20,19 +21,23 @@ import SwiftUI
|
|||||||
///
|
///
|
||||||
/// // Your other actions
|
/// // Your other actions
|
||||||
/// }
|
/// }
|
||||||
|
/// ```
|
||||||
///
|
///
|
||||||
/// And you model the state for showing the alert in your domain's state, and it can start off
|
/// And you model the state for showing the alert in your domain's state, and it can start off
|
||||||
/// `nil`:
|
/// `nil`:
|
||||||
///
|
///
|
||||||
|
/// ```swift
|
||||||
/// struct AppState: Equatable {
|
/// struct AppState: Equatable {
|
||||||
/// var alert: AlertState<AppAction>?
|
/// var alert: AlertState<AppAction>?
|
||||||
///
|
///
|
||||||
/// // Your other state
|
/// // 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:
|
/// to show to the user:
|
||||||
///
|
///
|
||||||
|
/// ```swift
|
||||||
/// let appReducer = Reducer<AppState, AppAction, AppEnvironment> { state, action, env in
|
/// let appReducer = Reducer<AppState, AppAction, AppEnvironment> { state, action, env in
|
||||||
/// switch action
|
/// switch action
|
||||||
/// case .cancelTapped:
|
/// case .cancelTapped:
|
||||||
@@ -53,15 +58,18 @@ import SwiftUI
|
|||||||
/// return .none
|
/// return .none
|
||||||
/// }
|
/// }
|
||||||
/// }
|
/// }
|
||||||
|
/// ```
|
||||||
///
|
///
|
||||||
/// And then, in your view you can use the `.alert(_:send:dismiss:)` method on `View` in order
|
/// 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:
|
/// to present the alert in a way that works best with the Composable Architecture:
|
||||||
///
|
///
|
||||||
|
/// ```swift
|
||||||
/// Button("Delete") { viewStore.send(.deleteTapped) }
|
/// Button("Delete") { viewStore.send(.deleteTapped) }
|
||||||
/// .alert(
|
/// .alert(
|
||||||
/// self.store.scope(state: \.alert),
|
/// self.store.scope(state: \.alert),
|
||||||
/// dismiss: .cancelTapped
|
/// dismiss: .cancelTapped
|
||||||
/// )
|
/// )
|
||||||
|
/// ```
|
||||||
///
|
///
|
||||||
/// This makes your reducer in complete control of when the alert is shown or dismissed, and makes
|
/// 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
|
/// 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:
|
/// Even better, you can instantly write tests that your alert behavior works as expected:
|
||||||
///
|
///
|
||||||
|
/// ```swift
|
||||||
/// let store = TestStore(
|
/// let store = TestStore(
|
||||||
/// initialState: AppState(),
|
/// initialState: AppState(),
|
||||||
/// reducer: appReducer,
|
/// reducer: appReducer,
|
||||||
@@ -87,6 +96,7 @@ import SwiftUI
|
|||||||
/// $0.alert = nil
|
/// $0.alert = nil
|
||||||
/// // Also verify that delete logic executed correctly
|
/// // Also verify that delete logic executed correctly
|
||||||
/// }
|
/// }
|
||||||
|
/// ```
|
||||||
///
|
///
|
||||||
public struct AlertState<Action> {
|
public struct AlertState<Action> {
|
||||||
public let id = UUID()
|
public let id = UUID()
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import SwiftUI
|
|||||||
///
|
///
|
||||||
/// For example, a settings screen may model its state with the following struct:
|
/// For example, a settings screen may model its state with the following struct:
|
||||||
///
|
///
|
||||||
|
/// ```swift
|
||||||
/// struct SettingsState {
|
/// struct SettingsState {
|
||||||
/// var digest = Digest.daily
|
/// var digest = Digest.daily
|
||||||
/// var displayName = ""
|
/// var displayName = ""
|
||||||
@@ -16,11 +17,13 @@ import SwiftUI
|
|||||||
/// var sendEmailNotifications = false
|
/// var sendEmailNotifications = false
|
||||||
/// var sendMobileNotifications = false
|
/// var sendMobileNotifications = false
|
||||||
/// }
|
/// }
|
||||||
|
/// ```
|
||||||
///
|
///
|
||||||
/// Each of these fields should be editable, and in the Composable Architecture this means that each
|
/// 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
|
/// 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:
|
/// form of an enum with a case per field:
|
||||||
///
|
///
|
||||||
|
/// ```swift
|
||||||
/// enum SettingsAction {
|
/// enum SettingsAction {
|
||||||
/// case digestChanged(Digest)
|
/// case digestChanged(Digest)
|
||||||
/// case displayNameChanged(String)
|
/// case displayNameChanged(String)
|
||||||
@@ -29,10 +32,12 @@ import SwiftUI
|
|||||||
/// case sendEmailNotificationsChanged(Bool)
|
/// case sendEmailNotificationsChanged(Bool)
|
||||||
/// case sendMobileNotificationsChanged(Bool)
|
/// case sendMobileNotificationsChanged(Bool)
|
||||||
/// }
|
/// }
|
||||||
|
/// ```
|
||||||
///
|
///
|
||||||
/// And we're not even done yet. In the reducer we must now handle each action, which simply
|
/// 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:
|
/// replaces the state at each field with a new value:
|
||||||
///
|
///
|
||||||
|
/// ```swift
|
||||||
/// let settingsReducer = Reducer<
|
/// let settingsReducer = Reducer<
|
||||||
/// SettingsState, SettingsAction, SettingsEnvironment
|
/// SettingsState, SettingsAction, SettingsEnvironment
|
||||||
/// > { state, action, environment in
|
/// > { state, action, environment in
|
||||||
@@ -62,19 +67,23 @@ import SwiftUI
|
|||||||
/// return .none
|
/// return .none
|
||||||
/// }
|
/// }
|
||||||
/// }
|
/// }
|
||||||
|
/// ```
|
||||||
///
|
///
|
||||||
/// This is a _lot_ of boilerplate for something that should be simple. Luckily, we can dramatically
|
/// 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
|
/// 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
|
/// field-mutating actions into a single case that holds a ``BindingAction`` generic over the
|
||||||
/// reducer's root `SettingsState`:
|
/// reducer's root `SettingsState`:
|
||||||
///
|
///
|
||||||
|
/// ```swift
|
||||||
/// enum SettingsAction {
|
/// enum SettingsAction {
|
||||||
/// case binding(BindingAction<SettingsState>)
|
/// case binding(BindingAction<SettingsState>)
|
||||||
/// }
|
/// }
|
||||||
|
/// ```
|
||||||
///
|
///
|
||||||
/// And then, we can simplify the settings reducer by allowing the `binding` method to handle these
|
/// And then, we can simplify the settings reducer by allowing the `binding` method to handle these
|
||||||
/// field mutations for us:
|
/// field mutations for us:
|
||||||
///
|
///
|
||||||
|
/// ```swift
|
||||||
/// let settingsReducer = Reducer<
|
/// let settingsReducer = Reducer<
|
||||||
/// SettingsState, SettingsAction, SettingsEnvironment
|
/// SettingsState, SettingsAction, SettingsEnvironment
|
||||||
/// > {
|
/// > {
|
||||||
@@ -84,30 +93,36 @@ import SwiftUI
|
|||||||
/// }
|
/// }
|
||||||
/// }
|
/// }
|
||||||
/// .binding(action: /SettingsAction.binding)
|
/// .binding(action: /SettingsAction.binding)
|
||||||
|
/// ```
|
||||||
///
|
///
|
||||||
/// Binding actions are constructed and sent to the store by providing a writable key path from root
|
/// 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.
|
/// 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:
|
/// You can derive a binding by specifying the key path and binding action case:
|
||||||
///
|
///
|
||||||
|
/// ```swift
|
||||||
/// TextField(
|
/// TextField(
|
||||||
/// "Display name",
|
/// "Display name",
|
||||||
/// text: viewStore.binding(keyPath: \.displayName, send: SettingsAction.binding)
|
/// text: viewStore.binding(keyPath: \.displayName, send: SettingsAction.binding)
|
||||||
/// )
|
/// )
|
||||||
|
/// ```
|
||||||
///
|
///
|
||||||
/// Should you need to layer additional functionality over these bindings, your reducer can pattern
|
/// Should you need to layer additional functionality over these bindings, your reducer can pattern
|
||||||
/// match the action for a given key path:
|
/// match the action for a given key path:
|
||||||
///
|
///
|
||||||
|
/// ```swift
|
||||||
/// case .binding(\.displayName):
|
/// case .binding(\.displayName):
|
||||||
/// // Validate display name
|
/// // Validate display name
|
||||||
///
|
///
|
||||||
/// case .binding(\.enableNotifications):
|
/// case .binding(\.enableNotifications):
|
||||||
/// // Return an authorization request effect
|
/// // Return an authorization request effect
|
||||||
|
/// ```
|
||||||
///
|
///
|
||||||
/// Binding actions can also be tested in much the same way regular actions are tested. Rather than
|
/// 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")`,
|
/// 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"))`:
|
/// as `.binding(.set(\.displayName, "Blob"))`:
|
||||||
///
|
///
|
||||||
|
/// ```swift
|
||||||
/// let store = TestStore(
|
/// let store = TestStore(
|
||||||
/// initialState: SettingsState(),
|
/// initialState: SettingsState(),
|
||||||
/// reducer: settingsReducer,
|
/// reducer: settingsReducer,
|
||||||
@@ -120,6 +135,7 @@ import SwiftUI
|
|||||||
/// store.send(.binding(.set(\.protectMyPosts, true))) {
|
/// store.send(.binding(.set(\.protectMyPosts, true))) {
|
||||||
/// $0.protectMyPosts = true
|
/// $0.protectMyPosts = true
|
||||||
/// )
|
/// )
|
||||||
|
/// ```
|
||||||
///
|
///
|
||||||
public struct BindingAction<Root>: Equatable {
|
public struct BindingAction<Root>: Equatable {
|
||||||
public let keyPath: PartialKeyPath<Root>
|
public let keyPath: PartialKeyPath<Root>
|
||||||
@@ -177,28 +193,32 @@ public struct BindingAction<Root>: Equatable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
extension Reducer {
|
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.
|
/// 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:
|
/// case:
|
||||||
///
|
///
|
||||||
|
/// ```swift
|
||||||
/// enum SettingsAction {
|
/// enum SettingsAction {
|
||||||
/// ...
|
/// ...
|
||||||
/// case binding(BindingAction<SettingsState>)
|
/// case binding(BindingAction<SettingsState>)
|
||||||
/// }
|
/// }
|
||||||
|
/// ```
|
||||||
///
|
///
|
||||||
/// The reducer can then be enhanced to automatically handle these mutations for you by tacking on
|
/// 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<SettingsState, SettingsAction, SettingsEnvironment {
|
/// let settingsReducer = Reducer<SettingsState, SettingsAction, SettingsEnvironment {
|
||||||
/// ...
|
/// ...
|
||||||
/// }
|
/// }
|
||||||
/// .binding(action: /SettingsAction.binding)
|
/// .binding(action: /SettingsAction.binding)
|
||||||
|
/// ```
|
||||||
///
|
///
|
||||||
/// - Parameter toBindingAction: A case path from this reducer's `Action` type to a
|
/// - Parameter toBindingAction: A case path from this reducer's `Action` type to a
|
||||||
/// `BindingAction` over this reducer's `State`.
|
/// `BindingAction` over this reducer's `State`.
|
||||||
/// - 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.
|
/// reducer's logic.
|
||||||
public func binding(action toBindingAction: CasePath<Action, BindingAction<State>>) -> Self {
|
public func binding(action toBindingAction: CasePath<Action, BindingAction<State>>) -> Self {
|
||||||
Self { state, action, environment in
|
Self { state, action, environment in
|
||||||
@@ -211,10 +231,11 @@ extension Reducer {
|
|||||||
|
|
||||||
extension ViewStore {
|
extension ViewStore {
|
||||||
/// Derives a binding from the store that mutates state at the given writable key path by wrapping
|
/// 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:
|
/// For example, a text field binding can be created like this:
|
||||||
///
|
///
|
||||||
|
/// ```swift
|
||||||
/// struct State { var text = "" }
|
/// struct State { var text = "" }
|
||||||
/// enum Action { case binding(BindingAction<State>) }
|
/// enum Action { case binding(BindingAction<State>) }
|
||||||
///
|
///
|
||||||
@@ -222,6 +243,7 @@ extension ViewStore {
|
|||||||
/// "Enter text",
|
/// "Enter text",
|
||||||
/// text: viewStore.binding(keyPath: \.text, Action.binding)
|
/// text: viewStore.binding(keyPath: \.text, Action.binding)
|
||||||
/// )
|
/// )
|
||||||
|
/// ```
|
||||||
///
|
///
|
||||||
/// - Parameters:
|
/// - Parameters:
|
||||||
/// - keyPath: A writable key path from the view store's state to a mutable field
|
/// - keyPath: A writable key path from the view store's state to a mutable field
|
||||||
|
|||||||
@@ -3,12 +3,13 @@ import SwiftUI
|
|||||||
/// A Composable Architecture-friendly wrapper around `ForEach` that simplifies working with
|
/// A Composable Architecture-friendly wrapper around `ForEach` that simplifies working with
|
||||||
/// collections of state.
|
/// 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
|
/// element. This allows you to extract and modularize an element's view and avoid concerns around
|
||||||
/// collection index math and parent-child store communication.
|
/// collection index math and parent-child store communication.
|
||||||
///
|
///
|
||||||
/// For example, a todos app may define the domain and logic associated with an individual todo:
|
/// For example, a todos app may define the domain and logic associated with an individual todo:
|
||||||
///
|
///
|
||||||
|
/// ```swift
|
||||||
/// struct TodoState: Equatable, Identifiable {
|
/// struct TodoState: Equatable, Identifiable {
|
||||||
/// let id: UUID
|
/// let id: UUID
|
||||||
/// var description = ""
|
/// var description = ""
|
||||||
@@ -20,42 +21,54 @@ import SwiftUI
|
|||||||
/// }
|
/// }
|
||||||
/// struct TodoEnvironment {}
|
/// struct TodoEnvironment {}
|
||||||
/// let todoReducer = Reducer<TodoState, TodoAction, TodoEnvironment { ... }
|
/// let todoReducer = Reducer<TodoState, TodoAction, TodoEnvironment { ... }
|
||||||
|
/// ```
|
||||||
///
|
///
|
||||||
/// As well as a view with a domain-specific store:
|
/// As well as a view with a domain-specific store:
|
||||||
///
|
///
|
||||||
|
/// ```swift
|
||||||
/// struct TodoView: View {
|
/// struct TodoView: View {
|
||||||
/// let store: Store<TodoState, TodoAction>
|
/// let store: Store<TodoState, TodoAction>
|
||||||
/// var body: some View { ... }
|
/// var body: some View { ... }
|
||||||
/// }
|
/// }
|
||||||
|
/// ```
|
||||||
///
|
///
|
||||||
/// For a parent domain to work with a collection of todos, it can hold onto this collection in
|
/// For a parent domain to work with a collection of todos, it can hold onto this collection in
|
||||||
/// state:
|
/// state:
|
||||||
///
|
///
|
||||||
|
/// ```swift
|
||||||
/// struct AppState: Equatable {
|
/// struct AppState: Equatable {
|
||||||
/// var todos: IdentifiedArrayOf<TodoState> = []
|
/// var todos: IdentifiedArrayOf<TodoState> = []
|
||||||
/// }
|
/// }
|
||||||
|
/// ```
|
||||||
///
|
///
|
||||||
/// Define a case to handle actions sent to the child domain:
|
/// Define a case to handle actions sent to the child domain:
|
||||||
///
|
///
|
||||||
|
/// ```swift
|
||||||
/// enum AppAction {
|
/// enum AppAction {
|
||||||
/// case todo(id: TodoState.ID, action: TodoAction)
|
/// 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(
|
/// let appReducer = todoReducer.forEach(
|
||||||
/// state: \.todos,
|
/// state: \.todos,
|
||||||
/// action: /AppAction.todo(id:action:),
|
/// action: /AppAction.todo(id:action:),
|
||||||
/// environment: { _ in TodoEnvironment() }
|
/// 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(
|
/// ForEachStore(
|
||||||
/// self.store.scope(state: \.todos, AppAction.todo(id:action:))
|
/// self.store.scope(state: \.todos, AppAction.todo(id:action:))
|
||||||
/// ) { todoStore in
|
/// ) { todoStore in
|
||||||
/// TodoView(store: todoStore)
|
/// TodoView(store: todoStore)
|
||||||
/// }
|
/// }
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
public struct ForEachStore<EachState, EachAction, Data, ID, Content>: DynamicViewContent
|
public struct ForEachStore<EachState, EachAction, Data, ID, Content>: DynamicViewContent
|
||||||
where Data: Collection, ID: Hashable, Content: View {
|
where Data: Collection, ID: Hashable, Content: View {
|
||||||
public let data: Data
|
public let data: Data
|
||||||
|
|||||||
@@ -17,7 +17,9 @@ public struct Identified<ID, Value>: Identifiable where ID: Hashable {
|
|||||||
/// Initializes an identified value from a given value and a function that can return a hashable
|
/// Initializes an identified value from a given value and a function that can return a hashable
|
||||||
/// identifier from the value.
|
/// identifier from the value.
|
||||||
///
|
///
|
||||||
|
/// ```swift
|
||||||
/// Identified(uuid, id: \.self)
|
/// Identified(uuid, id: \.self)
|
||||||
|
/// ```
|
||||||
///
|
///
|
||||||
/// - Parameters:
|
/// - Parameters:
|
||||||
/// - value: A value.
|
/// - value: A value.
|
||||||
@@ -30,7 +32,9 @@ public struct Identified<ID, Value>: Identifiable where ID: Hashable {
|
|||||||
/// Initializes an identified value from a given value and a function that can return a hashable
|
/// Initializes an identified value from a given value and a function that can return a hashable
|
||||||
/// identifier from the value.
|
/// identifier from the value.
|
||||||
///
|
///
|
||||||
|
/// ```swift
|
||||||
/// Identified(uuid, id: \.self)
|
/// Identified(uuid, id: \.self)
|
||||||
|
/// ```
|
||||||
///
|
///
|
||||||
/// - Parameters:
|
/// - Parameters:
|
||||||
/// - value: A value.
|
/// - value: A value.
|
||||||
|
|||||||
@@ -1,19 +1,22 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
/// An array of elements that can be identified by a given key path.
|
/// 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:
|
/// your application may model a counter in an identifiable fashion:
|
||||||
///
|
///
|
||||||
|
/// ```swift
|
||||||
/// struct CounterState: Identifiable {
|
/// struct CounterState: Identifiable {
|
||||||
/// let id: UUID
|
/// let id: UUID
|
||||||
/// var count = 0
|
/// var count = 0
|
||||||
/// }
|
/// }
|
||||||
/// enum CounterAction { case incr, decr }
|
/// enum CounterAction { case incr, decr }
|
||||||
/// let counterReducer = Reducer<CounterState, CounterAction, Void> { ... }
|
/// let counterReducer = Reducer<CounterState, CounterAction, Void> { ... }
|
||||||
|
/// ```
|
||||||
///
|
///
|
||||||
/// 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<CounterState>() }
|
/// struct AppState { var counters = IdentifiedArrayOf<CounterState>() }
|
||||||
/// enum AppAction { case counter(id: UUID, action: CounterAction) }
|
/// enum AppAction { case counter(id: UUID, action: CounterAction) }
|
||||||
/// let appReducer = counterReducer.forEach(
|
/// let appReducer = counterReducer.forEach(
|
||||||
@@ -21,9 +24,11 @@ import Foundation
|
|||||||
/// action: /AppAction.counter(id:action:),
|
/// action: /AppAction.counter(id:action:),
|
||||||
/// environment: { $0 }
|
/// environment: { $0 }
|
||||||
/// )
|
/// )
|
||||||
|
/// ```
|
||||||
///
|
///
|
||||||
/// And then SwiftUI can work with this array of identified elements in a list view:
|
/// And then SwiftUI can work with this array of identified elements in a list view:
|
||||||
///
|
///
|
||||||
|
/// ```swift
|
||||||
/// struct AppView: View {
|
/// struct AppView: View {
|
||||||
/// let store: Store<AppState, AppAction>
|
/// let store: Store<AppState, AppAction>
|
||||||
///
|
///
|
||||||
@@ -36,6 +41,8 @@ import Foundation
|
|||||||
/// }
|
/// }
|
||||||
/// }
|
/// }
|
||||||
/// }
|
/// }
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
public struct IdentifiedArray<ID, Element>: MutableCollection, RandomAccessCollection
|
public struct IdentifiedArray<ID, Element>: MutableCollection, RandomAccessCollection
|
||||||
where ID: Hashable {
|
where ID: Hashable {
|
||||||
/// A key path to a value that identifies an element.
|
/// A key path to a value that identifies an element.
|
||||||
|
|||||||
@@ -2,19 +2,22 @@ import SwiftUI
|
|||||||
|
|
||||||
/// A view that safely unwraps a store of optional state in order to show one of two views.
|
/// 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.
|
/// 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:
|
/// This is useful for deciding between two views to show depending on an optional piece of state:
|
||||||
///
|
///
|
||||||
|
/// ```swift
|
||||||
/// IfLetStore(
|
/// IfLetStore(
|
||||||
/// store.scope(state: \SearchState.results, action: SearchAction.results),
|
/// store.scope(state: \SearchState.results, action: SearchAction.results),
|
||||||
/// then: SearchResultsView.init(store:),
|
/// then: SearchResultsView.init(store:),
|
||||||
/// else: { Text("Loading search results...") }
|
/// else: { Text("Loading search results...") }
|
||||||
/// )
|
/// )
|
||||||
|
/// ```
|
||||||
///
|
///
|
||||||
/// And for performing navigation when a piece of state becomes non-`nil`:
|
/// And for performing navigation when a piece of state becomes non-`nil`:
|
||||||
///
|
///
|
||||||
|
/// ```swift
|
||||||
/// NavigationLink(
|
/// NavigationLink(
|
||||||
/// destination: IfLetStore(
|
/// destination: IfLetStore(
|
||||||
/// self.store.scope(state: \.detail, action: AppAction.detail),
|
/// self.store.scope(state: \.detail, action: AppAction.detail),
|
||||||
@@ -27,12 +30,13 @@ import SwiftUI
|
|||||||
/// ) {
|
/// ) {
|
||||||
/// Text("Start!")
|
/// Text("Start!")
|
||||||
/// }
|
/// }
|
||||||
|
/// ```
|
||||||
///
|
///
|
||||||
public struct IfLetStore<State, Action, Content>: View where Content: View {
|
public struct IfLetStore<State, Action, Content>: View where Content: View {
|
||||||
private let content: (ViewStore<State?, Action>) -> Content
|
private let content: (ViewStore<State?, Action>) -> Content
|
||||||
private let store: Store<State?, Action>
|
private let store: Store<State?, Action>
|
||||||
|
|
||||||
/// 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`.
|
/// state is `nil` or non-`nil`.
|
||||||
///
|
///
|
||||||
/// - Parameters:
|
/// - Parameters:
|
||||||
@@ -55,7 +59,7 @@ public struct IfLetStore<State, Action, Content>: 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`.
|
/// state is `nil` or non-`nil`.
|
||||||
///
|
///
|
||||||
/// - Parameters:
|
/// - Parameters:
|
||||||
|
|||||||
@@ -7,42 +7,50 @@ import SwiftUI
|
|||||||
/// `Equatable`, their `==` do not return `true` when used with seemingly equal values. If we were
|
/// `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.
|
/// 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.
|
/// 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
|
/// 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 {
|
/// struct AppState: Equatable {
|
||||||
/// var label: TextState
|
/// var label: TextState
|
||||||
/// }
|
/// }
|
||||||
|
/// ```
|
||||||
///
|
///
|
||||||
/// Your reducer can then assign a value to this state using an API similar to that of
|
/// Your reducer can then assign a value to this state using an API similar to that of
|
||||||
/// `SwiftUI.Text`.
|
/// `SwiftUI.Text`.
|
||||||
///
|
///
|
||||||
|
/// ```swift
|
||||||
/// state.label = TextState("Hello, ") + TextState(name).bold() + TextState("!")
|
/// state.label = TextState("Hello, ") + TextState(name).bold() + TextState("!")
|
||||||
|
/// ```
|
||||||
///
|
///
|
||||||
/// And your view store can render it directly:
|
/// And your view store can render it directly:
|
||||||
///
|
///
|
||||||
|
/// ```swift
|
||||||
/// var body: some View {
|
/// var body: some View {
|
||||||
/// WithViewStore(self.store) { viewStore in
|
/// WithViewStore(self.store) { viewStore in
|
||||||
/// viewStore.label
|
/// viewStore.label
|
||||||
/// }
|
/// }
|
||||||
/// }
|
/// }
|
||||||
|
/// ```
|
||||||
///
|
///
|
||||||
/// Certain SwiftUI APIs, like alerts and action sheets, take `Text` values and, not views. To
|
/// 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))
|
/// Alert(title: Text(viewStore.label))
|
||||||
|
/// ```
|
||||||
///
|
///
|
||||||
/// The Composable Architecture comes with a few convenience APIs for alerts and action sheets that
|
/// 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
|
/// 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
|
/// - Note: ``TextState`` does not support _all_ `LocalizedStringKey` permutations at this time
|
||||||
/// (interpolated `SwiftUI.Image`s, for example. `TextState` also uses reflection to determine
|
/// (interpolated `SwiftUI.Image`s, for example. ``TextState`` also uses reflection to determine
|
||||||
/// `LocalizedStringKey` equatability, so be mindful of edge cases.
|
/// `LocalizedStringKey` equatability, so be mindful of edge cases.
|
||||||
public struct TextState: Equatable, Hashable {
|
public struct TextState: Equatable, Hashable {
|
||||||
fileprivate var modifiers: [Modifier] = []
|
fileprivate var modifiers: [Modifier] = []
|
||||||
|
|||||||
@@ -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
|
/// 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:
|
/// 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
|
/// 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
|
/// possible, then you must hold onto an explicit
|
||||||
/// `@ObservedObject var viewStore: ViewStore<State, Action>` in your view in lieu of using this
|
/// `@ObservedObject var viewStore: ViewStore<State, Action>` in your view in lieu of using this
|
||||||
/// helper (see [here](https://gist.github.com/mbrandonw/cc5da3d487bcf7c4f21c27019a440d18)).
|
/// helper (see [here](https://gist.github.com/mbrandonw/cc5da3d487bcf7c4f21c27019a440d18)).
|
||||||
/// * If you create a `Stepper` via the `Stepper.init(onIncrement:onDecrement:label:)` initializer
|
/// * 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
|
/// initializer that takes a binding (see
|
||||||
/// [here](https://gist.github.com/mbrandonw/dee2ceac2c316a1619cfdf1dc7945f66)).
|
/// [here](https://gist.github.com/mbrandonw/dee2ceac2c316a1619cfdf1dc7945f66)).
|
||||||
public struct WithViewStore<State, Action, Content> {
|
public struct WithViewStore<State, Action, Content> {
|
||||||
|
|||||||
@@ -10,6 +10,7 @@
|
|||||||
/// For example, let's say we have a very simple counter application, where a user can increment
|
/// 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:
|
/// and decrement a number. The state and actions are simple enough:
|
||||||
///
|
///
|
||||||
|
/// ```swift
|
||||||
/// struct CounterState: Equatable {
|
/// struct CounterState: Equatable {
|
||||||
/// var count = 0
|
/// var count = 0
|
||||||
/// }
|
/// }
|
||||||
@@ -18,18 +19,22 @@
|
|||||||
/// case decrementButtonTapped
|
/// case decrementButtonTapped
|
||||||
/// case incrementButtonTapped
|
/// case incrementButtonTapped
|
||||||
/// }
|
/// }
|
||||||
|
/// ```
|
||||||
///
|
///
|
||||||
/// Let's throw in a side effect. If the user attempts to decrement the counter below zero, the
|
/// 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.
|
/// application should refuse and play an alert sound instead.
|
||||||
///
|
///
|
||||||
/// We can model playing a sound in the environment with an effect:
|
/// We can model playing a sound in the environment with an effect:
|
||||||
///
|
///
|
||||||
|
/// ```swift
|
||||||
/// struct CounterEnvironment {
|
/// struct CounterEnvironment {
|
||||||
/// let playAlertSound: () -> Effect<Never, Never>
|
/// let playAlertSound: () -> Effect<Never, Never>
|
||||||
/// }
|
/// }
|
||||||
|
/// ```
|
||||||
///
|
///
|
||||||
/// Now that we've defined the domain, we can describe the logic in a reducer:
|
/// Now that we've defined the domain, we can describe the logic in a reducer:
|
||||||
///
|
///
|
||||||
|
/// ```swift
|
||||||
/// let counterReducer = Reducer<
|
/// let counterReducer = Reducer<
|
||||||
/// CounterState, CounterAction, CounterEnvironment
|
/// CounterState, CounterAction, CounterEnvironment
|
||||||
/// > { state, action, environment in
|
/// > { state, action, environment in
|
||||||
@@ -48,11 +53,13 @@
|
|||||||
/// return .non
|
/// return .non
|
||||||
/// }
|
/// }
|
||||||
/// }
|
/// }
|
||||||
|
/// ```
|
||||||
///
|
///
|
||||||
/// Let's say we want to write a test for the increment path. We can see in the reducer that it
|
/// 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
|
/// should never play an alert, so we can configure the environment with an effect that will
|
||||||
/// fail if it ever executes:
|
/// fail if it ever executes:
|
||||||
///
|
///
|
||||||
|
/// ```swift
|
||||||
/// func testIncrement() {
|
/// func testIncrement() {
|
||||||
/// let store = TestStore(
|
/// let store = TestStore(
|
||||||
/// initialState: CounterState(count: 0)
|
/// initialState: CounterState(count: 0)
|
||||||
@@ -66,6 +73,7 @@
|
|||||||
/// $0.count = 1
|
/// $0.count = 1
|
||||||
/// }
|
/// }
|
||||||
/// }
|
/// }
|
||||||
|
/// ```
|
||||||
///
|
///
|
||||||
/// By using a `.failing` effect in our environment we have strengthened the assertion and made
|
/// 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
|
/// the test easier to understand at the same time. We can see, without consulting the reducer
|
||||||
|
|||||||
@@ -38,6 +38,7 @@
|
|||||||
///
|
///
|
||||||
/// For example, given a simple counter reducer:
|
/// For example, given a simple counter reducer:
|
||||||
///
|
///
|
||||||
|
/// ```swift
|
||||||
/// struct CounterState {
|
/// struct CounterState {
|
||||||
/// var count = 0
|
/// var count = 0
|
||||||
/// }
|
/// }
|
||||||
@@ -58,9 +59,11 @@
|
|||||||
/// return .none
|
/// return .none
|
||||||
/// }
|
/// }
|
||||||
/// }
|
/// }
|
||||||
|
/// ```
|
||||||
///
|
///
|
||||||
/// One can assert against its behavior over time:
|
/// One can assert against its behavior over time:
|
||||||
///
|
///
|
||||||
|
/// ```swift
|
||||||
/// class CounterTests: XCTestCase {
|
/// class CounterTests: XCTestCase {
|
||||||
/// func testCounter() {
|
/// func testCounter() {
|
||||||
/// let store = TestStore(
|
/// let store = TestStore(
|
||||||
@@ -73,14 +76,16 @@
|
|||||||
/// }
|
/// }
|
||||||
/// }
|
/// }
|
||||||
/// }
|
/// }
|
||||||
|
/// ```
|
||||||
///
|
///
|
||||||
/// Note that in the trailing closure of `.send(.incrementButtonTapped)` we are given a single
|
/// 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
|
/// 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`.
|
/// 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
|
/// 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 {
|
/// struct SearchState: Equatable {
|
||||||
/// var query = ""
|
/// var query = ""
|
||||||
/// var results: [String] = []
|
/// var results: [String] = []
|
||||||
@@ -112,9 +117,11 @@
|
|||||||
/// return .none
|
/// return .none
|
||||||
/// }
|
/// }
|
||||||
/// }
|
/// }
|
||||||
|
/// ```swift
|
||||||
///
|
///
|
||||||
/// It can be fully tested by controlling the environment's scheduler and effect:
|
/// 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
|
/// // Create a test dispatch scheduler to control the timing of effects
|
||||||
/// let scheduler = DispatchQueue.test
|
/// let scheduler = DispatchQueue.test
|
||||||
///
|
///
|
||||||
@@ -153,6 +160,7 @@
|
|||||||
/// // Assert that state updates accordingly
|
/// // Assert that state updates accordingly
|
||||||
/// $0.results = ["Composable Architecture"]
|
/// $0.results = ["Composable Architecture"]
|
||||||
/// }
|
/// }
|
||||||
|
/// ```
|
||||||
///
|
///
|
||||||
/// This test is proving that the debounced network requests are correctly canceled when we do not
|
/// 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
|
/// 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.
|
/// 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.
|
/// - Returns: A step that captures a sub-sequence of steps.
|
||||||
public static func sequence(
|
public static func sequence(
|
||||||
_ steps: [Step],
|
_ steps: [Step],
|
||||||
@@ -630,7 +638,7 @@
|
|||||||
|
|
||||||
/// A step that captures a sub-sequence of steps.
|
/// 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.
|
/// - Returns: A step that captures a sub-sequence of steps.
|
||||||
public static func sequence(
|
public static func sequence(
|
||||||
_ steps: Step...,
|
_ steps: Step...,
|
||||||
|
|||||||
@@ -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
|
/// 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:
|
/// `nil` to non-`nil`, or non-`nil` to `nil`, you can update the navigation stack accordingly:
|
||||||
///
|
///
|
||||||
|
/// ```swift
|
||||||
/// class ParentViewController: UIViewController {
|
/// class ParentViewController: UIViewController {
|
||||||
/// let store: Store<ParentState, ParentAction>
|
/// let store: Store<ParentState, ParentAction>
|
||||||
/// var cancellables: Set<AnyCancellable> = []
|
/// var cancellables: Set<AnyCancellable> = []
|
||||||
@@ -35,6 +36,7 @@ extension Store {
|
|||||||
/// .store(in: &self.cancellables)
|
/// .store(in: &self.cancellables)
|
||||||
/// }
|
/// }
|
||||||
/// }
|
/// }
|
||||||
|
/// ```
|
||||||
///
|
///
|
||||||
/// - Parameters:
|
/// - Parameters:
|
||||||
/// - unwrap: A function that is called with a store of non-optional state when the store's
|
/// - unwrap: A function that is called with a store of non-optional state when the store's
|
||||||
|
|||||||
@@ -1,14 +1,15 @@
|
|||||||
import Combine
|
import Combine
|
||||||
import SwiftUI
|
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
|
/// 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.
|
/// 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
|
/// It can be initialized with a store and a closure that is handed a view store and must return a
|
||||||
/// view to be rendered:
|
/// view to be rendered:
|
||||||
///
|
///
|
||||||
|
/// ```swift
|
||||||
/// var body: some View {
|
/// var body: some View {
|
||||||
/// WithViewStore(self.store) { viewStore in
|
/// WithViewStore(self.store) { viewStore in
|
||||||
/// VStack {
|
/// 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:
|
/// state updates:
|
||||||
///
|
///
|
||||||
|
/// ```swift
|
||||||
/// let store: Store<State, Action>
|
/// let store: Store<State, Action>
|
||||||
/// let viewStore: ViewStore<State, Action>
|
/// let viewStore: ViewStore<State, Action>
|
||||||
///
|
///
|
||||||
@@ -40,6 +42,7 @@ import SwiftUI
|
|||||||
/// @objc func incrementButtonTapped() {
|
/// @objc func incrementButtonTapped() {
|
||||||
/// self.viewStore.send(.incrementButtonTapped)
|
/// self.viewStore.send(.incrementButtonTapped)
|
||||||
/// }
|
/// }
|
||||||
|
/// ```
|
||||||
///
|
///
|
||||||
@dynamicMemberLookup
|
@dynamicMemberLookup
|
||||||
public final class ViewStore<State, Action>: ObservableObject {
|
public final class ViewStore<State, Action>: ObservableObject {
|
||||||
@@ -85,10 +88,10 @@ public final class ViewStore<State, Action>: ObservableObject {
|
|||||||
|
|
||||||
/// Sends an action to the store.
|
/// 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
|
/// 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
|
/// 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.
|
/// be fed back into the store.
|
||||||
///
|
///
|
||||||
/// - Parameter action: An action.
|
/// - Parameter action: An action.
|
||||||
@@ -100,11 +103,12 @@ public final class ViewStore<State, Action>: ObservableObject {
|
|||||||
/// actions to the store.
|
/// actions to the store.
|
||||||
///
|
///
|
||||||
/// The method is useful for dealing with SwiftUI components that work with two-way `Binding`s
|
/// 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.
|
/// sending actions.
|
||||||
///
|
///
|
||||||
/// For example, a text field binding can be created like this:
|
/// For example, a text field binding can be created like this:
|
||||||
///
|
///
|
||||||
|
/// ```swift
|
||||||
/// struct State { var name = "" }
|
/// struct State { var name = "" }
|
||||||
/// enum Action { case nameChanged(String) }
|
/// enum Action { case nameChanged(String) }
|
||||||
///
|
///
|
||||||
@@ -115,6 +119,7 @@ public final class ViewStore<State, Action>: ObservableObject {
|
|||||||
/// send: { Action.nameChanged($0) }
|
/// send: { Action.nameChanged($0) }
|
||||||
/// )
|
/// )
|
||||||
/// )
|
/// )
|
||||||
|
/// ```
|
||||||
///
|
///
|
||||||
/// - Parameters:
|
/// - Parameters:
|
||||||
/// - get: A function to get the state for the binding from the view
|
/// - get: A function to get the state for the binding from the view
|
||||||
@@ -144,11 +149,12 @@ public final class ViewStore<State, Action>: ObservableObject {
|
|||||||
/// actions to the store.
|
/// actions to the store.
|
||||||
///
|
///
|
||||||
/// The method is useful for dealing with SwiftUI components that work with two-way `Binding`s
|
/// 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.
|
/// sending actions.
|
||||||
///
|
///
|
||||||
/// For example, an alert binding can be dealt with like this:
|
/// For example, an alert binding can be dealt with like this:
|
||||||
///
|
///
|
||||||
|
/// ```swift
|
||||||
/// struct State { var alert: String? }
|
/// struct State { var alert: String? }
|
||||||
/// enum Action { case alertDismissed }
|
/// enum Action { case alertDismissed }
|
||||||
///
|
///
|
||||||
@@ -158,6 +164,7 @@ public final class ViewStore<State, Action>: ObservableObject {
|
|||||||
/// send: .alertDismissed
|
/// send: .alertDismissed
|
||||||
/// )
|
/// )
|
||||||
/// ) { alert in Alert(title: Text(alert.message)) }
|
/// ) { alert in Alert(title: Text(alert.message)) }
|
||||||
|
/// ```
|
||||||
///
|
///
|
||||||
/// - Parameters:
|
/// - Parameters:
|
||||||
/// - get: A function to get the state for the binding from the view store's full state.
|
/// - 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<State, Action>: ObservableObject {
|
|||||||
/// actions to the store.
|
/// actions to the store.
|
||||||
///
|
///
|
||||||
/// The method is useful for dealing with SwiftUI components that work with two-way `Binding`s
|
/// 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.
|
/// sending actions.
|
||||||
///
|
///
|
||||||
/// For example, a text field binding can be created like this:
|
/// For example, a text field binding can be created like this:
|
||||||
///
|
///
|
||||||
|
/// ```swift
|
||||||
/// typealias State = String
|
/// typealias State = String
|
||||||
/// enum Action { case nameChanged(String) }
|
/// enum Action { case nameChanged(String) }
|
||||||
///
|
///
|
||||||
@@ -188,6 +196,7 @@ public final class ViewStore<State, Action>: ObservableObject {
|
|||||||
/// send: { Action.nameChanged($0) }
|
/// send: { Action.nameChanged($0) }
|
||||||
/// )
|
/// )
|
||||||
/// )
|
/// )
|
||||||
|
/// ```
|
||||||
///
|
///
|
||||||
/// - Parameters:
|
/// - Parameters:
|
||||||
/// - localStateToViewAction: A function that transforms the binding's value
|
/// - localStateToViewAction: A function that transforms the binding's value
|
||||||
@@ -203,11 +212,12 @@ public final class ViewStore<State, Action>: ObservableObject {
|
|||||||
/// actions to the store.
|
/// actions to the store.
|
||||||
///
|
///
|
||||||
/// The method is useful for dealing with SwiftUI components that work with two-way `Binding`s
|
/// 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.
|
/// sending actions.
|
||||||
///
|
///
|
||||||
/// For example, an alert binding can be dealt with like this:
|
/// For example, an alert binding can be dealt with like this:
|
||||||
///
|
///
|
||||||
|
/// ```swift
|
||||||
/// typealias State = String
|
/// typealias State = String
|
||||||
/// enum Action { case alertDismissed }
|
/// enum Action { case alertDismissed }
|
||||||
///
|
///
|
||||||
@@ -216,6 +226,7 @@ public final class ViewStore<State, Action>: ObservableObject {
|
|||||||
/// send: .alertDismissed
|
/// send: .alertDismissed
|
||||||
/// )
|
/// )
|
||||||
/// ) { title in Alert(title: Text(title)) }
|
/// ) { title in Alert(title: Text(title)) }
|
||||||
|
/// ```
|
||||||
///
|
///
|
||||||
/// - Parameters:
|
/// - Parameters:
|
||||||
/// - action: The action to send when the binding is written to.
|
/// - action: The action to send when the binding is written to.
|
||||||
|
|||||||
Reference in New Issue
Block a user