Files
swift-composable-architectu…/Sources/ComposableArchitecture/SwiftUI/PresentationModifier.swift
Brandon Williams 2c93195c23 Prerelease 1.0 (#1929)
* 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

* fix some warnings

* docs

* wip

* wip

* Catch some typos in Articles (#2088)

* wip

* wip

* wip

* 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

* wip

* fix

* wip

* 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

* 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

* wip

* move docs around

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* Add case subscripts

* wip

* wip

* wip

* 5.7-only

* wip

* wip

* wip

* wip

* fix

* 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

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* Rename ReducerProtocol.swift to Reducer.swift (#2206)

* Hard-deprecate old SwitchStore initializers/overloads

* wip

* wip

* Resolve CaseStudies crash (#2258)

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* Bump timeout for CI

* wip

* wip

---------

Co-authored-by: Jackson Utsch <jutechs@gmail.com>
Co-authored-by: Stephen Celis <stephen@stephencelis.com>
Co-authored-by: 유재호 <y73447jh@gmail.com>
Co-authored-by: Dmytro <barabashdmyto@gmail.com>
Co-authored-by: mbrandonw <mbrandonw@users.noreply.github.com>
2023-07-27 17:35:07 -07:00

264 lines
8.1 KiB
Swift

import SwiftUI
extension View {
@_spi(Presentation)
public func presentation<State, Action, Content: View>(
store: Store<PresentationState<State>, PresentationAction<Action>>,
@ViewBuilder body: @escaping (
_ content: Self,
_ isPresented: Binding<Bool>,
_ destination: DestinationContent<State, Action>
) -> Content
) -> some View {
self.presentation(store: store) { `self`, $item, destination in
body(self, $item.isPresent(), destination)
}
}
@_disfavoredOverload
@_spi(Presentation)
public func presentation<State, Action, Content: View>(
store: Store<PresentationState<State>, PresentationAction<Action>>,
@ViewBuilder body: @escaping (
_ content: Self,
_ item: Binding<AnyIdentifiable?>,
_ destination: DestinationContent<State, Action>
) -> Content
) -> some View {
self.presentation(
store: store,
state: { $0 },
id: { $0.wrappedValue.map { _ in ObjectIdentifier(State.self) } },
action: { $0 },
body: body
)
}
@_spi(Presentation)
public func presentation<
State,
Action,
DestinationState,
DestinationAction,
Content: View
>(
store: Store<PresentationState<State>, PresentationAction<Action>>,
state toDestinationState: @escaping (_ state: State) -> DestinationState?,
action fromDestinationAction: @escaping (_ destinationAction: DestinationAction) -> Action,
@ViewBuilder body: @escaping (
_ content: Self,
_ isPresented: Binding<Bool>,
_ destination: DestinationContent<DestinationState, DestinationAction>
) -> Content
) -> some View {
self.presentation(
store: store, state: toDestinationState, action: fromDestinationAction
) { `self`, $item, destination in
body(self, $item.isPresent(), destination)
}
}
@_disfavoredOverload
@_spi(Presentation)
public func presentation<
State,
Action,
DestinationState,
DestinationAction,
Content: View
>(
store: Store<PresentationState<State>, PresentationAction<Action>>,
state toDestinationState: @escaping (_ state: State) -> DestinationState?,
action fromDestinationAction: @escaping (_ destinationAction: DestinationAction) -> Action,
@ViewBuilder body: @escaping (
_ content: Self,
_ item: Binding<AnyIdentifiable?>,
_ destination: DestinationContent<DestinationState, DestinationAction>
) -> Content
) -> some View {
self.presentation(
store: store,
state: toDestinationState,
id: { $0.id },
action: fromDestinationAction,
body: body
)
}
@_spi(Presentation)
@ViewBuilder
public func presentation<
State,
Action,
DestinationState,
DestinationAction,
Content: View
>(
store: Store<PresentationState<State>, PresentationAction<Action>>,
state toDestinationState: @escaping (State) -> DestinationState?,
id toID: @escaping (PresentationState<State>) -> AnyHashable?,
action fromDestinationAction: @escaping (DestinationAction) -> Action,
@ViewBuilder body: @escaping (
Self,
Binding<AnyIdentifiable?>,
DestinationContent<DestinationState, DestinationAction>
) -> Content
) -> some View {
PresentationStore(
store, state: toDestinationState, id: toID, action: fromDestinationAction
) { $item, destination in
body(self, $item, destination)
}
}
}
@_spi(Presentation)
public struct PresentationStore<
State, Action, DestinationState, DestinationAction, Content: View
>: View {
let store: Store<PresentationState<State>, PresentationAction<Action>>
let toDestinationState: (State) -> DestinationState?
let toID: (PresentationState<State>) -> AnyHashable?
let fromDestinationAction: (DestinationAction) -> Action
let content:
(
Binding<AnyIdentifiable?>,
DestinationContent<DestinationState, DestinationAction>
) -> Content
@ObservedObject var viewStore: ViewStore<PresentationState<State>, PresentationAction<Action>>
public init(
_ store: Store<PresentationState<State>, PresentationAction<Action>>,
@ViewBuilder content: @escaping (
_ isPresented: Binding<Bool>,
_ destination: DestinationContent<DestinationState, DestinationAction>
) -> Content
) where State == DestinationState, Action == DestinationAction {
self.init(store) { $item, destination in
content($item.isPresent(), destination)
}
}
@_disfavoredOverload
public init(
_ store: Store<PresentationState<State>, PresentationAction<Action>>,
@ViewBuilder content: @escaping (
_ item: Binding<AnyIdentifiable?>,
_ destination: DestinationContent<DestinationState, DestinationAction>
) -> Content
) where State == DestinationState, Action == DestinationAction {
self.init(
store,
state: { $0 },
action: { $0 },
content: content
)
}
public init(
_ store: Store<PresentationState<State>, PresentationAction<Action>>,
state toDestinationState: @escaping (_ state: State) -> DestinationState?,
action fromDestinationAction: @escaping (_ destinationAction: DestinationAction) -> Action,
@ViewBuilder content: @escaping (
_ isPresented: Binding<Bool>,
_ destination: DestinationContent<DestinationState, DestinationAction>
) -> Content
) {
self.init(
store, state: toDestinationState, action: fromDestinationAction
) { $item, destination in
content($item.isPresent(), destination)
}
}
@_disfavoredOverload
public init(
_ store: Store<PresentationState<State>, PresentationAction<Action>>,
state toDestinationState: @escaping (_ state: State) -> DestinationState?,
action fromDestinationAction: @escaping (_ destinationAction: DestinationAction) -> Action,
@ViewBuilder content: @escaping (
_ item: Binding<AnyIdentifiable?>,
_ destination: DestinationContent<DestinationState, DestinationAction>
) -> Content
) {
self.init(
store,
state: toDestinationState,
id: { $0.id },
action: fromDestinationAction,
content: content
)
}
fileprivate init<ID: Hashable>(
_ store: Store<PresentationState<State>, PresentationAction<Action>>,
state toDestinationState: @escaping (State) -> DestinationState?,
id toID: @escaping (PresentationState<State>) -> ID?,
action fromDestinationAction: @escaping (DestinationAction) -> Action,
content: @escaping (
_ item: Binding<AnyIdentifiable?>,
_ destination: DestinationContent<DestinationState, DestinationAction>
) -> Content
) {
let store = store.invalidate { $0.wrappedValue.flatMap(toDestinationState) == nil }
let viewStore = ViewStore(store, observe: { $0 }, removeDuplicates: { toID($0) == toID($1) })
self.store = store
self.toDestinationState = toDestinationState
self.toID = toID
self.fromDestinationAction = fromDestinationAction
self.content = content
self.viewStore = viewStore
}
public var body: some View {
let id = self.toID(self.viewStore.state)
self.content(
self.viewStore.binding(
get: {
$0.wrappedValue.flatMap(toDestinationState) != nil
? toID($0).map { AnyIdentifiable(Identified($0) { $0 }) }
: nil
},
compactSend: {
guard
$0 == nil,
self.viewStore.wrappedValue != nil,
id == nil || self.toID(self.viewStore.state) == id
else { return nil }
return .dismiss
}
),
DestinationContent(
store: self.store.scope(
state: { $0.wrappedValue.flatMap(self.toDestinationState) },
action: { .presented(fromDestinationAction($0)) }
)
)
)
}
}
@_spi(Presentation)
public struct AnyIdentifiable: Identifiable {
public let id: AnyHashable
public init<Base: Identifiable>(_ base: Base) {
self.id = base.id
}
}
@_spi(Presentation)
public struct DestinationContent<State, Action> {
let store: Store<State?, Action>
public func callAsFunction<Content: View>(
@ViewBuilder _ body: @escaping (_ store: Store<State, Action>) -> Content
) -> some View {
IfLetStore(
self.store.scope(state: returningLastNonNilValue { $0 }, action: { $0 }), then: body
)
}
}