Skip to content

Commit 04c1a82

Browse files
committed
FormAction → BindingAction (#372)
* Rename FormAction to BindingAction * Deprecations * Fix test * Update 01-GettingStarted-Bindings-Forms.swift
1 parent e9e27ce commit 04c1a82

File tree

3 files changed

+61
-58
lines changed

3 files changed

+61
-58
lines changed

Examples/CaseStudies/SwiftUICaseStudies/01-GettingStarted-Bindings-Forms.swift

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,12 @@ import ComposableArchitecture
22
import SwiftUI
33

44
private let readMe = """
5-
This file demonstrates how to handle two-way bindings in the Composable Architecture using form \
6-
actions.
5+
This file demonstrates how to handle two-way bindings in the Composable Architecture using \
6+
binding actions.
77
8-
Form actions allow you to eliminate the boilerplate caused by needing to have a unique action \
9-
for every UI control. Instead, all UI bindings can be consolidated into a single `form` action \
10-
that holds onto a `FormAction` value.
8+
Binding actions allow you to eliminate the boilerplate caused by needing to have a unique action \
9+
for every UI control. Instead, all UI bindings can be consolidated into a single `binding` \
10+
action that holds onto a `BindingAction` value.
1111
1212
It is instructive to compare this case study to the "Binding Basics" case study.
1313
"""
@@ -21,7 +21,7 @@ struct BindingFormState: Equatable {
2121
}
2222

2323
enum BindingFormAction: Equatable {
24-
case form(FormAction<BindingFormState>)
24+
case binding(BindingAction<BindingFormState>)
2525
case resetButtonTapped
2626
}
2727

@@ -32,19 +32,19 @@ let bindingFormReducer = Reducer<
3232
> {
3333
state, action, _ in
3434
switch action {
35-
case .form(\.stepCount):
35+
case .binding(\.stepCount):
3636
state.sliderValue = .minimum(state.sliderValue, Double(state.stepCount))
3737
return .none
3838

39-
case .form:
39+
case .binding:
4040
return .none
4141

4242
case .resetButtonTapped:
4343
state = .init()
4444
return .none
4545
}
4646
}
47-
.form(action: /BindingFormAction.form)
47+
.binding(action: /BindingFormAction.binding)
4848

4949
struct BindingFormView: View {
5050
let store: Store<BindingFormState, BindingFormAction>
@@ -56,20 +56,20 @@ struct BindingFormView: View {
5656
HStack {
5757
TextField(
5858
"Type here",
59-
text: viewStore.binding(keyPath: \.text, send: BindingFormAction.form)
59+
text: viewStore.binding(keyPath: \.text, send: BindingFormAction.binding)
6060
)
6161
.disableAutocorrection(true)
6262
.foregroundColor(viewStore.toggleIsOn ? .gray : .primary)
6363
Text(alternate(viewStore.text))
6464
}
6565
.disabled(viewStore.toggleIsOn)
6666

67-
Toggle(isOn: viewStore.binding(keyPath: \.toggleIsOn, send: BindingFormAction.form)) {
67+
Toggle(isOn: viewStore.binding(keyPath: \.toggleIsOn, send: BindingFormAction.binding)) {
6868
Text("Disable other controls")
6969
}
7070

7171
Stepper(
72-
value: viewStore.binding(keyPath: \.stepCount, send: BindingFormAction.form),
72+
value: viewStore.binding(keyPath: \.stepCount, send: BindingFormAction.binding),
7373
in: 0...100
7474
) {
7575
Text("Max slider value: \(viewStore.stepCount)")
@@ -81,7 +81,7 @@ struct BindingFormView: View {
8181
Text("Slider value: \(Int(viewStore.sliderValue))")
8282
.font(Font.body.monospacedDigit())
8383
Slider(
84-
value: viewStore.binding(keyPath: \.sliderValue, send: BindingFormAction.form),
84+
value: viewStore.binding(keyPath: \.sliderValue, send: BindingFormAction.binding),
8585
in: 0...Double(viewStore.stepCount)
8686
)
8787
}

Examples/CaseStudies/SwiftUICaseStudiesTests/01-GettingStarted-BindingBasicsTests.swift

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,17 +13,17 @@ class BindingFormTests: XCTestCase {
1313
)
1414

1515
store.assert(
16-
.send(.form(.set(\.sliderValue, 2))) {
16+
.send(.binding(.set(\.sliderValue, 2))) {
1717
$0.sliderValue = 2
1818
},
19-
.send(.form(.set(\.stepCount, 1))) {
19+
.send(.binding(.set(\.stepCount, 1))) {
2020
$0.sliderValue = 1
2121
$0.stepCount = 1
2222
},
23-
.send(.form(.set(\.text, "Blob"))) {
23+
.send(.binding(.set(\.text, "Blob"))) {
2424
$0.text = "Blob"
2525
},
26-
.send(.form(.set(\.toggleIsOn, true))) {
26+
.send(.binding(.set(\.toggleIsOn, true))) {
2727
$0.toggleIsOn = true
2828
},
2929
.send(.resetButtonTapped) {

Sources/ComposableArchitecture/SwiftUI/Forms.swift renamed to Sources/ComposableArchitecture/SwiftUI/Binding.swift

Lines changed: 44 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -66,49 +66,49 @@ import SwiftUI
6666
/// }
6767
///
6868
/// This is a _lot_ of boilerplate for something that should be simple. Luckily, we can dramatically
69-
/// eliminate this boilerplate using `FormAction`. First, we can collapse all of these
70-
/// field-mutating actions into a single case that holds a `FormAction` generic over the reducer's
71-
/// root `SettingsState`:
69+
/// eliminate this boilerplate using `BindingAction`. First, we can collapse all of these
70+
/// field-mutating actions into a single case that holds a `BindingAction` generic over the
71+
/// reducer's root `SettingsState`:
7272
///
7373
/// enum SettingsAction {
74-
/// case form(FormAction<SettingsState>)
74+
/// case binding(BindingAction<SettingsState>)
7575
/// }
7676
///
77-
/// And then, we can simplify the settings reducer by allowing the `form` method to handle these
77+
/// And then, we can simplify the settings reducer by allowing the `binding` method to handle these
7878
/// field mutations for us:
7979
///
8080
/// let settingsReducer = Reducer<
8181
/// SettingsState, SettingsAction, SettingsEnvironment
8282
/// > {
8383
/// switch action {
84-
/// case .form:
84+
/// case .binding:
8585
/// return .none
8686
/// }
8787
/// }
88-
/// .form(action: /SettingsAction.form)
88+
/// .binding(action: /SettingsAction.binding)
8989
///
90-
/// Form actions are constructed and sent to the store by providing a writable key path from root
90+
/// Binding actions are constructed and sent to the store by providing a writable key path from root
9191
/// state to the field being mutated. There is even a view store helper that simplifies this work.
92-
/// You can derive a binding by specifying the key path and form action case:
92+
/// You can derive a binding by specifying the key path and binding action case:
9393
///
9494
/// TextField(
9595
/// "Display name",
96-
/// text: viewStore.binding(keyPath: \.displayName, send: SettingsAction.form)
96+
/// text: viewStore.binding(keyPath: \.displayName, send: SettingsAction.binding)
9797
/// )
9898
///
99-
/// Should you need to layer additional functionality over your form, your reducer can pattern match
100-
/// the form action for a given key path:
99+
/// Should you need to layer additional functionality over these bindings, your reducer can pattern
100+
/// match the action for a given key path:
101101
///
102-
/// case .form(\.displayName):
102+
/// case .binding(\.displayName):
103103
/// // Validate display name
104104
///
105-
/// case .form(\.enableNotifications):
105+
/// case .binding(\.enableNotifications):
106106
/// // Return an authorization request effect
107107
///
108-
/// Form actions can also be tested in much the same way regular actions are tested. Rather than
108+
/// Binding actions can also be tested in much the same way regular actions are tested. Rather than
109109
/// send a specific action describing how a binding changed, such as `displayNameChanged("Blob")`,
110-
/// you will send a `.form` action that describes which key path is being set to what value, such
111-
/// as `.form(.set(\.displayName, "Blob"))`:
110+
/// you will send a `.binding` action that describes which key path is being set to what value, such
111+
/// as `.binding(.set(\.displayName, "Blob"))`:
112112
///
113113
/// let store = TestStore(
114114
/// initialState: SettingsState(),
@@ -117,15 +117,15 @@ import SwiftUI
117117
/// )
118118
///
119119
/// store.assert(
120-
/// .send(.form(.set(\.displayName, "Blob"))) {
120+
/// .send(.binding(.set(\.displayName, "Blob"))) {
121121
/// $0.displayName = "Blob"
122122
/// },
123-
/// .send(.form(.set(\.protectMyPosts, true))) {
123+
/// .send(.binding(.set(\.protectMyPosts, true))) {
124124
/// $0.protectMyPosts = true
125125
/// )
126126
/// )
127127
///
128-
public struct FormAction<Root>: Equatable {
128+
public struct BindingAction<Root>: Equatable {
129129
public let keyPath: PartialKeyPath<Root>
130130

131131
fileprivate let set: (inout Root) -> Void
@@ -152,12 +152,14 @@ public struct FormAction<Root>: Equatable {
152152
)
153153
}
154154

155-
/// Transforms a form action over some root state to some other type of root state given a key
155+
/// Transforms a binding action over some root state to some other type of root state given a key
156156
/// path.
157157
///
158158
/// - Parameter keyPath: A key path from a new type of root state to the original root state.
159-
/// - Returns: A form action over a new type of root state.
160-
public func pullback<NewRoot>(_ keyPath: WritableKeyPath<NewRoot, Root>) -> FormAction<NewRoot> {
159+
/// - Returns: A binding action over a new type of root state.
160+
public func pullback<NewRoot>(
161+
_ keyPath: WritableKeyPath<NewRoot, Root>
162+
) -> BindingAction<NewRoot> {
161163
.init(
162164
keyPath: (keyPath as AnyKeyPath).appending(path: self.keyPath) as! PartialKeyPath<NewRoot>,
163165
set: { self.set(&$0[keyPath: keyPath]) },
@@ -172,38 +174,39 @@ public struct FormAction<Root>: Equatable {
172174

173175
public static func ~= <Value>(
174176
keyPath: WritableKeyPath<Root, Value>,
175-
formAction: FormAction<Root>
177+
bindingAction: Self
176178
) -> Bool {
177-
keyPath == formAction.keyPath
179+
keyPath == bindingAction.keyPath
178180
}
179181
}
180182

181183
extension Reducer {
182-
/// Returns a reducer that applies `FormAction` mutations to `State` before running this reducer's
183-
/// logic.
184+
/// Returns a reducer that applies `BindingAction` mutations to `State` before running this
185+
/// reducer's logic.
184186
///
185-
/// For example, a settings screen may gather its form actions into a single `FormAction` case:
187+
/// For example, a settings screen may gather its binding actions into a single `BindingAction`
188+
/// case:
186189
///
187190
/// enum SettingsAction {
188191
/// ...
189-
/// case form(FormAction<SettingsState>)
192+
/// case binding(BindingAction<SettingsState>)
190193
/// }
191194
///
192195
/// The reducer can then be enhanced to automatically handle these mutations for you by tacking on
193-
/// the `form` method:
196+
/// the `binding` method:
194197
///
195198
/// let settingsReducer = Reducer<SettingsState, SettingsAction, SettingsEnvironment {
196199
/// ...
197200
/// }
198-
/// .form(action: /SettingsAction.form)
201+
/// .binding(action: /SettingsAction.binding)
199202
///
200-
/// - Parameter toFormAction: A case path from this reducer's `Action` type to a `FormAction` over
201-
/// this reducer's `State`.
202-
/// - Returns: A reducer that applies `FormAction` mutations to `State` before running this
203+
/// - Parameter toBindingAction: A case path from this reducer's `Action` type to a
204+
/// `BindingAction` over this reducer's `State`.
205+
/// - Returns: A reducer that applies `BindingAction` mutations to `State` before running this
203206
/// reducer's logic.
204-
public func form(action toFormAction: CasePath<Action, FormAction<State>>) -> Self {
207+
public func binding(action toBindingAction: CasePath<Action, BindingAction<State>>) -> Self {
205208
Self { state, action, environment in
206-
toFormAction.extract(from: action)?.set(&state)
209+
toBindingAction.extract(from: action)?.set(&state)
207210
return .none
208211
}
209212
.combined(with: self)
@@ -212,26 +215,26 @@ extension Reducer {
212215

213216
extension ViewStore {
214217
/// Derives a binding from the store that mutates state at the given writable key path by wrapping
215-
/// a `FormAction` with the store's action type.
218+
/// a `BindingAction` with the store's action type.
216219
///
217220
/// For example, a text field binding can be created like this:
218221
///
219222
/// struct State { var text = "" }
220-
/// enum Action { case form(FormAction<State>) }
223+
/// enum Action { case binding(BindingAction<State>) }
221224
///
222225
/// TextField(
223226
/// "Enter text",
224-
/// text: viewStore.binding(keyPath: \.text, Action.form)
227+
/// text: viewStore.binding(keyPath: \.text, Action.binding)
225228
/// )
226229
///
227230
/// - Parameters:
228231
/// - keyPath: A writable key path from the view store's state to a mutable field
229-
/// - action: A function that wraps a form action in the view store's action type.
232+
/// - action: A function that wraps a binding action in the view store's action type.
230233
/// - Returns: A binding.
231234
@available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *)
232235
public func binding<LocalState>(
233236
keyPath: WritableKeyPath<State, LocalState>,
234-
send action: @escaping (FormAction<State>) -> Action
237+
send action: @escaping (BindingAction<State>) -> Action
235238
) -> Binding<LocalState>
236239
where LocalState: Equatable {
237240
self.binding(

0 commit comments

Comments
 (0)