LoginSignup
9
2

More than 1 year has passed since last update.

[SwiftUI] PreferenceKeyの基本的な仕組みを知る

Last updated at Posted at 2022-06-26

子ビューから親ビューにデータを渡す仕組みとして頻繁に使用するPreferenceKeyですが、

  • reduce()メソッドの実装ってこれで合ってるんだっけ?
  • onPreferenceChangeにはどういう値が入ってくる?

といった点について、毎回デバッグして調べてたので、改めて整理しました。

コード例

ビューとPreferenceKeyを以下の通り実装します。

struct ContentView: View {
    var body: some View {
        VStack {
            Color.clear
                .preference(key: StringPreferenceKey.self, value: "Rectangle1")

            Color.clear
                .preference(key: StringPreferenceKey.self, value: "Rectangle2")

            Color.clear
                .preference(key: StringPreferenceKey.self, value: "Rectangle3")

            Color.clear
                .preference(key: StringPreferenceKey.self, value: "Rectangle4")
        }
        .onPreferenceChange(StringPreferenceKey.self) { value in
            print("onPreferenceChange() value: \(value)")
        }
    }
}

struct StringPreferenceKey: PreferenceKey {
    typealias Value = String
    static var defaultValue: Value = "Default"

    static func reduce(value: inout Value, nextValue: () -> Value) {
        let next = nextValue()
        print("reduce() value: \(value), nextValue: \(next)")
        value = next
    }
}

このビューを画面に表示したとき、コンソールには以下のように出力されます。

reduce() value: Rectangle1, nextValue: Rectangle2
reduce() value: Rectangle2, nextValue: Rectangle3
reduce() value: Rectangle3, nextValue: Rectangle4
onPreferenceChange() value: Rectangle4

PreferenceKeyのポイントを整理

ポイントを整理します。

  1. ビューに配置されたpreferenceモディファイアは4つ
  2. reduce()メソッドは3回呼ばれた
  3. reduce()メソッドが3回呼ばれた後、onPreferenceChange()には最後の処理結果が渡される

reduce()メソッドはビューに配置された同じPreferenceKeyの値をどう処理するかを決める役割を持ちます。
今回は以下のようにvaluenextValue()の値を代入する実装にしました(print文を消してわかりやすくしています)。

static func reduce(value: inout Value, nextValue: () -> Value) {
    value = nextValue()
}

すると、reduce()メソッドが呼ばれるたびにvalueの値は上書きされていき、最後の値であるRectangle4がonPreferenceChange()に渡されます。



次はPreferenceKeyを以下のように実装してみます。

struct StringPreferenceKey: PreferenceKey {
    typealias Value = [String]
    static var defaultValue: Value = []

    static func reduce(value: inout Value, nextValue: () -> Value) {
        let next = nextValue()
        print("reduce() value: \(value), nextValue: \(next)")
        value.append(contentsOf: next)
    }
}

すると、コンソールには以下のように出力されます。

reduce() value: ["Rectangle1"], nextValue: ["Rectangle2"]
reduce() value: ["Rectangle1", "Rectangle2"], nextValue: ["Rectangle3"]
reduce() value: ["Rectangle1", "Rectangle2", "Rectangle3"], nextValue: ["Rectangle4"]
onPreferenceChange() value: ["Rectangle1", "Rectangle2", "Rectangle3", "Rectangle4"]

valueに値をappendしていくので、最終的には4つの要素を持つ配列がonPreferenceChange()に渡されます。
複数の子ビューのデータを一度に受け取って何か処理したいときはこのように実装すると良さそうです。

下層に同じPreferenceKeyが1つしかない場合どうなるのか?

ちなみに、下層に同じPreferenceKeyが1つしかない場合はreduce()メソッドは呼ばれません。

struct ContentView: View {
    var body: some View {
        VStack {
            Color.clear
                .preference(key: StringPreferenceKey.self, value: ["Rectangle1"])
        }
        .onPreferenceChange(StringPreferenceKey.self) { value in
            print("onPreferenceChange() value: \(value)")
        }
    }
}

struct StringPreferenceKey: PreferenceKey {
    typealias Value = [String]
    static var defaultValue: Value = []

    static func reduce(value: inout Value, nextValue: () -> Value) {
        let next = nextValue()
        print("reduce() value: \(value), nextValue: \(next)")
        value.append(contentsOf: next)
    }
}

コンソールの出力。

onPreferenceChange() value: ["Rectangle1"]

defaultValueはどこで使われる?

どこで使われるのでしょう...?

9
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
9
2