[SwiftUI] FocusedValue の使い方

SwiftUI2021

     
⌛️ 2 min.
FocusedValue, FocusedBinding について説明します。

環境&対象

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

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

FocusedValue/FocusedBindingの使い所

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

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

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

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

この記事では、FocusedValue の使い方を説明していきます。FocusedBinding についての記事は、別途 作成予定です。

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

FocusedValue

この記事では、FocusedValue を説明します。

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

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

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

struct MyFocusedDataKey: FocusedValueKey {
    typealias Value = Int
}

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

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

定義したキーを使用して管理されていることがわかると思います。

データ型が Optional である理由は、フォーカスされていないときには、nil が設定されるためです。

MEMO

ちなみに、この処理 どこかで見たことがある気がすると思います。
ほとんど、environment と同じです。

environment 変数では、KeyPath の型が、WritableKeyPath<EnvironmentValue,V> となっていますが、FocusedValue では、WritableKeyPath<FocusedValues,Value?> となっています。

SwiftUI [SwiftUI] Environment 変数を作る方法

値設定の仕方

FocusedValue で扱われる値を設定するための View Modifier が用意されています。
.focusedValue です。

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

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

以下のような形で、FocusedValue を設定することになります。

TextField("Title", $title)
   .focusedValue(\.myData, 2)

気をつけなければいけない点は、”ビューが フォーカスを受けたとき”に 値が設定される という点です。

つまりフォーカスを受けなければ、値が設定されることはありません。
つまり、Optional である型に nil が設定されているということになります。

アクセス方法

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

struct MyView: View {
    @FocusedValue(\. myIntData) var intValue
    var body: some View {
            if let value = intValue {
                Text("intValue = \(intValue)")
            } else {
                Text("No Value")
            }
    }
}

FocusedValue のデータは、フォーカスされたビューが設定することによって設定されるため、まだフォーカスされていない状態であれば、nil となっているため、”No Value” が表示されることになります。

実装例

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

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

  • TextField がフォーカスを受けると、それぞれの focusedValue で設定されている 1, 2, 3 がスクリーン下部に表示されるので、フォーカスを受けるごとに FocusedValue が更新されていることがわかる
  • TextField でなく、Button がフォーカスを受けると、focusedValue が設定されないため、FocusedValue には、nil が設定される
  • 下位ビューでない、SubView が 受け取れていることからも 親子関係の無いビューからでも FocusedValue は、受け取ることができる

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

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

import SwiftUI

struct MyFocusedDataKey: FocusedValueKey {
    typealias Value = Int
}

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

struct ContentView: View {
    var body: some View {
        VStack {
            FieldsView()
            Button(action: {}, label: {Text("DummyButton")})
            SubView()
        }
        .padding()
    }
}

struct FieldsView: View {
    @State private var text1 = ""
    @State private var text2 = ""
    @State private var text3 = ""
    var body: some View {
        Group {
            TextField("Text1", text: $text1)
                .focusedValue(\.myIntData, 1)
            TextField("Text2", text: $text2)
                .focusedValue(\.myIntData, 2)
            TextField("Text3", text: $text3)
                .focusedValue(\.myIntData, 3)
        }.padding()
    }
}

struct SubView: View {
    @FocusedValue(\.myIntData) var intValue
    var body: some View {
        GroupBox("SubView") {
            if let value = intValue {
                Text("intValue = \(value)")
            } else {
                Text("No Value")
            }
        }
    }
}

まとめ

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

FocusedValue の使い方
  • 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版が最新版です。

コメントを残す

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