LoginSignup
2
0

[TCA] TextFieldの文字数制限をする方法

Last updated at Posted at 2023-12-06

 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を更新する」というアプローチではうまくいきませんでした。

 多分、ほかに筋の良い対処方法があるのだと思いますが、その情報に辿り着けませんでした。
原因や対処方法など、ご指摘いただけたら幸甚です🙇

2
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0