Jan 22, 2025

Manage FocusState using TCA

Here's how you can manage @FocusState using TCA.

import ComposableArchitecture
import SwiftUI

@Reducer
struct FocusStateFeature {
  @ObservableState
  struct State: Equatable {
    enum Field: Hashable {
      case username
      case password
    }
    
    var focusedField: Field?
    var username: String = ""
    var password: String = ""
  }
  
  enum Action: BindableAction {
    case binding(BindingAction<State>)
    
    case submittedUsername
  }

  var body: some Reducer<State, Action> {
    BindingReducer()
    Reduce { state, action in
      switch action {
      case .submittedUsername:
        state.focusedField = .password
        return .none
      case .binding:
        return .none
      }
    }
  }
}

struct FocusStateView: View {
  @Bindable var store: StoreOf<FocusStateFeature>
  @FocusState var focusedField: FocusStateFeature.State.Field?
  
  var body: some View {
    VStack {
      TextField("Username", text: $store.username)
        .focused($focusedField, equals: .username)
        .onSubmit {
          store.send(.submittedUsername)
        }
      SecureField("Password", text: $store.password)
        .focused($focusedField, equals: .password)
    }
    .bind($store.focusedField, to: $focusedField)
  }
}

#Preview {
  FocusStateView(
    store: Store(
      initialState: FocusStateFeature.State(),
      reducer: { FocusStateFeature() }
    )
  )
}