mirror of
https://github.com/pointfreeco/swift-composable-architecture.git
synced 2025-12-20 09:11:33 +01:00
* Conform `WithViewStore` to `Commands` * Fix typo * Add coverage for `Commands` * Fix documentation * Conform `WithViewStore` to `AccessibilityRotorContent` and `ToolbarContent` * Conform `WithViewStore` to `TableColumnContent` * Fix indentation * Conform `WithViewStore` to `TableRowContent` * Fix Typo * Conform `WithViewStore` to `DynamicTableRowContent` * Reorder declarations according to the protocol name * Add coverage for `WithViewStore` new conformances * Remove Table-related conformances * Rearrange extensions for better autocomplete. Co-authored-by: Brandon Williams <mbrandonw@hey.com>
428 lines
14 KiB
Swift
428 lines
14 KiB
Swift
import Combine
|
|
import CustomDump
|
|
import SwiftUI
|
|
|
|
/// A structure that transforms a store into an observable view store in order to compute views from
|
|
/// store state.
|
|
public struct WithViewStore<State, Action, Content> {
|
|
private let content: (ViewStore<State, Action>) -> Content
|
|
#if DEBUG
|
|
private let file: StaticString
|
|
private let line: UInt
|
|
private var prefix: String?
|
|
private var previousState: (State) -> State?
|
|
#endif
|
|
@ObservedObject private var viewStore: ViewStore<State, Action>
|
|
|
|
fileprivate init(
|
|
store: Store<State, Action>,
|
|
removeDuplicates isDuplicate: @escaping (State, State) -> Bool,
|
|
file: StaticString = #fileID,
|
|
line: UInt = #line,
|
|
content: @escaping (ViewStore<State, Action>) -> Content
|
|
) {
|
|
self.content = content
|
|
#if DEBUG
|
|
self.file = file
|
|
self.line = line
|
|
var previousState: State? = nil
|
|
self.previousState = { currentState in
|
|
defer { previousState = currentState }
|
|
return previousState
|
|
}
|
|
#endif
|
|
self.viewStore = ViewStore(store, removeDuplicates: isDuplicate)
|
|
}
|
|
|
|
/// Prints debug information to the console whenever the view is computed.
|
|
///
|
|
/// - Parameter prefix: A string with which to prefix all debug messages.
|
|
/// - Returns: A structure that prints debug messages for all computations.
|
|
public func debug(_ prefix: String = "") -> Self {
|
|
var view = self
|
|
#if DEBUG
|
|
view.prefix = prefix
|
|
#endif
|
|
return view
|
|
}
|
|
|
|
fileprivate var _body: Content {
|
|
#if DEBUG
|
|
if let prefix = self.prefix {
|
|
var stateDump = ""
|
|
customDump(self.viewStore.state, to: &stateDump, indent: 2)
|
|
let difference =
|
|
self.previousState(self.viewStore.state)
|
|
.map {
|
|
diff($0, self.viewStore.state).map { "(Changed state)\n\($0)" }
|
|
?? "(No difference in state detected)"
|
|
}
|
|
?? "(Initial state)\n\(stateDump)"
|
|
func typeName(_ type: Any.Type) -> String {
|
|
var name = String(reflecting: type)
|
|
if let index = name.firstIndex(of: ".") {
|
|
name.removeSubrange(...index)
|
|
}
|
|
return name
|
|
}
|
|
print(
|
|
"""
|
|
\(prefix.isEmpty ? "" : "\(prefix): ")\
|
|
WithViewStore<\(typeName(State.self)), \(typeName(Action.self)), _>\
|
|
@\(self.file):\(self.line) \(difference)
|
|
"""
|
|
)
|
|
}
|
|
#endif
|
|
return self.content(ViewStore(self.viewStore))
|
|
}
|
|
}
|
|
|
|
// MARK: - View
|
|
extension WithViewStore: View where Content: View {
|
|
/// Initializes a structure that transforms a store into an observable view store in order to
|
|
/// compute views from store state.
|
|
///
|
|
/// - Parameters:
|
|
/// - store: A store.
|
|
/// - isDuplicate: A function to determine when two `State` values are equal. When values are
|
|
/// equal, repeat view computations are removed,
|
|
/// - content: A function that can generate content from a view store.
|
|
public init(
|
|
_ store: Store<State, Action>,
|
|
removeDuplicates isDuplicate: @escaping (State, State) -> Bool,
|
|
file: StaticString = #fileID,
|
|
line: UInt = #line,
|
|
@ViewBuilder content: @escaping (ViewStore<State, Action>) -> Content
|
|
) {
|
|
self.init(
|
|
store: store,
|
|
removeDuplicates: isDuplicate,
|
|
file: file,
|
|
line: line,
|
|
content: content
|
|
)
|
|
}
|
|
|
|
public var body: Content {
|
|
self._body
|
|
}
|
|
}
|
|
|
|
extension WithViewStore where State: Equatable, Content: View {
|
|
/// Initializes a structure that transforms a store into an observable view store in order to
|
|
/// compute views from equatable store state.
|
|
///
|
|
/// - Parameters:
|
|
/// - store: A store of equatable state.
|
|
/// - content: A function that can generate content from a view store.
|
|
public init(
|
|
_ store: Store<State, Action>,
|
|
file: StaticString = #fileID,
|
|
line: UInt = #line,
|
|
@ViewBuilder content: @escaping (ViewStore<State, Action>) -> Content
|
|
) {
|
|
self.init(store, removeDuplicates: ==, file: file, line: line, content: content)
|
|
}
|
|
}
|
|
|
|
extension WithViewStore where State == Void, Content: View {
|
|
/// Initializes a structure that transforms a store into an observable view store in order to
|
|
/// compute views from void store state.
|
|
///
|
|
/// - Parameters:
|
|
/// - store: A store of equatable state.
|
|
/// - content: A function that can generate content from a view store.
|
|
public init(
|
|
_ store: Store<State, Action>,
|
|
file: StaticString = #fileID,
|
|
line: UInt = #line,
|
|
@ViewBuilder content: @escaping (ViewStore<State, Action>) -> Content
|
|
) {
|
|
self.init(store, removeDuplicates: ==, file: file, line: line, content: content)
|
|
}
|
|
}
|
|
|
|
extension WithViewStore: DynamicViewContent where State: Collection, Content: DynamicViewContent {
|
|
public typealias Data = State
|
|
|
|
public var data: State {
|
|
self.viewStore.state
|
|
}
|
|
}
|
|
|
|
// MARK: - AccessibilityRotorContent
|
|
@available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *)
|
|
extension WithViewStore: AccessibilityRotorContent where Content: AccessibilityRotorContent {
|
|
/// Initializes a structure that transforms a store into an observable view store in order to
|
|
/// compute accessibility rotor content from store state.
|
|
///
|
|
/// - Parameters:
|
|
/// - store: A store.
|
|
/// - isDuplicate: A function to determine when two `State` values are equal. When values are
|
|
/// equal, repeat view computations are removed,
|
|
/// - content: A function that can generate content from a view store.
|
|
public init(
|
|
_ store: Store<State, Action>,
|
|
removeDuplicates isDuplicate: @escaping (State, State) -> Bool,
|
|
file: StaticString = #fileID,
|
|
line: UInt = #line,
|
|
@AccessibilityRotorContentBuilder content: @escaping (ViewStore<State, Action>) -> Content
|
|
) {
|
|
self.init(
|
|
store: store,
|
|
removeDuplicates: isDuplicate,
|
|
file: file,
|
|
line: line,
|
|
content: content
|
|
)
|
|
}
|
|
public var body: some AccessibilityRotorContent {
|
|
self._body
|
|
}
|
|
}
|
|
|
|
@available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *)
|
|
extension WithViewStore where State: Equatable, Content: AccessibilityRotorContent {
|
|
/// Initializes a structure that transforms a store into an observable view store in order to
|
|
/// compute accessibility rotor content from equatable store state.
|
|
///
|
|
/// - Parameters:
|
|
/// - store: A store of equatable state.
|
|
/// - content: A function that can generate content from a view store.
|
|
public init(
|
|
_ store: Store<State, Action>,
|
|
file: StaticString = #fileID,
|
|
line: UInt = #line,
|
|
@AccessibilityRotorContentBuilder content: @escaping (ViewStore<State, Action>) -> Content
|
|
) {
|
|
self.init(store, removeDuplicates: ==, file: file, line: line, content: content)
|
|
}
|
|
}
|
|
|
|
@available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *)
|
|
extension WithViewStore where State == Void, Content: AccessibilityRotorContent {
|
|
/// Initializes a structure that transforms a store into an observable view store in order to
|
|
/// compute accessibility rotor content from void store state.
|
|
///
|
|
/// - Parameters:
|
|
/// - store: A store of equatable state.
|
|
/// - content: A function that can generate content from a view store.
|
|
public init(
|
|
_ store: Store<State, Action>,
|
|
file: StaticString = #fileID,
|
|
line: UInt = #line,
|
|
@AccessibilityRotorContentBuilder content: @escaping (ViewStore<State, Action>) -> Content
|
|
) {
|
|
self.init(store, removeDuplicates: ==, file: file, line: line, content: content)
|
|
}
|
|
}
|
|
|
|
// MARK: - Commands
|
|
@available(iOS 14.0, macOS 11.0, *)
|
|
@available(tvOS, unavailable)
|
|
@available(watchOS, unavailable)
|
|
extension WithViewStore: Commands where Content: Commands {
|
|
/// Initializes a structure that transforms a store into an observable view store in order to
|
|
/// compute commands from store state.
|
|
///
|
|
/// - Parameters:
|
|
/// - store: A store.
|
|
/// - isDuplicate: A function to determine when two `State` values are equal. When values are
|
|
/// equal, repeat view computations are removed,
|
|
/// - content: A function that can generate content from a view store.
|
|
public init(
|
|
_ store: Store<State, Action>,
|
|
removeDuplicates isDuplicate: @escaping (State, State) -> Bool,
|
|
file: StaticString = #fileID,
|
|
line: UInt = #line,
|
|
@CommandsBuilder content: @escaping (ViewStore<State, Action>) -> Content
|
|
) {
|
|
self.init(
|
|
store: store,
|
|
removeDuplicates: isDuplicate,
|
|
file: file,
|
|
line: line,
|
|
content: content
|
|
)
|
|
}
|
|
public var body: some Commands {
|
|
self._body
|
|
}
|
|
}
|
|
|
|
@available(iOS 14.0, macOS 11.0, *)
|
|
@available(tvOS, unavailable)
|
|
@available(watchOS, unavailable)
|
|
extension WithViewStore where State: Equatable, Content: Commands {
|
|
/// Initializes a structure that transforms a store into an observable view store in order to
|
|
/// compute commands from equatable store state.
|
|
///
|
|
/// - Parameters:
|
|
/// - store: A store of equatable state.
|
|
/// - content: A function that can generate content from a view store.
|
|
public init(
|
|
_ store: Store<State, Action>,
|
|
file: StaticString = #fileID,
|
|
line: UInt = #line,
|
|
@CommandsBuilder content: @escaping (ViewStore<State, Action>) -> Content
|
|
) {
|
|
self.init(store, removeDuplicates: ==, file: file, line: line, content: content)
|
|
}
|
|
}
|
|
|
|
@available(iOS 14.0, macOS 11.0, *)
|
|
@available(tvOS, unavailable)
|
|
@available(watchOS, unavailable)
|
|
extension WithViewStore where State == Void, Content: Commands {
|
|
/// Initializes a structure that transforms a store into an observable view store in order to
|
|
/// compute commands from void store state.
|
|
///
|
|
/// - Parameters:
|
|
/// - store: A store of equatable state.
|
|
/// - content: A function that can generate content from a view store.
|
|
public init(
|
|
_ store: Store<State, Action>,
|
|
file: StaticString = #fileID,
|
|
line: UInt = #line,
|
|
@CommandsBuilder content: @escaping (ViewStore<State, Action>) -> Content
|
|
) {
|
|
self.init(store, removeDuplicates: ==, file: file, line: line, content: content)
|
|
}
|
|
}
|
|
|
|
// MARK: - Scene
|
|
@available(iOS 14, macOS 11, tvOS 14, watchOS 7, *)
|
|
extension WithViewStore: Scene where Content: Scene {
|
|
/// Initializes a structure that transforms a store into an observable view store in order to
|
|
/// compute scenes from store state.
|
|
///
|
|
/// - Parameters:
|
|
/// - store: A store.
|
|
/// - isDuplicate: A function to determine when two `State` values are equal. When values are
|
|
/// equal, repeat view computations are removed,
|
|
/// - content: A function that can generate content from a view store.
|
|
public init(
|
|
_ store: Store<State, Action>,
|
|
removeDuplicates isDuplicate: @escaping (State, State) -> Bool,
|
|
file: StaticString = #fileID,
|
|
line: UInt = #line,
|
|
@SceneBuilder content: @escaping (ViewStore<State, Action>) -> Content
|
|
) {
|
|
self.init(
|
|
store: store,
|
|
removeDuplicates: isDuplicate,
|
|
file: file,
|
|
line: line,
|
|
content: content
|
|
)
|
|
}
|
|
|
|
public var body: Content {
|
|
self._body
|
|
}
|
|
}
|
|
|
|
@available(iOS 14, macOS 11, tvOS 14, watchOS 7, *)
|
|
extension WithViewStore where State: Equatable, Content: Scene {
|
|
/// Initializes a structure that transforms a store into an observable view store in order to
|
|
/// compute scenes from equatable store state.
|
|
///
|
|
/// - Parameters:
|
|
/// - store: A store of equatable state.
|
|
/// - content: A function that can generate content from a view store.
|
|
public init(
|
|
_ store: Store<State, Action>,
|
|
file: StaticString = #fileID,
|
|
line: UInt = #line,
|
|
@SceneBuilder content: @escaping (ViewStore<State, Action>) -> Content
|
|
) {
|
|
self.init(store, removeDuplicates: ==, file: file, line: line, content: content)
|
|
}
|
|
}
|
|
|
|
@available(iOS 14, macOS 11, tvOS 14, watchOS 7, *)
|
|
extension WithViewStore where State == Void, Content: Scene {
|
|
/// Initializes a structure that transforms a store into an observable view store in order to
|
|
/// compute scenes from void store state.
|
|
///
|
|
/// - Parameters:
|
|
/// - store: A store of equatable state.
|
|
/// - content: A function that can generate content from a view store.
|
|
public init(
|
|
_ store: Store<State, Action>,
|
|
file: StaticString = #fileID,
|
|
line: UInt = #line,
|
|
@SceneBuilder content: @escaping (ViewStore<State, Action>) -> Content
|
|
) {
|
|
self.init(store, removeDuplicates: ==, file: file, line: line, content: content)
|
|
}
|
|
}
|
|
|
|
// MARK: - ToolbarContent
|
|
@available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *)
|
|
extension WithViewStore: ToolbarContent where Content: ToolbarContent {
|
|
/// Initializes a structure that transforms a store into an observable view store in order to
|
|
/// compute toolbar content from store state.
|
|
///
|
|
/// - Parameters:
|
|
/// - store: A store.
|
|
/// - isDuplicate: A function to determine when two `State` values are equal. When values are
|
|
/// equal, repeat view computations are removed,
|
|
/// - content: A function that can generate content from a view store.
|
|
public init(
|
|
_ store: Store<State, Action>,
|
|
removeDuplicates isDuplicate: @escaping (State, State) -> Bool,
|
|
file: StaticString = #fileID,
|
|
line: UInt = #line,
|
|
@ToolbarContentBuilder content: @escaping (ViewStore<State, Action>) -> Content
|
|
) {
|
|
self.init(
|
|
store: store,
|
|
removeDuplicates: isDuplicate,
|
|
file: file,
|
|
line: line,
|
|
content: content
|
|
)
|
|
}
|
|
public var body: some ToolbarContent {
|
|
self._body
|
|
}
|
|
}
|
|
|
|
@available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *)
|
|
extension WithViewStore where State: Equatable, Content: ToolbarContent {
|
|
/// Initializes a structure that transforms a store into an observable view store in order to
|
|
/// compute toolbar content from equatable store state.
|
|
///
|
|
/// - Parameters:
|
|
/// - store: A store of equatable state.
|
|
/// - content: A function that can generate content from a view store.
|
|
public init(
|
|
_ store: Store<State, Action>,
|
|
file: StaticString = #fileID,
|
|
line: UInt = #line,
|
|
@ToolbarContentBuilder content: @escaping (ViewStore<State, Action>) -> Content
|
|
) {
|
|
self.init(store, removeDuplicates: ==, file: file, line: line, content: content)
|
|
}
|
|
}
|
|
|
|
@available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *)
|
|
extension WithViewStore where State == Void, Content: ToolbarContent {
|
|
/// Initializes a structure that transforms a store into an observable view store in order to
|
|
/// compute toolbar content from void store state.
|
|
///
|
|
/// - Parameters:
|
|
/// - store: A store of equatable state.
|
|
/// - content: A function that can generate content from a view store.
|
|
public init(
|
|
_ store: Store<State, Action>,
|
|
file: StaticString = #fileID,
|
|
line: UInt = #line,
|
|
@ToolbarContentBuilder content: @escaping (ViewStore<State, Action>) -> Content
|
|
) {
|
|
self.init(store, removeDuplicates: ==, file: file, line: line, content: content)
|
|
}
|
|
}
|