【SwiftUI】@FocusStateの使い方

SwiftUI

SwiftUIでは@FocusStateを使うことで入力フォームのフォーカスを制御出来ます。
フォーカスを外してソフトウェアキーボードを閉じたり、
他の入力フォームへ移動したり出来ます。

@FocusStateはiOS15からの機能ですので、
その点には気を付けて下さい。

@FocusStateとは

@FocusStateはProperty Wrapperです。
@Stateや@ObservedObjectでお馴染みですね。

同じようにViewで変数宣言する際に指定します。

@FocusState var focus:Bool

初期値は設定できません。
使用できるのはBool又はHashableなものになります。
Hashableの使い方については後述。

入力フォームに対してFocusStateの変数を割り当てます。
focused Modifierを使用します。

TextField("TextField", text: self.$text)
    .focused(self.$focus)

これで変数focusにTextFieldのフォーカスが反映される様になりました。
この変数のtrue/falseを切り替える事で、コード側からの変更も可能です。

フォーカスのON/OFFを切り替える

それでは実際にフォーカスを切り替えてみましょう。

FormにTextFieldと2つのButtonを設置し、
Buttonでフォーカス切り替える様にしました。

struct FocusStateBoolView: View {
    
    @State var text = ""
    @FocusState var focus:Bool
    
    var body: some View {
        Form {
            TextField("TextField", text: self.$text)
                .focused(self.$focus)
            Button("Focus ON"){
                self.focus = true
            }
            Button("Focus OFF"){
                self.focus = false
            }
        }
    }
}

TextFieldに触れずともボタンのみでフォーカスを制御できました。
ソフトウェアキーボードを使用している場合はフォーカスを外す事でクローズするので、
キーボードのreturnを押さずに閉じる事が出来ます。
TextEditerなどの改行を受け付ける入力フォームを使う際に有効でしょう。

フォーカスの移動

入力フォームが複数ある場合にも@FocusStateは有効です。
はじめにBool型で説明しましたが、複数ある場合はHashableを使います。
全ての入力フォームにBool型変数をそれぞれ用意する必要はありません。

struct FocusStateMoveView: View {
    enum Field:Hashable{
        case firstName
        case lastName
    }
    
    @State var firstName = ""
    @State var lastName = ""
    
    @FocusState var focus:Field?
    
    var body: some View {
        Form {
            TextField("FirstName", text: self.$firstName)
                .focused(self.$focus, equals: Field.firstName)
            TextField("LastName", text: self.$lastName)
                .focused(self.$focus, equals: Field.lastName)
            
            Button("FirstName"){
                self.focus = .firstName
            }
            Button("LastName"){
                self.focus = .lastName
            }
            Button("Close"){
                self.focus = nil
            }
        }
    }
}

enumにHashableを継承して使っています。
これはfocused ModifierにHashableな変数が要求されるからです。

変数はOptionalにします。
またfocusedの第二引数equalsにはその入力フォームに対応する値を指定します。

@FocusState var focus:Field?

TextField("FirstName", text: self.$firstName)
    .focused(self.$focus, equals: Field.firstName)

コード側から変更する際はequalsに指定した値を変数に代入するとフォーカスが切り替わります。
フォーカスを外す際はnilを代入します。

実用的なフォーカス制御UI

実際にフォーカス制御をする際にはFormにボタンを並べるだけではあまり意味がありません。
やはり置くならばキーボードの側でしょう。なのでtoolbarを使って配置します。

struct FocusStateView: View {
    
    enum Field:Int,Hashable{
        case FirstName
        case LastName
    }
    
    @State var firstName = ""
    @State var lastName = ""
    
    @FocusState var focus:Field?
    
    var body: some View {
        Form {
            TextField("FirstName", text: $firstName)
                .focused($focus, equals: Field.FirstName)
            TextField("LastName", text: $lastName)
                .focused($focus, equals: Field.LastName)
        }.toolbar{
            ToolbarItem(placement: .keyboard){
                HStack{
                    Button(action: {
                        focus = Field(rawValue: focus!.rawValue - 1)
                    }){
                        Image(systemName: "chevron.up")
                    }
                    Button(action: {
                        focus = Field(rawValue: focus!.rawValue + 1)
                    }){
                        Image(systemName: "chevron.down")
                    }
                    Spacer()
                    Button("Close"){
                        focus = nil
                    }
                }
            }
        }
    }
}

キーボードのツールバーにボタンを置いて、そこからフォーカスの操作を行う様にしました。
これで画面上部を触ることなくキーボード近辺のみで操作できるようになりました。

ToolbarItemに.keyboardを指定できるのはiOS15からですが、
@FocusStateもiOS15からなので特に問題はないでしょう。

なお実用的とは言ったものの、
フォーカス切り替えのためのenumのprev/next操作はサクッと適当に書いたものなので、
必要に応じてより良い形にして使用してください。

※余談ですがシミュレータでソフトウェアキーボードを使用する際は「cmd+shift+K」
又は「I/O→Keyboard→Connect HardwareKeyboard」で切り替えて下さい。

コメント

タイトルとURLをコピーしました