【Swift UI】PreferenceKeyの使い方!onPreferenceChangeメソッドとは?
この記事からわかること
- Swift UIのPreferenceとは?
- PreferenceKeyプロトコルの使い方
- preference(key:,value:)メソッドとonPreferenceChangeメソッドの使い方
- 子から親にデータを渡す方法
index
[open]
\ アプリをリリースしました /
友達や家族の誕生日をメモ!通知も届く-みんなの誕生日-
posted withアプリーチ
参考文献:公式リファレンス:
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)
}
まだまだ勉強中ですので間違っている点や至らぬ点がありましたら教えていただけると助かります。
ご覧いただきありがとうございました。