ハイライト
iOS 15 では SwiftUI が導入されました
@FocusStateプロパティラッパーとfocused()開発者がフォーカスの移動をプログラムで制御し、フォームの検証時に自動的にエラー フィールドにフォールバックし、パスを渡すことを可能にする修飾子focusSection()tvOS 上で大規模なフォーカス ナビゲーション ターゲットを構築します。
主な内容
フォームベースのアプリを作成する場合、キーボードとフォーカスの管理が常に課題となります。
以前のアプローチは UIKit を混合することでしたbecomeFirstResponder()そしてresignFirstResponder()、合格UIViewRepresentableブリッジ、煩雑なコード、プラットフォームの不一致。tvOS ではさらに苦痛です。矢印キーのフォーカス ジャンプはほとんど制御できません。
iOS 15 の SwiftUI は、一連の API を使用して、プラットフォーム全体にわたるフォーカス管理の問題を解決します。
プログラムによるフォーカス制御
@FocusStateは、フォーカス状態を追跡および管理するために特別に設計された新しいプロパティ ラッパーです。
enum Field: Hashable {
case email
case password
}
struct LoginView: View {
@FocusState private var focusedField: Field?
@State private var email = ""
@State private var password = ""
var body: some View {
VStack {
TextField("Email", text: $email)
.focused($focusedField, equals: .email)
SecureField("Password", text: $password)
.focused($focusedField, equals: .password)
}
}
}
(03:49)
キーポイント:
focusedFieldオプションのタイプです。nil はフォーム領域にフォーカスがないことを意味しますField列挙は任意のものにすることができますHashable型、文字列、または整数を使用できます- 改訂
focusedField値を指定すると、フォーカスが対応するビューに自動的に移動します。
フォーム検証とフォーカスのロールバック
VStack {
TextField("Email", text: $email)
.focused($focusedField, equals: .email)
.border(Color.red, width: (focusedField == .email && !isEmailValid) ? 2 : 0)
SecureField("Password", text: $password)
.focused($focusedField, equals: .password)
}
.onSubmit {
submittedEmail = email
if !isEmailValid {
focusedField = .email
} else {
focusedField = nil // 検証に成功したのでキーボードを閉じる
}
}
(06:07)
ユーザーがキーボードの「Go」ボタンをクリックすると、onSubmitがトリガーされます。電子メールの形式が正しくない場合、フォーカスは自動的に電子メール入力ボックスに戻り、赤い枠のプロンプトが表示されます。検証に合格したら、設定しますfocusedField = nilを押すと、キーボードが自動的に収納されます。
tvOS での大きなフォーカス ナビゲーション
tvOS のフォーカス ナビゲーションは近接関係に基づいています。フォーカス可能な 2 つのビューが隣接していない場合、矢印キーでそれらの間をジャンプすることはできません。
focusSection()モディファイアはコンテナ ビューをフォーカス領域としてマークし、隣接しないビューにフォーカスを「ジャンプ」できるようにします。
HStack {
VStack {
TextField("Email", text: $email)
SecureField("Password", text: $password)
}
.focusSection() // 左側のフォーカス領域
VStack {
Image(photoName)
Button("Browse Photos") {}
}
.focusSection() // 右側のフォーカス領域
}
(09:47)
追加focusSection()最後に、ユーザーが左側の入力ボックスでリモコンの右ボタンを押すと、画面上で写真が遠く離れていても、フォーカスは右側の写真の参照ボタンに直接ジャンプします。
##詳細
ログインフォームの完全な例
import SwiftUI
import AuthenticationServices
enum Field: Hashable {
case email
case password
}
struct ContentView: View {
@FocusState private var focusedField: Field?
@State private var email: String = ""
@State private var password: String = ""
@State private var submittedEmail: String = ""
var body: some View {
VStack(alignment: .center) {
Text("Vacation Planner")
.font(.custom("Baskerville-SemiBoldItalic", size: 60))
TextField("Email", text: $email)
.submitLabel(.next)
.textContentType(.emailAddress)
.keyboardType(.emailAddress)
.padding()
.frame(height: 50)
.background(Color.white.opacity(0.9))
.cornerRadius(15)
.padding(10)
.focused($focusedField, equals: .email)
.border(Color.red,
width: (focusedField == .email && !isEmailValid) ? 2 : 0)
SecureField("Password", text: $password)
.submitLabel(.go)
.textContentType(.password)
.padding()
.frame(height: 50)
.background(Color.white.opacity(0.9))
.cornerRadius(15)
.padding(10)
.focused($focusedField, equals: .password)
SignInWithAppleButton(.signIn) { request in
request.requestedScopes = [.fullName, .email]
} onCompletion: { result in
switch result {
case .success:
print("Authorization successful.")
case .failure(let error):
print("Authorization failed: " + error.localizedDescription)
}
}
.frame(height: 50)
.cornerRadius(15)
}
.onSubmit {
submittedEmail = email
if !isEmailValid {
focusedField = .email
} else {
focusedField = nil
}
}
}
private var isEmailValid: Bool {
let regex = "[A-Z0-9a-z._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,64}"
let predicate = NSPredicate(format: "SELF MATCHES %@", regex)
return submittedEmail.isEmpty || predicate.evaluate(with: submittedEmail)
}
}
(06:25)
キーポイント:
.submitLabel(.next)そして.submitLabel(.go)キーボードの右下隅にあるボタンのテキストを制御します.textContentType(.emailAddress)システムにオートコンプリートの提案を提供させます.keyboardType(.emailAddress)メールアドレスの入力に適したキーボードレイアウトが表示されます.border()条件式は、フォーカス状態と検証結果に基づいて赤い枠線を動的に表示します。
クロスプラットフォームのフォーカス管理
@FocusStateテキスト入力ボックスに限定されません。iOS、tvOS、watchOS、macOS など、フォーカス可能なビューであればどれでも機能します。
struct SettingsView: View {
@FocusState private var focusedButton: String?
var body: some View {
VStack {
Button("Notifications") {}
.focused($focusedButton, equals: "notifications")
Button("Privacy") {}
.focused($focusedButton, equals: "privacy")
Button("About") {}
.focused($focusedButton, equals: "about")
}
}
}
重要ポイント
1.複雑なフォームにスマート ナビゲーションを追加
登録プロセス中に、ユーザーがフィールドに入力して Enter キーを押すと、フォーカスは自動的に次の必須フィールドに移動します。現在のフィールドの検証が失敗した場合、フォーカスはその場に留まり、エラー メッセージが表示されます。入口 API: @FocusState + .submitLabel() + .onSubmit()。
2.ワンクリックで検索ボックスをクリア
検索インターフェイスにクリア ボタンを追加します。クリックすると、検索テキストがクリアされ、検索テキストが自動的に検索ボックスに再フォーカスされます。ユーザーは新しいキーワードを直接入力できます。入口 API: focusedField = .search。
3. tvOS アプリケーション用のサイドバー + コンテンツ レイアウトをデザイン
focusSection() を使用してサイドバーとコンテンツ領域を 2 つの独立したフォーカス領域としてマークします。ユーザーは、リモコンの方向キーを使用して、中央のすべての要素を 1 つずつ移動することなく、エリア間を自由にジャンプできます。入口 API: .focusSection()。
4.複数ステップのウィザード インターフェイスを構築する
セットアップ ウィザードでは、各ステップが完了すると、フォーカスが次のステップの最初の入力ボックスに自動的に移動します。@FocusState の列挙で、ステップとフィールドを組み合わせた状態を表します。入口 API: @FocusState + withAnimation。
5. macOS アプリケーションにショートカット キー ナビゲーションを追加
keyboardShortcut() と @FocusState を組み合わせると、ユーザーは Tab キーを押してフォーム フィールドを循環し、Cmd + Enter キーを押して送信できます。入口 API: .keyboardShortcut(.tab) + @FocusState。
関連セッション
- SwiftUI アプリにリッチ グラフィックスを追加 — グラフィックスの描画と視覚的なフィードバックの重視
- Demystify SwiftUI — ビュー ID とフォーカス状態の関係を理解する
- Mac 上の SwiftUI: 基礎を構築する — macOS での管理実践に重点を置く
- SwiftUI での同時実行性の発見 — 非同期操作後のフォーカスの復元
コメント
GitHub Issues · utterances