diff --git a/Examples/CaseStudies/SwiftUICaseStudies/01-GettingStarted-Bindings-Forms.swift b/Examples/CaseStudies/SwiftUICaseStudies/01-GettingStarted-Bindings-Forms.swift index 45fd0ca13..6222fcaca 100644 --- a/Examples/CaseStudies/SwiftUICaseStudies/01-GettingStarted-Bindings-Forms.swift +++ b/Examples/CaseStudies/SwiftUICaseStudies/01-GettingStarted-Bindings-Forms.swift @@ -8,7 +8,7 @@ private let readMe = """ Bindable state and actions allow you to safely eliminate the boilerplate caused by needing to \ have a unique action for every UI control. Instead, all UI bindings can be consolidated into a \ single `binding` action that holds onto a `BindingAction` value, and all bindable state can be \ - safeguarded with the `BindableState` property wrapper. + safeguarded with the `BindingState` property wrapper. It is instructive to compare this case study to the "Binding Basics" case study. """ @@ -17,10 +17,10 @@ private let readMe = """ struct BindingForm: ReducerProtocol { struct State: Equatable { - @BindableState var sliderValue = 5.0 - @BindableState var stepCount = 10 - @BindableState var text = "" - @BindableState var toggleIsOn = false + @BindingState var sliderValue = 5.0 + @BindingState var stepCount = 10 + @BindingState var text = "" + @BindingState var toggleIsOn = false } enum Action: BindableAction, Equatable { diff --git a/Examples/CaseStudies/SwiftUICaseStudies/01-GettingStarted-FocusState.swift b/Examples/CaseStudies/SwiftUICaseStudies/01-GettingStarted-FocusState.swift index d91aaacf9..505c7bb15 100644 --- a/Examples/CaseStudies/SwiftUICaseStudies/01-GettingStarted-FocusState.swift +++ b/Examples/CaseStudies/SwiftUICaseStudies/01-GettingStarted-FocusState.swift @@ -10,9 +10,9 @@ private let readMe = """ struct FocusDemo: ReducerProtocol { struct State: Equatable { - @BindableState var focusedField: Field? - @BindableState var password: String = "" - @BindableState var username: String = "" + @BindingState var focusedField: Field? + @BindingState var password: String = "" + @BindingState var username: String = "" enum Field: String, Hashable { case username, password diff --git a/Sources/ComposableArchitecture/Documentation.docc/Articles/Bindings.md b/Sources/ComposableArchitecture/Documentation.docc/Articles/Bindings.md index 47b11825a..7a409b98d 100644 --- a/Sources/ComposableArchitecture/Documentation.docc/Articles/Bindings.md +++ b/Sources/ComposableArchitecture/Documentation.docc/Articles/Bindings.md @@ -177,20 +177,20 @@ struct Settings: ReducerProtocol { ``` This is a _lot_ of boilerplate for something that should be simple. Luckily, we can dramatically -eliminate this boilerplate using ``BindableState``, ``BindableAction``, and ``BindingReducer``. +eliminate this boilerplate using ``BindingState``, ``BindableAction``, and ``BindingReducer``. -First, we can annotate each bindable value of state with the ``BindableState`` property wrapper: +First, we can annotate each bindable value of state with the ``BindingState`` property wrapper: ```swift struct Settings: ReducerProtocol { struct State: Equatable { - @BindableState var digest = Digest.daily - @BindableState var displayName = "" - @BindableState var enableNotifications = false + @BindingState var digest = Digest.daily + @BindingState var displayName = "" + @BindingState var enableNotifications = false var isLoading = false - @BindableState var protectMyPosts = false - @BindableState var sendEmailNotifications = false - @BindableState var sendMobileNotifications = false + @BindingState var protectMyPosts = false + @BindingState var sendEmailNotifications = false + @BindingState var sendMobileNotifications = false } // ... diff --git a/Sources/ComposableArchitecture/Documentation.docc/Extensions/SwiftUI.md b/Sources/ComposableArchitecture/Documentation.docc/Extensions/SwiftUI.md index a1f14953c..f95218514 100644 --- a/Sources/ComposableArchitecture/Documentation.docc/Extensions/SwiftUI.md +++ b/Sources/ComposableArchitecture/Documentation.docc/Extensions/SwiftUI.md @@ -19,7 +19,7 @@ The Composable Architecture can be used to power applications built in many fram - - ``ViewStore/binding(get:send:)-65xes`` -- ``BindableState`` +- ``BindingState`` - ``BindableAction`` - ``BindingAction`` - ``BindingReducer`` diff --git a/Sources/ComposableArchitecture/Internal/Deprecations.swift b/Sources/ComposableArchitecture/Internal/Deprecations.swift index b64e9734c..3daab6cf6 100644 --- a/Sources/ComposableArchitecture/Internal/Deprecations.swift +++ b/Sources/ComposableArchitecture/Internal/Deprecations.swift @@ -3,13 +3,21 @@ import ReactiveSwift import XCTestDynamicOverlay #if canImport(SwiftUI) - import SwiftUI +import SwiftUI #endif #if DEBUG && canImport(os) import os #endif +// MARK: - Deprecated after 0.49.2 + +// NB: As of Swift 5.7, property wrapper deprecations are not diagnosed, so we may want to keep this +// deprecation around for now: +// https://github.com/apple/swift/issues/63139 +@available(*, deprecated, renamed: "BindingState") +public typealias BindableState = BindingState + // MARK: - Deprecated after 0.47.2 extension ActorIsolated { @@ -30,16 +38,16 @@ extension ActorIsolated { // MARK: - Deprecated after 0.45.0: #if canImport(SwiftUI) - @available( - *, - deprecated, - message: "Pass 'TextState' to the 'SwiftUI.Text' initializer, instead, e.g., 'Text(textState)'." - ) - extension TextState: View { - public var body: some View { - Text(self) - } +@available( + *, + deprecated, + message: "Pass 'TextState' to the 'SwiftUI.Text' initializer, instead, e.g., 'Text(textState)'." +) +extension TextState: View { + public var body: some View { + Text(self) } +} #endif // MARK: - Deprecated after 0.42.0: @@ -106,416 +114,416 @@ extension ReducerProtocol { // MARK: - Deprecated after 0.40.0: #if canImport(SwiftUI) - @available(iOS 15, macOS 12, tvOS 15, watchOS 8, *) - 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 `ViewState` values are equal. When values - /// are equal, repeat view computations are removed, - /// - content: A function that can generate content from a view store. - @available( - *, - deprecated, - message: - """ +@available(iOS 15, macOS 12, tvOS 15, watchOS 8, *) +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 `ViewState` values are equal. When values + /// are equal, repeat view computations are removed, + /// - content: A function that can generate content from a view store. + @available( + *, + deprecated, + message: + """ For compiler performance, using "WithViewStore" from an accessibility rotor content builder is no longer supported. Extract this "WithViewStore" to the parent view, instead, or observe your view store from an "@ObservedObject" property. See the documentation for "WithViewStore" (https://pointfreeco.github.io/swift-composable-architecture/main/documentation/composablearchitecture/viewstore#overview) for more information. """ + ) + public init( + _ store: Store, + removeDuplicates isDuplicate: @escaping (ViewState, ViewState) -> Bool, + @AccessibilityRotorContentBuilder content: @escaping (ViewStore) -> + Content, + file: StaticString = #fileID, + line: UInt = #line + ) { + self.init( + store: store, + removeDuplicates: isDuplicate, + content: content, + file: file, + line: line ) - public init( - _ store: Store, - removeDuplicates isDuplicate: @escaping (ViewState, ViewState) -> Bool, - @AccessibilityRotorContentBuilder content: @escaping (ViewStore) -> - Content, - file: StaticString = #fileID, - line: UInt = #line - ) { - self.init( - store: store, - removeDuplicates: isDuplicate, - content: content, - file: file, - line: line - ) - } } +} - @available(iOS 15, macOS 12, tvOS 15, watchOS 8, *) - extension WithViewStore where ViewState: 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. - @available( - *, - deprecated, - message: - """ +@available(iOS 15, macOS 12, tvOS 15, watchOS 8, *) +extension WithViewStore where ViewState: 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. + @available( + *, + deprecated, + message: + """ For compiler performance, using "WithViewStore" from an accessibility rotor content builder is no longer supported. Extract this "WithViewStore" to the parent view, instead, or observe your view store from an "@ObservedObject" property. See the documentation for "WithViewStore" (https://pointfreeco.github.io/swift-composable-architecture/main/documentation/composablearchitecture/viewstore#overview) for more information. """ - ) - public init( - _ store: Store, - @AccessibilityRotorContentBuilder content: @escaping (ViewStore) -> - Content, - file: StaticString = #fileID, - line: UInt = #line - ) { - self.init(store, removeDuplicates: ==, content: content, file: file, line: line) - } + ) + public init( + _ store: Store, + @AccessibilityRotorContentBuilder content: @escaping (ViewStore) -> + Content, + file: StaticString = #fileID, + line: UInt = #line + ) { + self.init(store, removeDuplicates: ==, content: content, file: file, line: line) } +} - @available(iOS 15, macOS 12, tvOS 15, watchOS 8, *) - extension WithViewStore where ViewState == 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. - @available( - *, - deprecated, - message: - """ +@available(iOS 15, macOS 12, tvOS 15, watchOS 8, *) +extension WithViewStore where ViewState == 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. + @available( + *, + deprecated, + message: + """ For compiler performance, using "WithViewStore" from an accessibility rotor content builder is no longer supported. Extract this "WithViewStore" to the parent view, instead, or observe your view store from an "@ObservedObject" property. See the documentation for "WithViewStore" (https://pointfreeco.github.io/swift-composable-architecture/main/documentation/composablearchitecture/viewstore#overview) for more information. """ - ) - public init( - _ store: Store, - file: StaticString = #fileID, - line: UInt = #line, - @AccessibilityRotorContentBuilder content: @escaping (ViewStore) -> - Content - ) { - self.init(store, removeDuplicates: ==, content: content, file: file, line: line) - } + ) + public init( + _ store: Store, + file: StaticString = #fileID, + line: UInt = #line, + @AccessibilityRotorContentBuilder content: @escaping (ViewStore) -> + Content + ) { + self.init(store, removeDuplicates: ==, content: content, file: file, line: line) } +} - @available(iOS 14, macOS 11, *) - @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 `ViewState` values are equal. When values - /// are equal, repeat view computations are removed, - /// - content: A function that can generate content from a view store. - @available( - *, - deprecated, - message: - """ +@available(iOS 14, macOS 11, *) +@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 `ViewState` values are equal. When values + /// are equal, repeat view computations are removed, + /// - content: A function that can generate content from a view store. + @available( + *, + deprecated, + message: + """ For compiler performance, using "WithViewStore" from a command builder is no longer supported. Extract this "WithViewStore" to the parent view, instead, or observe your view store from an "@ObservedObject" property. See the documentation for "WithViewStore" (https://pointfreeco.github.io/swift-composable-architecture/main/documentation/composablearchitecture/viewstore#overview) for more information. """ + ) + public init( + _ store: Store, + removeDuplicates isDuplicate: @escaping (ViewState, ViewState) -> Bool, + @CommandsBuilder content: @escaping (ViewStore) -> Content, + file: StaticString = #fileID, + line: UInt = #line + ) { + self.init( + store: store, + removeDuplicates: isDuplicate, + content: content, + file: file, + line: line ) - public init( - _ store: Store, - removeDuplicates isDuplicate: @escaping (ViewState, ViewState) -> Bool, - @CommandsBuilder content: @escaping (ViewStore) -> Content, - file: StaticString = #fileID, - line: UInt = #line - ) { - self.init( - store: store, - removeDuplicates: isDuplicate, - content: content, - file: file, - line: line - ) - } } +} - @available(iOS 14, macOS 11, *) - @available(tvOS, unavailable) - @available(watchOS, unavailable) - extension WithViewStore where ViewState: 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. - @available( - *, - deprecated, - message: - """ +@available(iOS 14, macOS 11, *) +@available(tvOS, unavailable) +@available(watchOS, unavailable) +extension WithViewStore where ViewState: 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. + @available( + *, + deprecated, + message: + """ For compiler performance, using "WithViewStore" from a command builder is no longer supported. Extract this "WithViewStore" to the parent view, instead, or observe your view store from an "@ObservedObject" property. See the documentation for "WithViewStore" (https://pointfreeco.github.io/swift-composable-architecture/main/documentation/composablearchitecture/viewstore#overview) for more information. """ - ) - public init( - _ store: Store, - @CommandsBuilder content: @escaping (ViewStore) -> Content, - file: StaticString = #fileID, - line: UInt = #line - ) { - self.init(store, removeDuplicates: ==, content: content, file: file, line: line) - } + ) + public init( + _ store: Store, + @CommandsBuilder content: @escaping (ViewStore) -> Content, + file: StaticString = #fileID, + line: UInt = #line + ) { + self.init(store, removeDuplicates: ==, content: content, file: file, line: line) } +} - @available(iOS 14, macOS 11, *) - @available(tvOS, unavailable) - @available(watchOS, unavailable) - extension WithViewStore where ViewState == 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. - @available( - *, - deprecated, - message: - """ +@available(iOS 14, macOS 11, *) +@available(tvOS, unavailable) +@available(watchOS, unavailable) +extension WithViewStore where ViewState == 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. + @available( + *, + deprecated, + message: + """ For compiler performance, using "WithViewStore" from a command builder is no longer supported. Extract this "WithViewStore" to the parent view, instead, or observe your view store from an "@ObservedObject" property. See the documentation for "WithViewStore" (https://pointfreeco.github.io/swift-composable-architecture/main/documentation/composablearchitecture/viewstore#overview) for more information. """ - ) - public init( - _ store: Store, - file: StaticString = #fileID, - line: UInt = #line, - @CommandsBuilder content: @escaping (ViewStore) -> Content - ) { - self.init(store, removeDuplicates: ==, content: content, file: file, line: line) - } + ) + public init( + _ store: Store, + file: StaticString = #fileID, + line: UInt = #line, + @CommandsBuilder content: @escaping (ViewStore) -> Content + ) { + self.init(store, removeDuplicates: ==, content: content, file: file, line: line) } +} - @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 `ViewState` values are equal. When values - /// are equal, repeat view computations are removed, - /// - content: A function that can generate content from a view store. - @available( - *, - deprecated, - message: - """ +@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 `ViewState` values are equal. When values + /// are equal, repeat view computations are removed, + /// - content: A function that can generate content from a view store. + @available( + *, + deprecated, + message: + """ For compiler performance, using "WithViewStore" from a scene builder is no longer supported. Extract this "WithViewStore" to the parent view, instead, or observe your view store from an "@ObservedObject" property. See the documentation for "WithViewStore" (https://pointfreeco.github.io/swift-composable-architecture/main/documentation/composablearchitecture/viewstore#overview) for more information. """ + ) + public init( + _ store: Store, + removeDuplicates isDuplicate: @escaping (ViewState, ViewState) -> Bool, + @SceneBuilder content: @escaping (ViewStore) -> Content, + file: StaticString = #fileID, + line: UInt = #line + ) { + self.init( + store: store, + removeDuplicates: isDuplicate, + content: content, + file: file, + line: line ) - public init( - _ store: Store, - removeDuplicates isDuplicate: @escaping (ViewState, ViewState) -> Bool, - @SceneBuilder content: @escaping (ViewStore) -> Content, - file: StaticString = #fileID, - line: UInt = #line - ) { - self.init( - store: store, - removeDuplicates: isDuplicate, - content: content, - file: file, - line: line - ) - } } +} - @available(iOS 14, macOS 11, tvOS 14, watchOS 7, *) - extension WithViewStore where ViewState: 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. - @available( - *, - deprecated, - message: - """ +@available(iOS 14, macOS 11, tvOS 14, watchOS 7, *) +extension WithViewStore where ViewState: 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. + @available( + *, + deprecated, + message: + """ For compiler performance, using "WithViewStore" from a scene builder is no longer supported. Extract this "WithViewStore" to the parent view, instead, or observe your view store from an "@ObservedObject" property. See the documentation for "WithViewStore" (https://pointfreeco.github.io/swift-composable-architecture/main/documentation/composablearchitecture/viewstore#overview) for more information. """ - ) - public init( - _ store: Store, - @SceneBuilder content: @escaping (ViewStore) -> Content, - file: StaticString = #fileID, - line: UInt = #line - ) { - self.init(store, removeDuplicates: ==, content: content, file: file, line: line) - } + ) + public init( + _ store: Store, + @SceneBuilder content: @escaping (ViewStore) -> Content, + file: StaticString = #fileID, + line: UInt = #line + ) { + self.init(store, removeDuplicates: ==, content: content, file: file, line: line) } +} - @available(iOS 14, macOS 11, tvOS 14, watchOS 7, *) - extension WithViewStore where ViewState == 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. - @available( - *, - deprecated, - message: - """ +@available(iOS 14, macOS 11, tvOS 14, watchOS 7, *) +extension WithViewStore where ViewState == 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. + @available( + *, + deprecated, + message: + """ For compiler performance, using "WithViewStore" from a scene builder is no longer supported. Extract this "WithViewStore" to the parent view, instead, or observe your view store from an "@ObservedObject" property. See the documentation for "WithViewStore" (https://pointfreeco.github.io/swift-composable-architecture/main/documentation/composablearchitecture/viewstore#overview) for more information. """ - ) - public init( - _ store: Store, - file: StaticString = #fileID, - line: UInt = #line, - @SceneBuilder content: @escaping (ViewStore) -> Content - ) { - self.init(store, removeDuplicates: ==, content: content, file: file, line: line) - } + ) + public init( + _ store: Store, + file: StaticString = #fileID, + line: UInt = #line, + @SceneBuilder content: @escaping (ViewStore) -> Content + ) { + self.init(store, removeDuplicates: ==, content: content, file: file, line: line) } +} - @available(iOS 14, macOS 11, tvOS 14, watchOS 7, *) - 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 `ViewState` values are equal. When values - /// are equal, repeat view computations are removed, - /// - content: A function that can generate content from a view store. - @available( - *, - deprecated, - message: - """ +@available(iOS 14, macOS 11, tvOS 14, watchOS 7, *) +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 `ViewState` values are equal. When values + /// are equal, repeat view computations are removed, + /// - content: A function that can generate content from a view store. + @available( + *, + deprecated, + message: + """ For compiler performance, using "WithViewStore" from a toolbar content builder is no longer supported. Extract this "WithViewStore" to the parent view, instead, or observe your view store from an "@ObservedObject" property. See the documentation for "WithViewStore" (https://pointfreeco.github.io/swift-composable-architecture/main/documentation/composablearchitecture/viewstore#overview) for more information. """ + ) + public init( + _ store: Store, + removeDuplicates isDuplicate: @escaping (ViewState, ViewState) -> Bool, + file: StaticString = #fileID, + line: UInt = #line, + @ToolbarContentBuilder content: @escaping (ViewStore) -> Content + ) { + self.init( + store: store, + removeDuplicates: isDuplicate, + content: content, + file: file, + line: line ) - public init( - _ store: Store, - removeDuplicates isDuplicate: @escaping (ViewState, ViewState) -> Bool, - file: StaticString = #fileID, - line: UInt = #line, - @ToolbarContentBuilder content: @escaping (ViewStore) -> Content - ) { - self.init( - store: store, - removeDuplicates: isDuplicate, - content: content, - file: file, - line: line - ) - } } +} - @available(iOS 14, macOS 11, tvOS 14, watchOS 7, *) - extension WithViewStore where ViewState: 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. - @available( - *, - deprecated, - message: - """ +@available(iOS 14, macOS 11, tvOS 14, watchOS 7, *) +extension WithViewStore where ViewState: 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. + @available( + *, + deprecated, + message: + """ For compiler performance, using "WithViewStore" from a toolbar content builder is no longer supported. Extract this "WithViewStore" to the parent view, instead, or observe your view store from an "@ObservedObject" property. See the documentation for "WithViewStore" (https://pointfreeco.github.io/swift-composable-architecture/main/documentation/composablearchitecture/viewstore#overview) for more information. """ - ) - public init( - _ store: Store, - file: StaticString = #fileID, - line: UInt = #line, - @ToolbarContentBuilder content: @escaping (ViewStore) -> Content - ) { - self.init(store, removeDuplicates: ==, file: file, line: line, content: content) - } + ) + public init( + _ store: Store, + file: StaticString = #fileID, + line: UInt = #line, + @ToolbarContentBuilder content: @escaping (ViewStore) -> Content + ) { + self.init(store, removeDuplicates: ==, file: file, line: line, content: content) } +} - @available(iOS 14, macOS 11, tvOS 14, watchOS 7, *) - extension WithViewStore where ViewState == 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. - @available( - *, - deprecated, - message: - """ +@available(iOS 14, macOS 11, tvOS 14, watchOS 7, *) +extension WithViewStore where ViewState == 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. + @available( + *, + deprecated, + message: + """ For compiler performance, using "WithViewStore" from a toolbar content builder is no longer supported. Extract this "WithViewStore" to the parent view, instead, or observe your view store from an "@ObservedObject" property. See the documentation for "WithViewStore" (https://pointfreeco.github.io/swift-composable-architecture/main/documentation/composablearchitecture/viewstore#overview) for more information. """ - ) - public init( - _ store: Store, - file: StaticString = #fileID, - line: UInt = #line, - @ToolbarContentBuilder content: @escaping (ViewStore) -> Content - ) { - self.init(store, removeDuplicates: ==, file: file, line: line, content: content) - } + ) + public init( + _ store: Store, + file: StaticString = #fileID, + line: UInt = #line, + @ToolbarContentBuilder content: @escaping (ViewStore) -> Content + ) { + self.init(store, removeDuplicates: ==, file: file, line: line, content: content) } +} #endif // MARK: - Deprecated after 0.39.1: #if canImport(SwiftUI) - extension WithViewStore { - @available(*, deprecated, renamed: "ViewState") - public typealias State = ViewState +extension WithViewStore { + @available(*, deprecated, renamed: "ViewState") + public typealias State = ViewState - @available(*, deprecated, renamed: "ViewAction") - public typealias Action = ViewAction - } + @available(*, deprecated, renamed: "ViewAction") + public typealias Action = ViewAction +} #endif // MARK: - Deprecated after 0.39.0: #if canImport(SwiftUI) - extension CaseLet { - @available(*, deprecated, renamed: "EnumState") - public typealias GlobalState = EnumState +extension CaseLet { + @available(*, deprecated, renamed: "EnumState") + public typealias GlobalState = EnumState - @available(*, deprecated, renamed: "EnumAction") - public typealias GlobalAction = EnumAction + @available(*, deprecated, renamed: "EnumAction") + public typealias GlobalAction = EnumAction - @available(*, deprecated, renamed: "CaseState") - public typealias LocalState = CaseState + @available(*, deprecated, renamed: "CaseState") + public typealias LocalState = CaseState - @available(*, deprecated, renamed: "CaseAction") - public typealias LocalAction = CaseAction - } + @available(*, deprecated, renamed: "CaseAction") + public typealias LocalAction = CaseAction +} #endif extension TestStore { @@ -543,19 +551,19 @@ extension EffectProducer where Failure == Error { var task: Task<(), Never>? let producer = SignalProducer { observer, lifetime in task = Task(priority: priority) { @MainActor in - do { - try Task.checkCancellation() - let output = try await operation() - try Task.checkCancellation() + do { + try Task.checkCancellation() + let output = try await operation() + try Task.checkCancellation() observer.send(value: output) observer.sendCompleted() - } catch is CancellationError { + } catch is CancellationError { observer.sendCompleted() - } catch { + } catch { observer.send(error: error) - } } } + } return producer.on(disposed: task?.cancel) } @@ -886,301 +894,301 @@ extension TestStore where ScopedState: Equatable, Action: Equatable { // MARK: - Deprecated after 0.27.1: #if canImport(SwiftUI) +@available(iOS 13, *) +@available(macOS 12, *) +@available(tvOS 13, *) +@available(watchOS 6, *) +@available(*, deprecated, renamed: "ConfirmationDialogState") +public typealias ActionSheetState = ConfirmationDialogState + +extension View { @available(iOS 13, *) @available(macOS 12, *) @available(tvOS 13, *) @available(watchOS 6, *) - @available(*, deprecated, renamed: "ConfirmationDialogState") - public typealias ActionSheetState = ConfirmationDialogState - - extension View { - @available(iOS 13, *) - @available(macOS 12, *) - @available(tvOS 13, *) - @available(watchOS 6, *) - @available(*, deprecated, renamed: "confirmationDialog") - public func actionSheet( - _ store: Store?, Action>, - dismiss: Action - ) -> some View { - self.confirmationDialog(store, dismiss: dismiss) - } + @available(*, deprecated, renamed: "confirmationDialog") + public func actionSheet( + _ store: Store?, Action>, + dismiss: Action + ) -> some View { + self.confirmationDialog(store, dismiss: dismiss) } +} - extension Store { - @available( - *, deprecated, - message: - """ +extension Store { + @available( + *, deprecated, + message: + """ If you use this method, please open a discussion on GitHub and let us know how: \ https://github.com/pointfreeco/swift-composable-architecture/discussions/new """ - ) + ) public func producerScope( state toChildState: @escaping (SignalProducer) -> SignalProducer< ChildState, Never >, - action fromChildAction: @escaping (ChildAction) -> Action + action fromChildAction: @escaping (ChildAction) -> Action ) -> SignalProducer, Never> { - func extractChildState(_ state: State) -> ChildState? { - var childState: ChildState? + func extractChildState(_ state: State) -> ChildState? { + var childState: ChildState? _ = toChildState(SignalProducer(value: state)) .startWithValues { childState = $0 } - return childState - } + return childState + } return toChildState(self.producer) - .map { childState in + .map { childState in let localStore = Store( - initialState: childState, - reducer: .init { childState, childAction, _ in - let task = self.send(fromChildAction(childAction)) + initialState: childState, + reducer: .init { childState, childAction, _ in + let task = self.send(fromChildAction(childAction)) childState = extractChildState(self.state) ?? childState - if let task = task { - return .fireAndForget { await task.cancellableValue } - } else { - return .none - } - }, - environment: () - ) + if let task = task { + return .fireAndForget { await task.cancellableValue } + } else { + return .none + } + }, + environment: () + ) localStore.parentDisposable = self.producer.startWithValues { [weak localStore] state in guard let localStore = localStore else { return } localStore.state = extractChildState(state) ?? localStore.state } return localStore - } - } + } + } - @available( - *, deprecated, - message: - """ + @available( + *, deprecated, + message: + """ If you use this method, please open a discussion on GitHub and let us know how: \ https://github.com/pointfreeco/swift-composable-architecture/discussions/new """ - ) + ) public func producerScope( state toChildState: @escaping (SignalProducer) -> SignalProducer< ChildState, Never > ) -> SignalProducer, Never> { self.producerScope(state: toChildState, action: { $0 }) - } } +} - extension ViewStore where ViewAction: BindableAction, ViewAction.State == ViewState { - @available( - *, deprecated, - message: - """ +extension ViewStore where ViewAction: BindableAction, ViewAction.State == ViewState { + @available( + *, deprecated, + message: + """ Dynamic member lookup is no longer supported for bindable state. Instead of dot-chaining on \ the view store, e.g. 'viewStore.$value', invoke the 'binding' method on view store with a \ key path to the value, e.g. 'viewStore.binding(\\.$value)'. For more on this change, see: \ https://github.com/pointfreeco/swift-composable-architecture/pull/810 """ + ) + @MainActor + public subscript( + dynamicMember keyPath: WritableKeyPath> + ) -> Binding { + self.binding( + get: { $0[keyPath: keyPath].wrappedValue }, + send: { .binding(.set(keyPath, $0)) } ) - @MainActor - public subscript( - dynamicMember keyPath: WritableKeyPath> - ) -> Binding { - self.binding( - get: { $0[keyPath: keyPath].wrappedValue }, - send: { .binding(.set(keyPath, $0)) } - ) - } } +} - // MARK: - Deprecated after 0.25.0: +// MARK: - Deprecated after 0.25.0: - extension BindingAction { - @available( - *, deprecated, - message: - """ - For improved safety, bindable properties must now be wrapped explicitly in 'BindableState', \ - and accessed via key paths to that 'BindableState', like '\\.$value' +extension BindingAction { + @available( + *, deprecated, + message: + """ + For improved safety, bindable properties must now be wrapped explicitly in 'BindingState', \ + and accessed via key paths to that 'BindingState', like '\\.$value' """ + ) + public static func set( + _ keyPath: WritableKeyPath, + _ value: Value + ) -> Self { + .init( + keyPath: keyPath, + set: { $0[keyPath: keyPath] = value }, + value: value, + valueIsEqualTo: { $0 as? Value == value } ) - public static func set( - _ keyPath: WritableKeyPath, - _ value: Value - ) -> Self { - .init( - keyPath: keyPath, - set: { $0[keyPath: keyPath] = value }, - value: value, - valueIsEqualTo: { $0 as? Value == value } - ) - } + } - @available( - *, deprecated, - message: - """ - For improved safety, bindable properties must now be wrapped explicitly in 'BindableState', \ - and accessed via key paths to that 'BindableState', like '\\.$value' + @available( + *, deprecated, + message: """ - ) - public static func ~= ( - keyPath: WritableKeyPath, - bindingAction: Self - ) -> Bool { - keyPath == bindingAction.keyPath - } + For improved safety, bindable properties must now be wrapped explicitly in 'BindingState', \ + and accessed via key paths to that 'BindingState', like '\\.$value' + """ + ) + public static func ~= ( + keyPath: WritableKeyPath, + bindingAction: Self + ) -> Bool { + keyPath == bindingAction.keyPath } +} - extension AnyReducer { - @available( - *, deprecated, - message: - """ +extension AnyReducer { + @available( + *, deprecated, + message: + """ 'Reducer.binding()' no longer takes an explicit extract function and instead the reducer's \ 'Action' type must conform to 'BindableAction' """ - ) + ) public func binding(action toBindingAction: @escaping (Action) -> BindingAction?) -> Self { - Self { state, action, environment in - toBindingAction(action)?.set(&state) - return self.run(&state, action, environment) - } + Self { state, action, environment in + toBindingAction(action)?.set(&state) + return self.run(&state, action, environment) } } +} #if canImport(SwiftUI) - extension ViewStore { - @available( - *, deprecated, - message: - """ - For improved safety, bindable properties must now be wrapped explicitly in 'BindableState'. \ - Bindings are now derived via 'ViewStore.binding' with a key path to that 'BindableState' \ +extension ViewStore { + @available( + *, deprecated, + message: + """ + For improved safety, bindable properties must now be wrapped explicitly in 'BindingState'. \ + Bindings are now derived via 'ViewStore.binding' with a key path to that 'BindingState' \ (for example, 'viewStore.binding(\\.$value)'). For dynamic member lookup to be available, \ the view store's 'Action' type must also conform to 'BindableAction'. """ - ) - @MainActor - public func binding( - keyPath: WritableKeyPath, - send action: @escaping (BindingAction) -> ViewAction - ) -> Binding { - self.binding( - get: { $0[keyPath: keyPath] }, - send: { action(.set(keyPath, $0)) } - ) - } - } + ) + @MainActor + public func binding( + keyPath: WritableKeyPath, + send action: @escaping (BindingAction) -> ViewAction + ) -> Binding { + self.binding( + get: { $0[keyPath: keyPath] }, + send: { action(.set(keyPath, $0)) } + ) + } +} #endif - // MARK: - Deprecated after 0.20.0: +// MARK: - Deprecated after 0.20.0: - extension AnyReducer { - @available(*, deprecated, message: "Use the 'IdentifiedArray'-based version, instead.") - public func forEach( - state toElementsState: WritableKeyPath, - action toElementAction: CasePath, - environment toElementEnvironment: @escaping (ParentEnvironment) -> Environment, - breakpointOnNil: Bool = true, - file: StaticString = #file, - fileID: StaticString = #fileID, - line: UInt = #line - ) -> AnyReducer { - .init { parentState, parentAction, parentEnvironment in - guard let (index, action) = toElementAction.extract(from: parentAction) else { - return .none - } - if index >= parentState[keyPath: toElementsState].endIndex { - runtimeWarn( - """ - A "forEach" reducer at "\(fileID):\(line)" received an action when state contained no \ - element at that index. … - - Action: - \(debugCaseOutput(action)) - Index: - \(index) - - This is generally considered an application logic error, and can happen for a few \ - reasons: - - • This "forEach" reducer was combined with or run from another reducer that removed \ - the element at this index when it handled this action. To fix this make sure that this \ - "forEach" reducer is run before any other reducers that can move or remove elements \ - from state. This ensures that "forEach" reducers can handle their actions for the \ - element at the intended index. - - • An in-flight effect emitted this action while state contained no element at this \ - index. While it may be perfectly reasonable to ignore this action, you may want to \ - cancel the associated effect when moving or removing an element. If your "forEach" \ - reducer returns any long-living effects, you should use the identifier-based "forEach" \ - instead. - - • This action was sent to the store while its state contained no element at this index \ - To fix this make sure that actions for this reducer can only be sent to a view store \ - when its state contains an element at this index. In SwiftUI applications, use \ - "ForEachStore". - """, - file: file, - line: line - ) - return .none - } - return self.run( - &parentState[keyPath: toElementsState][index], - action, - toElementEnvironment(parentEnvironment) +extension AnyReducer { + @available(*, deprecated, message: "Use the 'IdentifiedArray'-based version, instead.") + public func forEach( + state toElementsState: WritableKeyPath, + action toElementAction: CasePath, + environment toElementEnvironment: @escaping (ParentEnvironment) -> Environment, + breakpointOnNil: Bool = true, + file: StaticString = #file, + fileID: StaticString = #fileID, + line: UInt = #line + ) -> AnyReducer { + .init { parentState, parentAction, parentEnvironment in + guard let (index, action) = toElementAction.extract(from: parentAction) else { + return .none + } + if index >= parentState[keyPath: toElementsState].endIndex { + runtimeWarn( + """ + A "forEach" reducer at "\(fileID):\(line)" received an action when state contained no \ + element at that index. … + + Action: + \(debugCaseOutput(action)) + Index: + \(index) + + This is generally considered an application logic error, and can happen for a few \ + reasons: + + • This "forEach" reducer was combined with or run from another reducer that removed \ + the element at this index when it handled this action. To fix this make sure that this \ + "forEach" reducer is run before any other reducers that can move or remove elements \ + from state. This ensures that "forEach" reducers can handle their actions for the \ + element at the intended index. + + • An in-flight effect emitted this action while state contained no element at this \ + index. While it may be perfectly reasonable to ignore this action, you may want to \ + cancel the associated effect when moving or removing an element. If your "forEach" \ + reducer returns any long-living effects, you should use the identifier-based "forEach" \ + instead. + + • This action was sent to the store while its state contained no element at this index \ + To fix this make sure that actions for this reducer can only be sent to a view store \ + when its state contains an element at this index. In SwiftUI applications, use \ + "ForEachStore". + """, + file: file, + line: line ) - .map { toElementAction.embed((index, $0)) } + return .none } + return self.run( + &parentState[keyPath: toElementsState][index], + action, + toElementEnvironment(parentEnvironment) + ) + .map { toElementAction.embed((index, $0)) } } } +} - extension ForEachStore { - @available(*, deprecated, message: "Use the 'IdentifiedArray'-based version, instead.") - public init( - _ store: Store, - id: KeyPath, - @ViewBuilder content: @escaping (Store) -> EachContent - ) - where - Data == [EachState], - Content == WithViewStore< - [ID], (Data.Index, EachAction), ForEach<[(offset: Int, element: ID)], ID, EachContent> - > - { +extension ForEachStore { + @available(*, deprecated, message: "Use the 'IdentifiedArray'-based version, instead.") + public init( + _ store: Store, + id: KeyPath, + @ViewBuilder content: @escaping (Store) -> EachContent + ) + where + Data == [EachState], + Content == WithViewStore< + [ID], (Data.Index, EachAction), ForEach<[(offset: Int, element: ID)], ID, EachContent> + > + { let data = store.state - self.data = data + self.data = data self.content = WithViewStore(store.scope(state: { $0.map { $0[keyPath: id] } })) { viewStore in - ForEach(Array(viewStore.state.enumerated()), id: \.element) { index, _ in - content( - store.scope( - state: { index < $0.endIndex ? $0[index] : data[index] }, - action: { (index, $0) } - ) + ForEach(Array(viewStore.state.enumerated()), id: \.element) { index, _ in + content( + store.scope( + state: { index < $0.endIndex ? $0[index] : data[index] }, + action: { (index, $0) } ) - } + ) } } + } - @available(*, deprecated, message: "Use the 'IdentifiedArray'-based version, instead.") - public init( - _ store: Store, - @ViewBuilder content: @escaping (Store) -> EachContent - ) - where - Data == [EachState], - Content == WithViewStore< - [ID], (Data.Index, EachAction), ForEach<[(offset: Int, element: ID)], ID, EachContent> - >, - EachState: Identifiable, - EachState.ID == ID - { - self.init(store, id: \.id, content: content) - } + @available(*, deprecated, message: "Use the 'IdentifiedArray'-based version, instead.") + public init( + _ store: Store, + @ViewBuilder content: @escaping (Store) -> EachContent + ) + where + Data == [EachState], + Content == WithViewStore< + [ID], (Data.Index, EachAction), ForEach<[(offset: Int, element: ID)], ID, EachContent> + >, + EachState: Identifiable, + EachState.ID == ID + { + self.init(store, id: \.id, content: content) } +} #endif diff --git a/Sources/ComposableArchitecture/SwiftUI/Binding.swift b/Sources/ComposableArchitecture/SwiftUI/Binding.swift index 2f04eed16..f932991d5 100644 --- a/Sources/ComposableArchitecture/SwiftUI/Binding.swift +++ b/Sources/ComposableArchitecture/SwiftUI/Binding.swift @@ -1,7 +1,7 @@ import CustomDump #if canImport(SwiftUI) - import SwiftUI +import SwiftUI #endif /// A property wrapper type that can designate properties of app state that can be directly bindable @@ -14,7 +14,7 @@ import CustomDump /// Read for more information. @dynamicMemberLookup @propertyWrapper -public struct BindableState { +public struct BindingState { /// The underlying value wrapped by the bindable state. public var wrappedValue: Value @@ -26,13 +26,13 @@ public struct BindableState { /// A projection that can be used to derive bindings from a view store. /// /// Use the projected value to derive bindings from a view store with properties annotated with - /// `@BindableState`. To get the `projectedValue`, prefix the property with `$`: + /// `@BindingState`. To get the `projectedValue`, prefix the property with `$`: /// /// ```swift /// TextField("Display name", text: viewStore.binding(\.$displayName)) /// ``` /// - /// See ``BindableState`` for more details. + /// See ``BindingState`` for more details. public var projectedValue: Self { get { self } set { self = newValue } @@ -44,17 +44,17 @@ public struct BindableState { /// - Returns: A new bindable state. public subscript( dynamicMember keyPath: WritableKeyPath - ) -> BindableState { + ) -> BindingState { get { .init(wrappedValue: self.wrappedValue[keyPath: keyPath]) } set { self.wrappedValue[keyPath: keyPath] = newValue.wrappedValue } } } -extension BindableState: Equatable where Value: Equatable {} +extension BindingState: Equatable where Value: Equatable {} -extension BindableState: Hashable where Value: Hashable {} +extension BindingState: Hashable where Value: Hashable {} -extension BindableState: Decodable where Value: Decodable { +extension BindingState: Decodable where Value: Decodable { public init(from decoder: Decoder) throws { do { let container = try decoder.singleValueContainer() @@ -65,7 +65,7 @@ extension BindableState: Decodable where Value: Decodable { } } -extension BindableState: Encodable where Value: Encodable { +extension BindingState: Encodable where Value: Encodable { public func encode(to encoder: Encoder) throws { do { var container = encoder.singleValueContainer() @@ -76,29 +76,29 @@ extension BindableState: Encodable where Value: Encodable { } } -extension BindableState: CustomReflectable { +extension BindingState: CustomReflectable { public var customMirror: Mirror { Mirror(reflecting: self.wrappedValue) } } -extension BindableState: CustomDumpRepresentable { +extension BindingState: CustomDumpRepresentable { public var customDumpValue: Any { self.wrappedValue } } -extension BindableState: CustomDebugStringConvertible where Value: CustomDebugStringConvertible { +extension BindingState: CustomDebugStringConvertible where Value: CustomDebugStringConvertible { public var debugDescription: String { self.wrappedValue.debugDescription } } -extension BindableState: Sendable where Value: Sendable {} +extension BindingState: Sendable where Value: Sendable {} /// An action type that exposes a `binding` case that holds a ``BindingAction``. /// -/// Used in conjunction with ``BindableState`` to safely eliminate the boilerplate typically +/// Used in conjunction with ``BindingState`` to safely eliminate the boilerplate typically /// associated with mutating multiple fields in state. /// /// Read for more information. @@ -119,7 +119,7 @@ extension BindableAction { /// /// - Returns: A binding action. public static func set( - _ keyPath: WritableKeyPath>, + _ keyPath: WritableKeyPath>, _ value: Value ) -> Self { self.binding(.set(keyPath, value)) @@ -127,42 +127,42 @@ extension BindableAction { } #if canImport(SwiftUI) - extension ViewStore where ViewAction: BindableAction, ViewAction.State == ViewState { - /// Returns a binding to the resulting bindable state of a given key path. - /// - /// - Parameter keyPath: A key path to a specific bindable state. - /// - Returns: A new binding. - public func binding( - _ keyPath: WritableKeyPath>, - file: StaticString = #file, - fileID: StaticString = #fileID, - line: UInt = #line - ) -> Binding { - self.binding( - get: { $0[keyPath: keyPath].wrappedValue }, - send: { value in - #if DEBUG - let debugger = BindableActionViewStoreDebugger( - value: value, bindableActionType: ViewAction.self, file: file, fileID: fileID, - line: line - ) - let set: (inout ViewState) -> Void = { - $0[keyPath: keyPath].wrappedValue = value - debugger.wasCalled = true - } - #else - let set: (inout ViewState) -> Void = { $0[keyPath: keyPath].wrappedValue = value } - #endif - return .binding(.init(keyPath: keyPath, set: set, value: value)) - } - ) - } +extension ViewStore where ViewAction: BindableAction, ViewAction.State == ViewState { + /// Returns a binding to the resulting bindable state of a given key path. + /// + /// - Parameter keyPath: A key path to a specific bindable state. + /// - Returns: A new binding. + public func binding( + _ keyPath: WritableKeyPath>, + file: StaticString = #file, + fileID: StaticString = #fileID, + line: UInt = #line + ) -> Binding { + self.binding( + get: { $0[keyPath: keyPath].wrappedValue }, + send: { value in + #if DEBUG + let debugger = BindableActionViewStoreDebugger( + value: value, bindableActionType: ViewAction.self, file: file, fileID: fileID, + line: line + ) + let set: (inout ViewState) -> Void = { + $0[keyPath: keyPath].wrappedValue = value + debugger.wasCalled = true + } + #else + let set: (inout ViewState) -> Void = { $0[keyPath: keyPath].wrappedValue = value } + #endif + return .binding(.init(keyPath: keyPath, set: set, value: value)) + } + ) } +} #endif /// An action that describes simple mutations to some root state at a writable key path. /// -/// Used in conjunction with ``BindableState`` and ``BindableAction`` to safely eliminate the +/// Used in conjunction with ``BindingState`` and ``BindableAction`` to safely eliminate the /// boilerplate typically associated with mutating multiple fields in state. /// /// Read for more information. @@ -185,12 +185,12 @@ extension BindingAction { /// /// - Parameters: /// - keyPath: A key path to the property that should be mutated. This property must be - /// annotated with the ``BindableState`` property wrapper. + /// annotated with the ``BindingState`` property wrapper. /// - value: A value to assign at the given key path. /// - Returns: An action that describes simple mutations to some root state at a writable key /// path. public static func set( - _ keyPath: WritableKeyPath>, + _ keyPath: WritableKeyPath>, _ value: Value ) -> Self { return .init( @@ -213,14 +213,14 @@ extension BindingAction { /// // Return an authorization request effect /// ``` public static func ~= ( - keyPath: WritableKeyPath>, + keyPath: WritableKeyPath>, bindingAction: Self ) -> Bool { keyPath == bindingAction.keyPath } init( - keyPath: WritableKeyPath>, + keyPath: WritableKeyPath>, set: @escaping (inout Root) -> Void, value: Value ) { @@ -238,7 +238,7 @@ extension BindingAction { /// key path. /// /// Useful in transforming binding actions on view state into binding actions on reducer state - /// when the domain contains ``BindableState`` and ``BindableAction``. + /// when the domain contains ``BindingState`` and ``BindableAction``. /// /// For example, we can model an feature that can bind an integer count to a stepper and make a /// network request to fetch a fact about that integer with the following domain: @@ -246,7 +246,7 @@ extension BindingAction { /// ```swift /// struct MyFeature: ReducerProtocol { /// struct State: Equatable { - /// @BindableState var count = 0 + /// @BindingState var count = 0 /// var fact: String? /// ... /// } @@ -284,7 +284,7 @@ extension BindingAction { /// ```swift /// extension MyFeatureView { /// struct ViewState: Equatable { - /// @BindableState var count: Int + /// @BindingState var count: Int /// let fact: String? /// // no access to any other state on `MyFeature.State`, like child domains /// } diff --git a/Tests/ComposableArchitectureTests/BindingTests.swift b/Tests/ComposableArchitectureTests/BindingTests.swift index d3d452b3e..dcd225de9 100644 --- a/Tests/ComposableArchitectureTests/BindingTests.swift +++ b/Tests/ComposableArchitectureTests/BindingTests.swift @@ -5,10 +5,10 @@ import XCTest @MainActor final class BindingTests: XCTestCase { #if swift(>=5.7) - func testNestedBindableState() { + func testNestedBindingState() { struct BindingTest: ReducerProtocol { struct State: Equatable { - @BindableState var nested = Nested() + @BindingState var nested = Nested() struct Nested: Equatable { var field = "" diff --git a/Tests/ComposableArchitectureTests/DebugTests.swift b/Tests/ComposableArchitectureTests/DebugTests.swift index fa6df953d..fb258936a 100644 --- a/Tests/ComposableArchitectureTests/DebugTests.swift +++ b/Tests/ComposableArchitectureTests/DebugTests.swift @@ -44,23 +44,23 @@ } #if canImport(SwiftUI) - func testBindingAction() { - struct State { - @BindableState var width = 0 - } - let action = BindingAction.set(\State.$width, 50) - var dump = "" - customDump(action, to: &dump) - XCTAssertEqual( - dump, - #""" - BindingAction.set( - WritableKeyPath>, - 50 - ) - """# - ) + func testBindingAction() { + struct State { + @BindingState var width = 0 } + let action = BindingAction.set(\State.$width, 50) + var dump = "" + customDump(action, to: &dump) + XCTAssertEqual( + dump, + #""" + BindingAction.set( + WritableKeyPath>, + 50 + ) + """# + ) + } #endif @MainActor diff --git a/Tests/ComposableArchitectureTests/RuntimeWarningTests.swift b/Tests/ComposableArchitectureTests/RuntimeWarningTests.swift index 88b5d8eca..4e69f2912 100644 --- a/Tests/ComposableArchitectureTests/RuntimeWarningTests.swift +++ b/Tests/ComposableArchitectureTests/RuntimeWarningTests.swift @@ -196,7 +196,7 @@ @MainActor func testBindingUnhandledAction() { struct State: Equatable { - @BindableState var value = 0 + @BindingState var value = 0 } enum Action: BindableAction, Equatable { case binding(BindingAction)