@FocusState — とは何か、SwiftUIでのフォーカスとキーボード管理

著者: IT Sectr 公開日: 2026-06-26 読了時間: 9 分

@FocusStateは、SwiftUIのproperty wrapperで、iOS 15で導入され、テキストフィールドやその他の要素の入力フォーカスをプログラムで制御できるようにします。これが登場するまでは、開発者はbecomeFirstResponderやresignFirstResponderといったUIKitメソッドにアクセスするためにUIViewRepresentableを使用する必要がありました。@FocusStateはこの問題をネイティブに解決します。.focused()モディファイアを介してプロパティをフィールドにバインドし、その後フォーカスの設定や解除は単純な値の代入で行われます。Apple Developer Documentation — FocusState (2025)によると、@FocusStateは2つのモードをサポートしています。シンプルな管理用のBool(フォーカスオンまたはオフ)と、複数フィールド用のenumで、各ケースが特定の入力フィールドに対応します。

重要なポイント

  • @FocusState — SwiftUIでプログラムによるフォーカス管理を行うためのproperty wrapper、iOS 15以降で利用可能。
  • Boolモード — 単一フィールドには@FocusState var isFocused: Boolを.focused($isFocused)と共に使用。
  • Enumモード — 複数フィールドにはFocusStateValueプロトコルに準拠したenumと.focused($field, equals: .fieldName)を使用。
  • キーボードを非表示 — フォーカスをnilまたはfalseに設定してキーボードを閉じる。
  • 自動フォーカス — 画面表示時にキーボードを表示するには.onAppearで初期値を設定。

SwiftUIでの@FocusStateとは

@FocusStateは、SwiftUIにおいてフォーカス状態を特定の入力フィールドやその他のフォーカス可能な要素に結び付けるproperty wrapperです。becomeFirstResponderやresignFirstResponderメソッドを通じてフォーカス管理を行うUIKitとは異なり、SwiftUIは宣言的アプローチを使用します。状態(@FocusState)を宣言し、.focused()モディファイアを介して要素にバインドします。状態を変更すると、自動的にフォーカスが変更されます。

iOS 15で@FocusStateが導入される前は、開発者はUITextFieldの周りにUIViewRepresentableラッパーを作成するか、サードパーティのライブラリを使用する必要がありました。@FocusStateはSwiftUIに直接統合されており、TextField、TextEditor、SecureField、SearchFieldで動作します。これによりコードがよりクリーンになり、UIKitブリッジの数が減り、テスト容易性が向上します。

WWDC Session 10136 — What's new in SwiftUI (2024)によると、@FocusStateはSwiftUIのpreference keyシステムを使用して要素間でフォーカス情報を渡します。フィールドがフォーカスを受け取ると、SwiftUIは関連付けられた@FocusStateプロパティを自動的に更新し、コード内でフォーカスの変更に反応できるようにします。

Boolによるフォーカス管理

@FocusStateを使用する最も簡単な方法は、Bool型を使用することです。フィールドがフォーカスされている場合、プロパティはtrueになります。フォーカスが外れるとfalseになります。trueに設定してフォーカスを強制したり、falseに設定してフォーカスを解除したりできます。

swift
struct LoginForm: View {
    @State var email = ""
    @FocusState var isEmailFocused: Bool
    
    var body: some View {
        VStack {
            TextField("Email", text: $email)
                .focused($isEmailFocused)
            
            Button("キーボードを表示") {
                isEmailFocused = true
            }
            Button("キーボードを非表示") {
                isEmailFocused = false
            }
        }
    }
}

この例では、isEmailFocusedはユーザーがテキストフィールドをタップすると自動的にtrueになり、キーボードが非表示になるとfalseになります。ボタンを使用するとプログラムでフォーカスを管理できます。カスタムキーボード、「次へ」ボタン、フォーム送信後に強制的にキーボードを閉じる必要がある状況に便利です。

複数フィールドのEnumによるフォーカス管理

複数フィールドのフォームの場合、@FocusStateFocusStateValue(またはHashable)プロトコルに準拠したenumをサポートしています。各enumケースは特定のフィールドに対応します。これにより、フィールド間でフォーカスを切り替えることができます。例えば、ユーザーがキーボードの「次へ」を押して次のフィールドに移動する場合などです。

swift
struct RegistrationForm: View {
    enum Field: Hashable {
        case email
        case password
        case confirmPassword
    }
    
    @State var email = ""
    @State var password = ""
    @State var confirmPassword = ""
    @FocusState var focusedField: Field?
    
    var body: some View {
        Form {
            TextField("Email", text: $email)
                .focused($focusedField, equals: .email)
                .onSubmit { focusedField = .password }
            
            SecureField("Password", text: $password)
                .focused($focusedField, equals: .password)
                .onSubmit { focusedField = .confirmPassword }
            
            SecureField("Confirm", text: $confirmPassword)
                .focused($focusedField, equals: .confirmPassword)
                .onSubmit { submitForm() }
        }
    }
}

.onSubmitモディファイアに注目してください。これはユーザーがキーボードで「Return」を押したときに呼び出されます。.onSubmit内でfocusedFieldを次のフィールドに切り替えると、自動的にフォーカスが移動します。最後のフィールドはsubmitForm()を呼び出してフォームを送信します。

キーボードの表示と非表示

@FocusStateはキーボードを非表示にする簡単な方法を提供します。プロパティをnil(enumの場合)またはfalse(Boolの場合)に設定するだけです。ただし、特定のフィールドに結び付けずにキーボードを非表示にする必要がある場合もあります。例えば、空のスペースをタップした場合などです。この場合、いくつかのアプローチがあります。

swift
struct DismissKeyboardView: View {
    @State var text = ""
    @FocusState var isFocused: Bool
    
    var body: some View {
        TextField("Enter text", text: $text)
            .focused($isFocused)
            .toolbar {
                ToolbarItemGroup(placement: .keyboard) {
                    Spacer()
                    Button("完了") {
                        isFocused = false
                    }
                }
            }
    }
}

placement .keyboardを指定した.toolbarモディファイアは、キーボードの上にボタンを追加します。これはiOSでキーボードを閉じるための標準的なUXパターンです。別のアプローチとして、ルートVStackに.onTapGestureを使用して、背景をタップしたときにフォーカスをリセットする方法があります。

フォーカスとフォームバリデーション

@FocusStateはフォームバリデーションと完全に組み合わせることができます。典型的なパターン:「送信」ボタンを押した後、すべてのフィールドを検証し、エラーのある最初のフィールドにフォーカスを設定します。これによりユーザーエクスペリエンスが向上し、ユーザーはどのフィールドを修正すべきかをすぐに確認できます。

swift
struct ValidatedForm: View {
    enum Field: Hashable { case name; case phone }
    
    @State var name = ""
    @State var phone = ""
    @FocusState var focusedField: Field?
    @State var errors: [String] = []
    
    var body: some View {
        Form {
            TextField("Name", text: $name)
                .focused($focusedField, equals: .name)
            TextField("Phone", text: $phone)
                .focused($focusedField, equals: .phone)
            
            Button("送信") { validateAndSubmit() }
        }
    }
    
    func validateAndSubmit() {
        if name.isEmpty {
            focusedField = .name
            return
        }
        if phone.isEmpty {
            focusedField = .phone
            return
        }
        // フォームを送信
    }
}

この例では、nameフィールドが空の場合、フォーカスがそのフィールドに移動し、ユーザーはすぐにエラーの場所を確認できます。nameが入力されている場合は、phoneがチェックされます。これはフォームの自然な動作です。ユーザーは上から下にフィールドを入力し、バリデーションも同じ順序に従います。

@FocusStateのよくある間違い

最も一般的な間違いは、Hashableに準拠していない型で@FocusStateを使用しようとすることです。@FocusStateはプロパティ型がHashableであることを要求します(Boolとオプショナルenumはすでに準拠しています)。カスタム構造体を使用しようとしている場合は、それがHashableを実装していることを確認してください。

  • .focused()モディファイアを忘れた — @FocusState単独ではフォーカスを管理しません。.focused($property)または.focused($property, equals: .case)を介してフィールドにバインドする必要があります。
  • 1つのViewに複数の@FocusState — 複数のフィールドには、複数の@FocusStateプロパティではなく、enumを持つ1つの@FocusStateを使用します。複数のBoolプロパティは互いに同期されません。
  • メインスレッド外での@FocusStateの変更 — @FocusStateは、SwiftUIのすべてのUIプロパティと同様に、メインスレッドでのみ変更する必要があります。非同期操作は、変更する前にMainActorに切り替える必要があります。
  • View再構築時のフォーカスリセット — Viewが再構築されると、@FocusStateがリセットされる可能性があります。安定したView識別のために.id()モディファイアを使用してください。
swift
// ❌ 間違い: enumの代わりに2つの@FocusState Bool
@FocusState var isNameFocused: Bool
@FocusState var isEmailFocused: Bool

// ✅ 正解: 単一のenum @FocusState
enum Field: Hashable { case name; case email }
@FocusState var focusedField: Field?

よくある質問

@FocusStateはどのiOSバージョンから利用可能ですか?

@FocusStateはiOS 15、iPadOS 15、macOS 12、tvOS 15、watchOS 8以降で利用可能です。iOS 14以下をサポートするプロジェクトでは、UITextFieldとbecomeFirstResponderを使用したUIViewRepresentable、またはカスタムフォーカス管理実装を持つサードパーティライブラリを使用してください。

カスタムUIViewRepresentableで@FocusStateを使用できますか?

はい。そのためには、カスタムUIViewRepresentableでUIViewRepresentableプロトコルを通じてFocusStateのサポートを実装する必要があります。カスタムViewにはbecomeFirstResponderとresignFirstResponderが必要です。.focused()モディファイアを指定すると、SwiftUIはこれらのメソッドに@FocusStateを自動的にリンクします。

List内のTextFieldで@FocusStateが機能しないのはなぜですか?

ListやFormでは、セルが再利用される可能性があり、フィールドとの@FocusStateバインディングが壊れます。解決策:各TextFieldに一意の識別子を持つ.id()モディファイアを追加します。例:.id(fieldName)。これにより、SwiftUIは各フィールドに個別のViewインスタンスを作成します。

空のスペースをタップしたときにキーボードを閉じるには?

ルートコンテナ(VStack、ZStack)に.onTapGestureを追加し、フォーカスをリセットします:focusedField = nil。ただし、.onTapGestureは内部のボタンへのタップをブロックする可能性があります。.contentShape(Rectangle())を持つコンテナとその上に.onTapGestureを使用するか、カスタムのUIKitBackgroundViewを使用してください。

@FocusStateでキーボード表示をアニメーション化するには?

@FocusStateはキーボードアニメーションの直接的なAPIを提供していません。これはiOSのシステム動作です。ただし、.onChange(of: focusedField)または.onReceive(NotificationCenter.default.publisher(for: UIResponder.keyboardWillShowNotification))を使用してフォーカスの変更に反応し、カスタムコンテンツアニメーションを行うことができます。

まとめ

  • @FocusState — 入力フォーカス管理のためのネイティブSwiftUI property wrapper、iOS 15以降で利用可能。
  • 2つのモード — 単一フィールド用のBool、複数フォームフィールド用のHashable enum。
  • .focused()モディファイア — @FocusStateを特定の入力フィールドにバインドするために必須。
  • プログラムによる制御 — 値をnilまたはfalseに設定するとキーボードが非表示になります。
  • フォームバリデーション — @FocusStateを使用すると、バリデーション後にエラーのある最初のフィールドにフォーカスを設定できます。
  • 複数フィールド用のEnum — 複数のBoolプロパティよりもenumを使用した単一の@FocusStateが推奨されます。
  • iOS 15+ — 古いバージョンでは、becomeFirstResponderを使用したUIViewRepresentableを使用してください。

ターンキー方式のモバイルアプリケーションを開発します

IT Sectrは2017年からスタートアップや企業向けにiOS・Androidアプリケーションを開発しています。私たちがご相談に乗り、最適なソリューションをご提案します。

プロジェクトについて相談

こちらもお読みください