TCA環境にてTextFieldの文字数制限が思ったようにできませんでした。ワークアラウンドがとりあえず動いているので紹介します。
直面した問題
- フォーカスのあるTextFieldに対してReducerから値を設定しても更新されない。
具体的に次のようなコードでは更新されませんでした。
(環境:TCA 1.5.1 / Xcode 15.0.1 / iOS 17.0.1)
@BindingState
を使っていますがviewStoreのbinding(get:send:)
でバイディングしたり@BindingViewState
を挟んでみたりしても解決しませんでした。
◾️対処前
@Reducer
struct BindingBasics {
struct State: Equatable {
@BindingState var text = ""
}
enum Action: BindableAction, Equatable {
case binding(BindingAction<State>)
}
var body: some Reducer<State, Action> {
BindingReducer()
Reduce { state, action in
switch action {
case .binding(\.$text):
state.text = String(state.text.prefix(5)) // フォーカスのあるTextFieldには反映されない
return .none
case .binding:
return .none
}
}
}
}
struct BindingBasicsView: View {
@State var store = Store(initialState: BindingBasics.State()) {
BindingBasics()
}
var body: some View {
WithViewStore(self.store, observe: { $0 }) { viewStore in
Form {
TextField( "Type here", text: viewStore.$text )
}
}
}
}
対処
TCAを使わずTextFieldを@State
にバインディングしていれば更新できていたので、TextFieldはTCAのStateではなく@State
にバインディングしておき、TCAのStateと@State
で値の同期をとることで目的の動作にさせることができました。
-
bind(_:to:)
でTCAのStateと@State
の同期をとる -
onChange(of:initial:_:)
で文字を加工し@State
の値を更新 -
@State
の初期化は onAppear→Reducer→Stateを経由
◾️対処後
@Reducer
struct BindingBasics {
struct State: Equatable {
@BindingState var text = ""
}
enum Action: BindableAction, Equatable {
case onAppear
case binding(BindingAction<State>)
}
var body: some Reducer<State, Action> {
BindingReducer()
Reduce { state, action in
switch action {
case .onAppear:
state.text = "初期値" // 間接的にViewの @Stateを初期化
return .none
case .binding:
return .none
}
}
}
}
struct BindingBasicsView: View {
@State var store = Store(initialState: BindingBasics.State()) {
BindingBasics()
}
@State var text: String = ""
var body: some View {
WithViewStore(self.store, observe: { $0 }) { viewStore in
Form {
TextField( "Type here", text: $text )
}
.bind(viewStore.$text, to: self.$text) // TCAのStateと @State を同期
.onAppear {
viewStore.send(.onAppear)
}
.onChange(of: text) {
text = String(text.prefix(5)) // 致し方なくここで制限をかける
}
}
}
}
最後に
ご覧のとおり「文字数制約をかけるロジックがReducerではなくViewにある」という問題があります。いくつか他の方法を探りましたが、結局「ReducerでStateを更新する」というアプローチではうまくいきませんでした。
多分、ほかに筋の良い対処方法があるのだと思いますが、その情報に辿り着けませんでした。
原因や対処方法など、ご指摘いただけたら幸甚です🙇