0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

SwiftUI PreferenceKey の使い方まとめ

Last updated at Posted at 2025-08-01

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. PreferenceKeyreduce メソッドの役割

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

この reduce メソッドは、複数のビューから渡された値(この場合は高さなど)をひとつにまとめる役割を持ちます。

この実装では、valuenextValue()大きい方(最大値)value に代入しています。

つまり、複数のカードの高さが報告されたとき、一番大きい高さを最終的な値として採用する、という意味です。


PreferenceKeyreduce メソッドの一般的な使用例

✅ 最大値を集める場合

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. GeometryReaderanchorPreference の使い方

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() を使ってそれをカードに重ねることで、カード自体の高さを計測し、その情報を親ビューに渡すことができます。

この仕組みにより、親ビューで 「全カードの最大高さを揃える」 などのレイアウト調整が可能になります。


0
0
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
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?