6
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

TCAでFocusStateを正しく使う方法

Posted at

iOS15で追加されたFocusStateは、TextFieldなどのフォーカス制御が簡単になる便利なプロパティラッパーです。
今回はFocusStateをTCAで正しく扱う方法について解説していきたいと思います。

実装

Viewに以下のようなextensionを追加します。

public extension View {
    func synchronize<Value: Equatable>(
        _ first: Binding<Value>,
        _ second: FocusState<Value>.Binding
    ) -> some View {
        onChange(of: first.wrappedValue, perform: { second.wrappedValue = $0 })
            .onChange(of: second.wrappedValue, perform: { first.wrappedValue = $0 })
    }
}

onChangeを使ってfirstのwrappedValueが変化したらsecondのwrappedValueにfirstの値を入れ、
同じようにsecondが変化したらfirstに代入しています。このようにして、BindingとFocusStateをリンクすることができます。

使用方法

今回はemailとpasswordの入力を例にします。EmailのTextFieldでエンターを押すとPasswordのTextFieldにfocusされる処理を書いていきます。

HogeReducer.swift
struct HogeReducer: ReducerProtocol {
    struct State: Equatable {
        @BindingState var focused: Field?
        @BindingState var email = ""
        @BindingState var password = ""
    }

    enum Action: Equatable, BindableAction {
        case binding(BindingAction<State>)
    }

    var body: some ReducerProtocolOf<Self> {
        BindingReducer()
    }
}

extension HogeReducer {
    enum Field {
        case email, password
    }
}
HogeView.swift
struct HogeView: View {
    @FocusState private var focused: HogeReducer.Field?
    private let store: StoreOf<HogeReducer>

    init(store: StoreOf<HogeReducer>) {
        self.store = store
    }

    var body: some View {
        WithViewStore(store, observe: { $0 }) { viewStore in
            HStack {
                TextField(
                    "email",
                    text: viewStore.binding(\.$email),
                    onCommit: {
                        // Enterを押したらPasswordのFieldがFocusされるように
                        viewStore.send(.set(\.$focused, .password))
                    }
                )
                .focused($focused, equals: .email)

                SecureField("password", text: viewStore.binding(\.$password))
                    .focused($focused, equals: .password)
            }
            // HogeReducer.StateのfocusedとHogeViewのfocusedをシンクロさせる。
            .synchronize(viewStore.binding(\.$focused), $focused)
        }
    }
}

TCAは単方向のデータフローなのでViewで状態を保ち、Viewで状態を変更するのは好ましくないです。そのため、HogeReducerのほうでfocusedの状態を保持、変更されるようにしています。

まとめ

TCAでFocusStateを正しく利用することができました。

参考文献

6
4
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
6
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?