WWDC Quick Look 💓 By SwiftGGTeam
Direct and reflect focus in SwiftUI

Direct and reflect focus in SwiftUI

元の動画を見る

ハイライト

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

関連セッション

コメント

GitHub Issues · utterances