SwiftUI PreferenceKey サンプル実装
ポイント
-
DetermineHeight
で各カードの高さを計測し、MaxHeightPreferenceKey
で最大値を集約 -
onPreferenceChange
で最大値が変わったら@State
に反映し、全カードの高さを揃える -
.overlay
で見た目に影響なく高さを計測
さらに解説やサンプルコードをご希望の場合はお知らせください。段階的に展開も可能です。
import SwiftUI
// 1. 最大高さを集約する PreferenceKey
struct MaxHeightPreferenceKey: PreferenceKey {
static var defaultValue: CGFloat = 0
static func reduce(value: inout CGFloat, nextValue: () -> CGFloat) {
value = max(value, nextValue())
}
}
// 2. 各カードの高さを PreferenceKey に伝えるための View
struct DetermineHeight: View {
var body: some View {
GeometryReader { proxy in
Color.clear
.anchorPreference(
key: MaxHeightPreferenceKey.self,
value: .bounds
) { anchor in
proxy[anchor].size.height
}
}
}
}
// 3. サンプルカード
struct SampleCard: View {
let text: String
let maxHeight: CGFloat
var body: some View {
Text(text)
.frame(maxWidth: .infinity)
.frame(height: maxHeight)
.background(Color.yellow.opacity(0.3))
.overlay(DetermineHeight())
.cornerRadius(8)
.padding(.horizontal)
}
}
// 4. 親ビューで高さを揃える
// onPreferenceChange で最大の高さが更新されたら全体の高さを更新
struct ContentView: View {
let items = [
"短いテキスト",
"とてもとても長いテキストサンプルです",
"中くらい"
]
// 状態管理プロパティ:@State を使うと更新時に ContentView の body が再評価される
// ※ 親Viewで状態(@State)を管理し、子Viewには値として渡すだけの場合は @Binding 不要
// 子Viewから親Viewの値を変更したい場合のみ、@Binding を使います
@State private var maxHeight: CGFloat = 0
var body: some View {
VStack(spacing: 16) {
ForEach(items, id: \.self) { item in
SampleCard(text: item, maxHeight: maxHeight)
}
}
.onPreferenceChange(MaxHeightPreferenceKey.self) { value in
maxHeight = value
}
.padding()
}
}
1. onPreferenceChange
はいつ動作するか?
.onPreferenceChange(MaxHeightPreferenceKey.self) { value in
maxHeight = value
}
この部分は、MaxHeightPreferenceKey.Key
の値(= 各カードの高さ)が変化したときに実行されます。
- 画面初回表示時(カードがレイアウトされたとき)
- データが変わってカードの高さが変化したとき
- 画面サイズや向きが変わってカードの高さが変化したとき
など、カードの高さに変化があったタイミングで自動的に実行されます。
この仕組みにより、すべてのカードの高さを揃えるために「最大の高さ」を動的に計算し、maximumCardHeight
に反映しています。
2. PreferenceKey
の reduce
メソッドの役割
static func reduce(value: inout CGFloat, nextValue: () -> CGFloat) {
value = max(value, nextValue())
}
この reduce
メソッドは、複数のビューから渡された値(この場合は高さなど)をひとつにまとめる役割を持ちます。
この実装では、value
と nextValue()
の 大きい方(最大値) を value
に代入しています。
つまり、複数のカードの高さが報告されたとき、一番大きい高さを最終的な値として採用する、という意味です。
PreferenceKey
の reduce
メソッドの一般的な使用例
✅ 最大値を集める場合
struct MaxHeightPreferenceKey: PreferenceKey {
static var defaultValue: CGFloat = 0
static func reduce(value: inout CGFloat, nextValue: () -> CGFloat) {
value = max(value, nextValue())
}
}
✅ 合計値を集める場合
struct TotalWidthPreferenceKey: PreferenceKey {
static var defaultValue: CGFloat = 0
static func reduce(value: inout CGFloat, nextValue: () -> CGFloat) {
value += nextValue()
}
}
✅ 配列で全ての値を集める場合
struct ItemsPreferenceKey: PreferenceKey {
static var defaultValue: [String] = []
static func reduce(value: inout [String], nextValue: () -> [String]) {
value.append(contentsOf: nextValue())
}
}
3. GeometryReader
と anchorPreference
の使い方
GeometryReader { proxy in
Color.clear
.anchorPreference(
key: ItemsPreferenceKey.self,
value: .bounds
) { anchor in
[proxy[anchor].size.height.description]
}
}
このコードは、子ビューの高さ(size.height
)を親ビューに伝えるための仕組みを実装しています。
-
GeometryReader
は、自身のサイズや位置情報を取得できるビューです。 -
Color.clear
は透明なビューで、見た目には何も表示されません。 -
.anchorPreference(key:value:)
は、ビューの特定の領域(ここでは.bounds
)に関する情報をPreferenceKey
に渡すための修飾子です。 - クロージャ内で
proxy[anchor].size.height
を呼ぶことで、このビューの高さを取得し、PreferenceKey
に渡します。
これにより、親ビューで子ビューのサイズ情報を利用できるようになります。
5. .overlay(DetermineHeight())
の意味
.overlay(DetermineHeight())
これは、各カード(または各セル)の上に DetermineHeight
ビューを重ねて表示しています。
-
DetermineHeight()
は、自分自身の高さをPreferenceKey
経由で親ビューに伝えるためのビューです。 -
.overlay()
を使ってそれをカードに重ねることで、カード自体の高さを計測し、その情報を親ビューに渡すことができます。
この仕組みにより、親ビューで 「全カードの最大高さを揃える」 などのレイアウト調整が可能になります。