Skip to content

Rename BindableState to BindingState #1855

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jan 20, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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.
"""
Expand All @@ -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 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

// ...
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ The Composable Architecture can be used to power applications built in many fram

- <doc:Bindings>
- ``ViewStore/binding(get:send:)-65xes``
- ``BindableState``
- ``BindingState``
- ``BindableAction``
- ``BindingAction``
- ``BindingReducer``
Expand Down
22 changes: 15 additions & 7 deletions Sources/ComposableArchitecture/Internal/Deprecations.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,14 @@ import Combine
import SwiftUI
import XCTestDynamicOverlay

// 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 {
Expand Down Expand Up @@ -972,7 +980,7 @@ extension ViewStore where ViewAction: BindableAction, ViewAction.State == ViewSt
)
@MainActor
public subscript<Value: Equatable>(
dynamicMember keyPath: WritableKeyPath<ViewState, BindableState<Value>>
dynamicMember keyPath: WritableKeyPath<ViewState, BindingState<Value>>
) -> Binding<Value> {
self.binding(
get: { $0[keyPath: keyPath].wrappedValue },
Expand All @@ -988,8 +996,8 @@ extension BindingAction {
*, deprecated,
message:
"""
For improved safety, bindable properties must now be wrapped explicitly in 'BindableState', \
and accessed via key paths to that 'BindableState', like '\\.$value'
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<Value: Equatable>(
Expand All @@ -1008,8 +1016,8 @@ extension BindingAction {
*, deprecated,
message:
"""
For improved safety, bindable properties must now be wrapped explicitly in 'BindableState', \
and accessed via key paths to that 'BindableState', like '\\.$value'
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 ~= <Value>(
Expand Down Expand Up @@ -1042,8 +1050,8 @@ extension ViewStore {
*, 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' \
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'.
"""
Expand Down
46 changes: 23 additions & 23 deletions Sources/ComposableArchitecture/SwiftUI/Binding.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import SwiftUI
/// Read <doc:Bindings> for more information.
@dynamicMemberLookup
@propertyWrapper
public struct BindableState<Value> {
public struct BindingState<Value> {
/// The underlying value wrapped by the bindable state.
public var wrappedValue: Value

Expand All @@ -23,13 +23,13 @@ public struct BindableState<Value> {
/// 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 }
Expand All @@ -41,17 +41,17 @@ public struct BindableState<Value> {
/// - Returns: A new bindable state.
public subscript<Subject>(
dynamicMember keyPath: WritableKeyPath<Value, Subject>
) -> BindableState<Subject> {
) -> BindingState<Subject> {
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()
Expand All @@ -62,7 +62,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()
Expand All @@ -73,29 +73,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 <doc:Bindings> for more information.
Expand All @@ -116,7 +116,7 @@ extension BindableAction {
///
/// - Returns: A binding action.
public static func set<Value: Equatable>(
_ keyPath: WritableKeyPath<State, BindableState<Value>>,
_ keyPath: WritableKeyPath<State, BindingState<Value>>,
_ value: Value
) -> Self {
self.binding(.set(keyPath, value))
Expand All @@ -129,7 +129,7 @@ extension ViewStore where ViewAction: BindableAction, ViewAction.State == ViewSt
/// - Parameter keyPath: A key path to a specific bindable state.
/// - Returns: A new binding.
public func binding<Value: Equatable>(
_ keyPath: WritableKeyPath<ViewState, BindableState<Value>>,
_ keyPath: WritableKeyPath<ViewState, BindingState<Value>>,
file: StaticString = #file,
fileID: StaticString = #fileID,
line: UInt = #line
Expand Down Expand Up @@ -157,7 +157,7 @@ extension ViewStore where ViewAction: BindableAction, ViewAction.State == ViewSt

/// 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 <doc:Bindings> for more information.
Expand All @@ -180,12 +180,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<Value: Equatable>(
_ keyPath: WritableKeyPath<Root, BindableState<Value>>,
_ keyPath: WritableKeyPath<Root, BindingState<Value>>,
_ value: Value
) -> Self {
return .init(
Expand All @@ -208,14 +208,14 @@ extension BindingAction {
/// // Return an authorization request effect
/// ```
public static func ~= <Value>(
keyPath: WritableKeyPath<Root, BindableState<Value>>,
keyPath: WritableKeyPath<Root, BindingState<Value>>,
bindingAction: Self
) -> Bool {
keyPath == bindingAction.keyPath
}

init<Value: Equatable>(
keyPath: WritableKeyPath<Root, BindableState<Value>>,
keyPath: WritableKeyPath<Root, BindingState<Value>>,
set: @escaping (inout Root) -> Void,
value: Value
) {
Expand All @@ -233,15 +233,15 @@ 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:
///
/// ```swift
/// struct MyFeature: ReducerProtocol {
/// struct State: Equatable {
/// @BindableState var count = 0
/// @BindingState var count = 0
/// var fact: String?
/// ...
/// }
Expand Down Expand Up @@ -279,7 +279,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
/// }
Expand Down
4 changes: 2 additions & 2 deletions Tests/ComposableArchitectureTests/BindingTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,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 = ""
Expand Down
4 changes: 2 additions & 2 deletions Tests/ComposableArchitectureTests/DebugTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@

func testBindingAction() {
struct State {
@BindableState var width = 0
@BindingState var width = 0
}
let action = BindingAction.set(\State.$width, 50)
var dump = ""
Expand All @@ -54,7 +54,7 @@
dump,
#"""
BindingAction.set(
WritableKeyPath<State, BindableState<Int>>,
WritableKeyPath<State, BindingState<Int>>,
50
)
"""#
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -190,7 +190,7 @@
@MainActor
func testBindingUnhandledAction() {
struct State: Equatable {
@BindableState var value = 0
@BindingState var value = 0
}
enum Action: BindableAction, Equatable {
case binding(BindingAction<State>)
Expand Down