子ビューから親ビューにデータを渡す仕組みとして頻繁に使用する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のポイントを整理
ポイントを整理します。
- ビューに配置されたpreferenceモディファイアは4つ
- reduce()メソッドは3回呼ばれた
- reduce()メソッドが3回呼ばれた後、onPreferenceChange()には最後の処理結果が渡される
reduce()メソッドはビューに配置された同じPreferenceKeyの値をどう処理するかを決める役割を持ちます。
今回は以下のようにvalue
にnextValue()
の値を代入する実装にしました(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はどこで使われる?
どこで使われるのでしょう...?