[SwiftUI] FocusedBinding の使い方

SwiftUI2021

     
⌛️ 3 min.
FocusedBinding について説明します。

環境&対象

以下の環境で動作確認を行なっています。

  • macOS Monterey 13 Beta9
  • Xcode 14.1 beta 3
  • iOS 16.0

FocusedValue/FocusedBindingの使い所

FocusState は、ビューがフォーカスされているかどうかを そのビュー中で 参照することができました。

FocusedValue / FocusedBinding は、もうすこし拡張された機能です。
ビューがフォーカスされたことをきっかけに、特定のデータを設定することができ、参照することができるようになります。

データの使われ方に応じて、FocusedValue, FocusedBinding が用意されています。

使用シーンとしては、以下のようなシーンが考えられます。
・フォーカスされているビューに応じて、切り替えられるメニュー/コマンド (Apple ドキュメントによく出てきます)
・フォーカスされているテキストフィールドに応じて、内容が切り替えて表示される補助ビュー

この記事では、FocusedBinding の使い方を説明していきます。

FocusState は、以下の記事で説明しています。
SwiftUI2021 [SwiftUI] @FocusState の使い方

FocusedValue は、以下の記事で説明しています。
SwiftUI2021 [SwiftUI] FocusedValue の使い方

FocusedBinding

FocusedValue は、フォーカスを受けたタイミングでデータをセットし、別の箇所から参照することができるものでした。

ただし、別の箇所からは参照のみで データ変更して返すことはできませんでした。

別の箇所からもデータを変更することができるようにするのが、FocusBinding です。

FocusedValue でアクセスする変数の定義

FocusedBinding を使用するためには FocusedValue のときと同様に設定する/アクセスされるためのデータを定義することが必要です。

FocusedBinding のデータも、内部的には、キーを使用して管理されますので、まずは、キーを定義することが必要です。
以下のように、FocusedValueKey に準拠する形で定義します。以下の例では、MyFocusedBindingDataKey というキーが定義できます。
扱う型を typealias を使って、Value に設定します。以下の例では Binding<Int> 型です。

struct MyFocusedBindingDataKey: FocusedValueKey {
    //typealias Value = Int
    typealias Value = Binding<Int>
}

次に、キーを使ってアクセスされた時の処理を定義します。FocusedValues の extension として定義します。

extension FocusedValues {
    var myIntDataBinding: MyFocusedBindingDataKey.Value? {
        get { self[MyFocusedBindingDataKey.self] }
        set { self[MyFocusedBindingDataKey.self] = newValue }
    }
}

キーの定義や FocusedValues の extension での追加コードは、ほとんど FocusedValue と同じです。
異なる点は、データ型が、Binding であるということです。

データ型が Binding であっても、フォーカスされていないケースを処理するために、FocusedValues でのデータ型は Optional として 定義します。

値設定の仕方

FocusedBinding で扱われる値を設定するときにも、 FocusedValue と同じ View Modifier を使用します。
.focusedValue です。

func focusedValue(
    _ keyPath: WritableKeyPath,
    _ value: Value
) -> some View

Apple のドキュメントは、こちら

以下のような形で、値を設定することになりますが、Binding 型を設定することが必要です。

@State private var myIntData1 = 1
~ snip ~
TextField("Title", $title)
   .focusedValue(\.myIntDataBinding, $myIntData1)

FocusValue と同様に、FocusedBinding も ”ビューが フォーカスを受けたとき”に 値が設定される という点に注意することが必要です。

つまりフォーカスを受けなければ、値が設定されることはありません。

アクセス方法

設定された FocusedBinding にアクセスするには、Property Wrapper である @FocusedBinding を使います。
データ型が Optional であるため、確認してから 使用しています。

struct SubView: View {
    @FocusedBinding(\.myIntDataBinding) var myBinding
    var body: some View {
        GroupBox("SubView") {
            Button(action: {
                if let value = myBinding {
                    myBinding = value * 8
                }
            }, label: {
                Text("set x 8")
            })
            .disabled(myBinding == nil)
        }
    }
}

FocusedBinding のデータは、フォーカスされたビューが設定することによって設定されるため、まだフォーカスされていない状態であれば、nil となっています。つまり、Binding 経由でのデータを変更することはできません。上記コードでは、nil の時には、Button を disable にしています。

実装例

以下のようなアプリを作って動作を確認しました。
TextField 3つと Button を含む SubView で構成されています。

動画で動作を確認してもらうと、以下の点を確認することができます。

  • TextField がフォーカスを受けると、それぞれの focusedValue で設定されている 1, 2, 3 がスクリーン下部に表示される。
  • Button を押して、focusedValue を8倍にセットされる
  • セットされた値は、記憶されていて、再度フォーカスされると、記録されている値が表示される
  • 下位ビューでない、SubView が 受け取れていることからも 親子関係の無いビューからでも FocusedValue は、受け取ることができる

以下は使用したコードです。

//
//  ContentView.swift
//
//  Created by : Tomoaki Yagishita on 2022/09/30
//  © 2022  SmallDeskSoftware
//

import SwiftUI

struct ContentView: View {
    var body: some View {
        VStack {
            FieldsView()
            SubView()
        }
        .padding()
    }
}

struct FieldsView: View {
    @FocusedBinding(\.myIntDataBinding) var intValue
    @State private var text1 = ""
    @State private var text2 = ""
    @State private var text3 = ""
    @State private var myIntData1 = 1
    @State private var myIntData2 = 2
    @State private var myIntData3 = 3
    var body: some View {
        Group {
            TextField("Text1", text: $text1)
                .focusedValue(\.myIntDataBinding, $myIntData1)
            TextField("Text2", text: $text2)
                .focusedValue(\.myIntDataBinding, $myIntData2)
            TextField("Text3", text: $text3)
                .focusedValue(\.myIntDataBinding, $myIntData3)
            if let intValue = intValue {
                Text("Current FocusedValue: \(intValue)")
            } else {
                Text("No value")
            }
        }.padding()
    }
}

struct SubView: View {
    @FocusedBinding(\.myIntDataBinding) var myBinding
    var body: some View {
        GroupBox("SubView") {
            Button(action: {
                if let value = myBinding {
                    myBinding = value * 8
                }
            }, label: {
                Text("set x 8")
            })
            .disabled(myBinding == nil)
        }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

struct MyFocusedBindingDataKey: FocusedValueKey {
    typealias Value = Binding<Int>
}

extension FocusedValues {
    var myIntDataBinding: MyFocusedBindingDataKey.Value? {
        get { self[MyFocusedBindingDataKey.self] }
        set { self[MyFocusedBindingDataKey.self] = newValue }
    }
}

まとめ

FocusedBinding の使い方を説明しました。

FocusedBinding の使い方
  • FocusedBinding は、FocusValue と似ている
  • FocusedBinding は、Binding を対象とする
  • FocusedValueKey に準拠したキーを定義する, Value に型も設定する
  • FocusedValues の extension にアクセスを定義する
  • focusedValue は、View がフォーカスを受けないと値をセットしない
  • focusedValue は、フォーカスを失うと nil になる

説明は以上です。
不明な点やおかしな点ありましたら、こちらまで。

SwiftUI おすすめ本

SwiftUI を理解するには、以下の本がおすすめです。

SwiftUI ViewMastery

SwiftUI で開発していくときに、ViewやLayoutのための適切なmodifierを探すのが大変です。
英語での説明になってしまいますが、以下の”SwiftUI Views Mastery Bundle”という本がビジュアル的に確認して探せるので、便利です。

英語ではありますが、1ページに コードと画面が並んでいるので、非常にわかりやすいです。

View に適用できる modifier もわかりやすく説明されているので、ビューの理解だけではなく、どのような装飾ができるかも簡単にわかります。

超便利です

SwiftUIViewsMastery

販売元のページは、こちらです。

SwiftUI 徹底入門

# SwiftUI は、毎年大きく改善されていますので、少し古くなってしまいましたが、いまでも 定番本です。

Swift学習におすすめの本

詳解Swift

Swift の学習には、詳解 Swift という書籍が、おすすめです。

著者は、Swift の初期から書籍を出していますし、Swift の前に主力言語だった Objective-C という言語についても同様の書籍を出しています。

最新版を購入するのがおすすめです。

現時点では、上記の Swift 5 に対応した第5版が最新版です。

2 COMMENTS

aaaaaa

末尾のコード例で「」が抜けてる気がします
> struct MyFocusedBindingDataKey: FocusedValueKey {
> typealias Value = Binding
> }

返信する
tyagishi

指摘ありがとうございます。
type parameter の < をエスケープし忘れて意味不明になっていました。

返信する

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です