【Swift UI】PreferenceKeyの使い方!onPreferenceChangeメソッドとは?
この記事からわかること
- Swift UIのPreferenceとは?
- PreferenceKeyプロトコルの使い方
- preference(key:,value:)メソッドとonPreferenceChangeメソッドの使い方
- 子から親にデータを渡す方法
index
[open]
\ アプリをリリースしました /
参考文献:公式リファレンス:
Preferenceとは?
Swift UIのPreferenceは異なるビュー間でデータを共有するための機能を提供する仕組みです。Preferenceを使用することで子から親へデータを渡すことが可能になります。
Swift UIでも実際に使用されておりnavigationTitle(_:)モディファイアなどが実際にPreferenceを使用している具体例としてあげられています。
NavigationStack {
List {
Text("要素1")
Text("要素2")
}.navigationTitle("タイトル")
}
navigationTitleはNavigationStackから呼び出しそうな気がしますが、子から呼び出す仕様になっています。このように子から指定したデータを親側に反映させる時などにPreferenceは使用されます。
Preferenceの実装方法
Preferenceを簡単なデモ機能を作って実装してみます。今回は「スクロール量を計測し出力する」機能を実装してみます。ここでは子側としてGeometryReaderから取得したスクロール量を親側としてクラスのプロパティに格納し出力する流れを実装してみます。
PreferenceKeyプロトコル
まずは子側から親側にデータを渡すためのインターフェースとしてPreferenceKeyプロトコルに準拠した構造体を実装していきます。
struct ScrollOffsetYPreferenceKey: PreferenceKey {
static var defaultValue: [CGFloat] = [0]
static func reduce(value: inout [CGFloat], nextValue: () -> [CGFloat]) {
value.append(contentsOf: nextValue())
}
}
PreferenceKeyプロトコルにはdefaultValueプロパティとreduceメソッドの実装が必要になります。defaultValueプロパティはそのままデフォルト値を、reduceメソッドには値の更新処理を実装します。
preference(key:,value:)メソッドで値を渡す
公式リファレンス:preference(key:,value:)メソッド
子側から親側にデータを渡すためにpreference(key:,value:)メソッドを使用してインターフェースに値を渡します。引数keyにはインターフェース自身を、valueには更新したい値を渡します。
struct ContentView: View {
@State private var offsetY: CGFloat = 0
@State private var initOffsetY: CGFloat = 0
var body: some View {
ScrollView() {
VStack {
ForEach(1...100, id: \.self) {
Text("Item \($0)")
.padding()
}
}.background(
GeometryReader { geometry in
Color.clear
.preference(
key: ScrollOffsetYPreferenceKey.self,
value: [geometry.frame(in: .global).minY]
).onAppear {
initOffsetY = geometry.frame(in: .global).minY
}
}
)
}
}
}
onPreferenceChangeメソッドで変更を検知
公式リファレンス:onPreferenceChangeメソッド
インターフェースから値を取得するためにonPreferenceChangeメソッドを使用して変更を観測します。変化があればクロージャー内から取得できるのであとは自由に操作するだけです。
ScrollView() {
VStack {
ForEach(1...100, id: \.self) {
Text("Item \($0)")
.padding()
}
}.background(
GeometryReader { geometry in
Color.clear
.preference(
key: ScrollOffsetYPreferenceKey.self,
value: [geometry.frame(in: .global).minY]
).onAppear {
initOffsetY = geometry.frame(in: .global).minY
}
}
)
}
.onPreferenceChange(ScrollOffsetYPreferenceKey.self) { value in
offsetY = value[0]
print(offsetY - initOffsetY)
}
まだまだ勉強中ですので間違っている点や至らぬ点がありましたら教えていただけると助かります。
ご覧いただきありがとうございました。







