[SwiftUI][TCA] binding
概要
この記事ではTCA初心者の筆者が理解を深めていくために、
pointfreeco
公式のサンプルアプリを基に理解しやすく整理していきます。
今回はbindingの使用例を整理して理解していきます。
前回のpullback・combine・scopeについてはこちら
今回扱うファイル
今回は公式サンプルの以下の2つのファイルです。
Bindings-Basics
Bindings-Forms
Bindings-Basics
ここではbinding
を使用した双方向のバインディングについての基礎を学びます。
実装ではTextFieldやToggle、StepperやSlider、
これらを動かした時にAction
を送るだけでなく、
動かした状態もAction
に引数として持たせてState
を更新しています。
State,Action
これまでと異なるのがActionに引数を持たせています。
struct BindingBasicsState: Equatable {
var sliderValue = 5.0
var stepCount = 10
var text = ""
var toggleIsOn = false
}
enum BindingBasicsAction {
case sliderValueChanged(Double) //Actionに引数を持たせる
case stepCountChanged(Int)
case textChanged(String)
case toggleChanged(isOn: Bool)
}
Reducer
Actionの引数の値でStateを更新します。
let bindingBasicsReducer = Reducer<BindingBasicsState, BindingBasicsAction, BindingBasicsEnvironment> {
state, action, _ in
switch action {
case let .sliderValueChanged(value):
state.sliderValue = value
return .none
case let .stepCountChanged(count):
state.sliderValue = .minimum(state.sliderValue, Double(count))
state.stepCount = count
return .none
case let .textChanged(text):
state.text = text // Actionの引数の値でStateを更新
return .none
case let .toggleChanged(isOn):
state.toggleIsOn = isOn
return .none
}
}
View,Store
ここのポイントは、binding
を使用している、
viewStore.binding(get: \.text, send: BindingBasicsAction.textChanged)
です。
TextFieldで入力された値をget
に、
send
でこれまでと同じようにAction
に送っています。
他のTextField以外のコンポーネントも同様です。
struct BindingBasicsView: View {
let store: Store<BindingBasicsState, BindingBasicsAction>
var body: some View {
WithViewStore(self.store) { viewStore in
Form {
Section(header: Text(template: readMe, .caption)) {
HStack {
TextField(
"Type here",
// ここのget,setのパラメータで双方向のバインディング
text: viewStore.binding(get: \.text, send: BindingBasicsAction.textChanged)
)
.disableAutocorrection(true)
.foregroundColor(viewStore.toggleIsOn ? .gray : .primary)
Text(alternate(viewStore.text))
}
.disabled(viewStore.toggleIsOn)
Toggle(
isOn: viewStore.binding(get: \.toggleIsOn, send: BindingBasicsAction.toggleChanged)
) {
Text("Disable other controls")
}
Stepper(
value: viewStore.binding(
get: \.stepCount, send: BindingBasicsAction.stepCountChanged),
in: 0...100
) {
Text("Max slider value: \(viewStore.stepCount)")
.font(.body.monospacedDigit())
}
.disabled(viewStore.toggleIsOn)
HStack {
Text("Slider value: \(Int(viewStore.sliderValue))")
.font(.body.monospacedDigit())
Slider(
value: viewStore.binding(
get: \.sliderValue,
send: BindingBasicsAction.sliderValueChanged
),
in: 0...Double(viewStore.stepCount)
)
}
.disabled(viewStore.toggleIsOn)
}
}
}
.navigationBarTitle("Bindings basics")
}
}
Bindings-Forms
ここではBindableState
というState
の定義をすることで、
BindingAction
というAction
を使用し、
binding
の処理をより簡潔にすることを学びます。
前回のBindings-Basicsとの比較で分かりやすく整理していきます。
State,Action
Bindings-Basicsと異なるのは、
BindableState
のアノテーションを付与している点と、
同じ機能なのにAction
のケースが少なくなっている点です。
BindableAction
を継承し、
BindingAction
にBindingFormStateを持つことで一括りで保持出来ます。
struct BindingFormState: Equatable {
@BindableState var sliderValue = 5.0
@BindableState var stepCount = 10
@BindableState var text = ""
@BindableState var toggleIsOn = false
}
enum BindingFormAction: BindableAction, Equatable {
case binding(BindingAction<BindingFormState>)
case resetButtonTapped
}
struct BindingBasicsState: Equatable {
var sliderValue = 5.0
var stepCount = 10
var text = ""
var toggleIsOn = false
}
enum BindingBasicsAction {
case sliderValueChanged(Double)
case stepCountChanged(Int)
case textChanged(String)
case toggleChanged(isOn: Bool)
}
Reducer
Reducer
でBindings-Basicsと異なるのは、
まず各機能のアクションケースごとにState
を更新する必要が無い点と、
最後に.binding()
というメソッドを使用していることです。
Reducer
のロジックを実行する前に、
State
に対してBindingAction
の変更を適用する為のReducer
を返します。
簡単に整理するとBindingAction
を使用する場合は必須なメソッドのようです。
let bindingFormReducer = Reducer<
BindingFormState, BindingFormAction, BindingFormEnvironment> {
state, action, _ in
switch action {
case .binding(\.$stepCount):
state.sliderValue = .minimum(state.sliderValue, Double(state.stepCount))
return .none
case .binding:
return .none
case .resetButtonTapped:
state = .init()
return .none
}
}
.binding() // Stateに対してBindingActionの変更を適用するReducerを返す
View,Store
ここでBindings-Basicsと異なるのは、
各機能の実行アクションのところです。
例えばTextFieldの部分を見てみます。
Bindings-Basicsではget
とsend
でbindingしてましたが、
今回の場合はこれだけで済みます。
viewStore.binding(\.$text)
struct BindingFormView: View {
let store: Store<BindingFormState, BindingFormAction>
var body: some View {
WithViewStore(self.store) { viewStore in
Form {
Section(header: Text(template: readMe, .caption)) {
HStack {
TextField("Type here", text: viewStore.binding(\.$text))
.disableAutocorrection(true)
.foregroundColor(viewStore.toggleIsOn ? .gray : .primary)
Text(alternate(viewStore.text))
}
.disabled(viewStore.toggleIsOn)
// ...
struct BindingBasicsView: View {
let store: Store<BindingBasicsState, BindingBasicsAction>
var body: some View {
WithViewStore(self.store) { viewStore in
Form {
Section(header: Text(template: readMe, .caption)) {
HStack {
TextField(
"Type here",
text: viewStore.binding(get: \.text, send: BindingBasicsAction.textChanged)
)
.disableAutocorrection(true)
.foregroundColor(viewStore.toggleIsOn ? .gray : .primary)
Text(alternate(viewStore.text))
}
.disabled(viewStore.toggleIsOn)
// ...
次回
次回は様々なStateの使い方について理解していきます。
記事はこちら
Discussion