mirror of
https://github.com/pointfreeco/swift-composable-architecture.git
synced 2025-12-24 12:14:25 +01:00
* wip
* fix
* wip
* wip
* move
* wip
* wip
* wip
* wip
* wip
* wip
* wip
* wip
* wip
* wip
* wip
* wip
* wip
* wip
* wip
* wip
* wip
* wip
* Fix
* wip
* wip
* Renamed action to onTap in NavigationLinkStore (#2043)
Renamed the `action` parameter to mirror other inits and differentiate itself from `action fromDestinationAction`
* wip
* wip
* wip
* wip
* wip
* wip
* wip
* wip
* wip
* wip
* wip
* wip
* wip
* wip
* Tie view identity to stack element identity
* Tie item identity to case
* wip
* wip
* cleanup
* fix
* fix
* Add warning to nav link
* wip
* wip
* Rename FullscreenCover.swift to FullScreenCover.swift (#2062)
* wip
* fix isDetailLink on non-iOS platforms
* Correct some comments in Effect.swift (#2081)
* add integration tests for showing alert/dialog from alert/dialog.
* copy StackElementIDGenerator dependency before running TestStore receive closure.
* Removed some unneeded delegate actions.
* wip
* clean up
* lots of clean up
* Converted voice memos back to identified array
* update deps
* update docs for DismissEffect
* wip
* Add Sendable conformance to PresentationState (#2086)
* wip
* swift-format
* wip
* wip
* docs
* wip
* wip
* Catch some typos in Articles (#2088)
* wip
* wip
* wip
* wip
* wip
* docs
* wip
* wip
* docs
* wip
* wip
* wip
* wip
* docs
* docs
* wip
* wip
* wip
* wip
* wip
* wip
* wip
* Fix invalid states count for 3 optionals and typos (#2094)
* wip
* wip
* more dismisseffect docs
* fixed some references
* navigation doc corrections
* more nav docs
* fix cancellation tests in release mode
* wrap some tests in #if DEBUG since they are testing expected failures
* update UUIDs in tests to use shorter initializer
* fixed a todo
* wip
* fix merge errors
* wip
* fix
* wip
* wip
* fixing a bunch of todos
* get rid of rawvalue in StackElementID
* more todos
* NavLinkStore docs
* fix swift 5.6 stuff
* fix some standups tests
* fix
* clean up
* docs fix
* fixes
* wip
* 5.6 fix
* wip
* wip
* dont parallelize tests
* updated demo readmes
* wip
* Use ObservedObject instead of StateObject for alert/dialog modifiers.
* integration tests for bad dismissal behavior
* check for runtime warnings in every integration test
* wip
* wip
* fix
* wip
* wip
* wip
* wip
* wip
* Drop a bunch of Hashables.
* some nav bug fixes
* wip
* wip
* wip
* fix
* fix
* wip
* wip
* Simplify recording test.
* add concurrent async test
* fix
* wip
* Refact how detail dismisses itself.
* fix
* 5.6 fix
* wip
* wip
* Add TestStore.assert.
* Revert "Add TestStore.assert."
This reverts commit a892cccc66.
* add Ukrainian Readme.md (#2121)
* Add TestStore.assert. (#2123)
* Add TestStore.assert.
* wip
* Update Sources/ComposableArchitecture/TestStore.swift
Co-authored-by: Stephen Celis <stephen@stephencelis.com>
* Update Sources/ComposableArchitecture/Documentation.docc/Extensions/TestStore.md
Co-authored-by: Stephen Celis <stephen@stephencelis.com>
* fix tests
---------
Co-authored-by: Stephen Celis <stephen@stephencelis.com>
* Run swift-format
* push for store.finish and presentation
* move docs around
* wip
* wip
* wip
* wip
* wip
* wip
* wip
* wip
* wip
* wip
* wip
* wip
* wip
* wip
* Add case subscripts
* wip
* wip
* 5.7-only
* wip
* wip
* wip
* wip
* revert store.finish task cancellation
* wip
* wip
* wip
* wip
* wip
* wip
* wip
* wip
* wip
* wip
* wip
* wip
* wip
* wip
* wip
* wip
* wip
* wip
* wip
* wip
* add test for presentation scope
* wip
* wip
* wip
* wip
* wip
* cleanup
* updated presentation scope test
* sytnax update
* clean up
* fix test
* wip
* wip
* wip
* wip
* wip
---------
Co-authored-by: Brandon Williams <mbrandonw@hey.com>
Co-authored-by: Martin Václavík <mvaclavik96@icloud.com>
Co-authored-by: 유재호 <y73447jh@gmail.com>
Co-authored-by: Jackson Utsch <jutechs@gmail.com>
Co-authored-by: Dmytro <barabashdmyto@gmail.com>
Co-authored-by: Brandon Williams <135203+mbrandonw@users.noreply.github.com>
Co-authored-by: mbrandonw <mbrandonw@users.noreply.github.com>
197 lines
7.8 KiB
Swift
197 lines
7.8 KiB
Swift
import SwiftUI
|
|
|
|
/// A view that safely unwraps a store of optional state in order to show one of two views.
|
|
///
|
|
/// When the underlying state is non-`nil`, the `then` closure will be performed with a ``Store``
|
|
/// that holds onto non-optional state, and otherwise the `else` closure will be performed.
|
|
///
|
|
/// This is useful for deciding between two views to show depending on an optional piece of state:
|
|
///
|
|
/// ```swift
|
|
/// IfLetStore(
|
|
/// store.scope(state: \.results, action: Search.Action.results)
|
|
/// ) {
|
|
/// SearchResultsView(store: $0)
|
|
/// } else: {
|
|
/// Text("Loading search results...")
|
|
/// }
|
|
/// ```
|
|
///
|
|
public struct IfLetStore<State, Action, Content: View>: View {
|
|
private let content: (ViewStore<State?, Action>) -> Content
|
|
private let store: Store<State?, Action>
|
|
|
|
/// Initializes an ``IfLetStore`` view that computes content depending on if a store of optional
|
|
/// state is `nil` or non-`nil`.
|
|
///
|
|
/// - Parameters:
|
|
/// - store: A store of optional state.
|
|
/// - ifContent: A function that is given a store of non-optional state and returns a view that
|
|
/// is visible only when the optional state is non-`nil`.
|
|
/// - elseContent: A view that is only visible when the optional state is `nil`.
|
|
public init<IfContent, ElseContent>(
|
|
_ store: Store<State?, Action>,
|
|
@ViewBuilder then ifContent: @escaping (Store<State, Action>) -> IfContent,
|
|
@ViewBuilder else elseContent: () -> ElseContent
|
|
) where Content == _ConditionalContent<IfContent, ElseContent> {
|
|
let store = store.invalidate { $0 == nil }
|
|
self.store = store
|
|
let elseContent = elseContent()
|
|
self.content = { viewStore in
|
|
if var state = viewStore.state {
|
|
return ViewBuilder.buildEither(
|
|
first: ifContent(
|
|
store
|
|
.invalidate { $0 == nil }
|
|
.scope(
|
|
state: {
|
|
state = $0 ?? state
|
|
return state
|
|
},
|
|
action: { $0 }
|
|
)
|
|
)
|
|
)
|
|
} else {
|
|
return ViewBuilder.buildEither(second: elseContent)
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Initializes an ``IfLetStore`` view that computes content depending on if a store of optional
|
|
/// state is `nil` or non-`nil`.
|
|
///
|
|
/// - Parameters:
|
|
/// - store: A store of optional state.
|
|
/// - ifContent: A function that is given a store of non-optional state and returns a view that
|
|
/// is visible only when the optional state is non-`nil`.
|
|
public init<IfContent>(
|
|
_ store: Store<State?, Action>,
|
|
@ViewBuilder then ifContent: @escaping (Store<State, Action>) -> IfContent
|
|
) where Content == IfContent? {
|
|
let store = store.invalidate { $0 == nil }
|
|
self.store = store
|
|
self.content = { viewStore in
|
|
if var state = viewStore.state {
|
|
return ifContent(
|
|
store
|
|
.invalidate { $0 == nil }
|
|
.scope(
|
|
state: {
|
|
state = $0 ?? state
|
|
return state
|
|
},
|
|
action: { $0 }
|
|
)
|
|
)
|
|
} else {
|
|
return nil
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Initializes an ``IfLetStore`` view that computes content depending on if a store of
|
|
/// ``PresentationState`` and ``PresentationAction`` is `nil` or non-`nil`.
|
|
///
|
|
/// - Parameters:
|
|
/// - store: A store of optional state.
|
|
/// - ifContent: A function that is given a store of non-optional state and returns a view that
|
|
/// is visible only when the optional state is non-`nil`.
|
|
/// - elseContent: A view that is only visible when the optional state is `nil`.
|
|
public init<IfContent, ElseContent>(
|
|
_ store: Store<PresentationState<State>, PresentationAction<Action>>,
|
|
@ViewBuilder then ifContent: @escaping (Store<State, Action>) -> IfContent,
|
|
@ViewBuilder else elseContent: @escaping () -> ElseContent
|
|
) where Content == _ConditionalContent<IfContent, ElseContent> {
|
|
self.init(
|
|
store.scope(state: { $0.wrappedValue }, action: PresentationAction.presented),
|
|
then: ifContent,
|
|
else: elseContent
|
|
)
|
|
}
|
|
|
|
/// Initializes an ``IfLetStore`` view that computes content depending on if a store of
|
|
/// ``PresentationState`` and ``PresentationAction`` is `nil` or non-`nil`.
|
|
///
|
|
/// - Parameters:
|
|
/// - store: A store of optional state.
|
|
/// - ifContent: A function that is given a store of non-optional state and returns a view that
|
|
/// is visible only when the optional state is non-`nil`.
|
|
public init<IfContent>(
|
|
_ store: Store<PresentationState<State>, PresentationAction<Action>>,
|
|
@ViewBuilder then ifContent: @escaping (Store<State, Action>) -> IfContent
|
|
) where Content == IfContent? {
|
|
self.init(
|
|
store.scope(state: { $0.wrappedValue }, action: PresentationAction.presented),
|
|
then: ifContent
|
|
)
|
|
}
|
|
|
|
/// Initializes an ``IfLetStore`` view that computes content depending on if a store of
|
|
/// ``PresentationState`` and ``PresentationAction`` is `nil` or non-`nil` and state can further
|
|
/// be extracted from the destination state, _e.g._ it matches a particular case of an enum.
|
|
///
|
|
/// - Parameters:
|
|
/// - store: A store of optional state.
|
|
/// - toState: A closure that attempts to extract state for the "if" branch from the destination
|
|
/// state.
|
|
/// - fromAction: A closure that embeds actions for the "if" branch in destination actions.
|
|
/// - ifContent: A function that is given a store of non-optional state and returns a view that
|
|
/// is visible only when the optional state is non-`nil` and state can be extracted from the
|
|
/// destination state.
|
|
/// - elseContent: A view that is only visible when state cannot be extracted from the
|
|
/// destination.
|
|
public init<DestinationState, DestinationAction, IfContent, ElseContent>(
|
|
_ store: Store<PresentationState<DestinationState>, PresentationAction<DestinationAction>>,
|
|
state toState: @escaping (DestinationState) -> State?,
|
|
action fromAction: @escaping (Action) -> DestinationAction,
|
|
@ViewBuilder then ifContent: @escaping (Store<State, Action>) -> IfContent,
|
|
@ViewBuilder else elseContent: @escaping () -> ElseContent
|
|
) where Content == _ConditionalContent<IfContent, ElseContent> {
|
|
self.init(
|
|
store.scope(
|
|
state: { $0.wrappedValue.flatMap(toState) },
|
|
action: { .presented(fromAction($0)) }
|
|
),
|
|
then: ifContent,
|
|
else: elseContent
|
|
)
|
|
}
|
|
|
|
/// Initializes an ``IfLetStore`` view that computes content depending on if a store of
|
|
/// ``PresentationState`` and ``PresentationAction`` is `nil` or non-`nil` and state can further
|
|
/// be extracted from the destination state, _e.g._ it matches a particular case of an enum.
|
|
///
|
|
/// - Parameters:
|
|
/// - store: A store of optional state.
|
|
/// - toState: A closure that attempts to extract state for the "if" branch from the destination
|
|
/// state.
|
|
/// - fromAction: A closure that embeds actions for the "if" branch in destination actions.
|
|
/// - ifContent: A function that is given a store of non-optional state and returns a view that
|
|
/// is visible only when the optional state is non-`nil` and state can be extracted from the
|
|
/// destination state.
|
|
public init<DestinationState, DestinationAction, IfContent>(
|
|
_ store: Store<PresentationState<DestinationState>, PresentationAction<DestinationAction>>,
|
|
state toState: @escaping (DestinationState) -> State?,
|
|
action fromAction: @escaping (Action) -> DestinationAction,
|
|
@ViewBuilder then ifContent: @escaping (Store<State, Action>) -> IfContent
|
|
) where Content == IfContent? {
|
|
self.init(
|
|
store.scope(
|
|
state: { $0.wrappedValue.flatMap(toState) },
|
|
action: { .presented(fromAction($0)) }
|
|
),
|
|
then: ifContent
|
|
)
|
|
}
|
|
|
|
public var body: some View {
|
|
WithViewStore(
|
|
self.store,
|
|
observe: { $0 },
|
|
removeDuplicates: { ($0 != nil) == ($1 != nil) },
|
|
content: self.content
|
|
)
|
|
}
|
|
}
|