@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です。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プロパティを自動的に更新し、コード内でフォーカスの変更に反応できるようにします。
@FocusStateを使用する最も簡単な方法は、Bool型を使用することです。フィールドがフォーカスされている場合、プロパティはtrueになります。フォーカスが外れるとfalseになります。trueに設定してフォーカスを強制したり、falseに設定してフォーカスを解除したりできます。
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になります。ボタンを使用するとプログラムでフォーカスを管理できます。カスタムキーボード、「次へ」ボタン、フォーム送信後に強制的にキーボードを閉じる必要がある状況に便利です。
複数フィールドのフォームの場合、@FocusStateはFocusStateValue(またはHashable)プロトコルに準拠したenumをサポートしています。各enumケースは特定のフィールドに対応します。これにより、フィールド間でフォーカスを切り替えることができます。例えば、ユーザーがキーボードの「次へ」を押して次のフィールドに移動する場合などです。
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の場合)に設定するだけです。ただし、特定のフィールドに結び付けずにキーボードを非表示にする必要がある場合もあります。例えば、空のスペースをタップした場合などです。この場合、いくつかのアプローチがあります。
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はフォームバリデーションと完全に組み合わせることができます。典型的なパターン:「送信」ボタンを押した後、すべてのフィールドを検証し、エラーのある最初のフィールドにフォーカスを設定します。これによりユーザーエクスペリエンスが向上し、ユーザーはどのフィールドを修正すべきかをすぐに確認できます。
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がチェックされます。これはフォームの自然な動作です。ユーザーは上から下にフィールドを入力し、バリデーションも同じ順序に従います。
最も一般的な間違いは、Hashableに準拠していない型で@FocusStateを使用しようとすることです。@FocusStateはプロパティ型がHashableであることを要求します(Boolとオプショナルenumはすでに準拠しています)。カスタム構造体を使用しようとしている場合は、それがHashableを実装していることを確認してください。
// ❌ 間違い: 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 15、iPadOS 15、macOS 12、tvOS 15、watchOS 8以降で利用可能です。iOS 14以下をサポートするプロジェクトでは、UITextFieldとbecomeFirstResponderを使用したUIViewRepresentable、またはカスタムフォーカス管理実装を持つサードパーティライブラリを使用してください。
はい。そのためには、カスタムUIViewRepresentableでUIViewRepresentableプロトコルを通じてFocusStateのサポートを実装する必要があります。カスタムViewにはbecomeFirstResponderとresignFirstResponderが必要です。.focused()モディファイアを指定すると、SwiftUIはこれらのメソッドに@FocusStateを自動的にリンクします。
ListやFormでは、セルが再利用される可能性があり、フィールドとの@FocusStateバインディングが壊れます。解決策:各TextFieldに一意の識別子を持つ.id()モディファイアを追加します。例:.id(fieldName)。これにより、SwiftUIは各フィールドに個別のViewインスタンスを作成します。
ルートコンテナ(VStack、ZStack)に.onTapGestureを追加し、フォーカスをリセットします:focusedField = nil。ただし、.onTapGestureは内部のボタンへのタップをブロックする可能性があります。.contentShape(Rectangle())を持つコンテナとその上に.onTapGestureを使用するか、カスタムのUIKitBackgroundViewを使用してください。
@FocusStateはキーボードアニメーションの直接的なAPIを提供していません。これはiOSのシステム動作です。ただし、.onChange(of: focusedField)または.onReceive(NotificationCenter.default.publisher(for: UIResponder.keyboardWillShowNotification))を使用してフォーカスの変更に反応し、カスタムコンテンツアニメーションを行うことができます。
まとめ
ターンキー方式のモバイルアプリケーションを開発します
IT Sectrは2017年からスタートアップや企業向けにiOS・Androidアプリケーションを開発しています。私たちがご相談に乗り、最適なソリューションをご提案します。