SwiftUI の GeometryReader でサイズと位置を取得する

GeometryReader は、親ビューから提案されたサイズや自身の座標空間上の位置を取得できるコンテナビューです。通常の SwiftUI レイアウトでは親子間のサイズ交渉が自動で行われますが、GeometryReader を使うと具体的な数値を取り出してレイアウト計算に利用できます。

基本的な使い方

GeometryReader はクロージャで GeometryProxy を受け取ります。この proxy から size と safeAreaInsets にアクセスできます。

GeometryReader { proxy in
    VStack {
        Text("幅: \(proxy.size.width, specifier: "%.0f")")
        Text("高さ: \(proxy.size.height, specifier: "%.0f")")
    }
}

proxy.size には親から提案されたサイズが入っています。画面回転やウィンドウリサイズに応じて値が変わるため、レスポンシブなレイアウトを組むときの基盤となります。

親のサイズに対する比率でレイアウトする

GeometryReader の典型的な用途は、親の幅や高さに対する割合でビューのサイズを決めることです。

GeometryReader { proxy in
    HStack(spacing: 0) {
        Color.blue
            .frame(width: proxy.size.width * 0.3)
        Color.orange
            .frame(width: proxy.size.width * 0.7)
    }
}
.frame(height: 100)

画面幅の 30% と 70% に分割した 2 カラムレイアウトが実現できます。固定値ではなく比率で指定しているため、どんな画面サイズでも同じ比率が維持されます。

frame(in:) で座標を取得する

GeometryProxy の frame(in:) メソッドを使うと、指定した座標空間におけるビューの CGRect を取得できます。

ScrollView {
    GeometryReader { proxy in
        let frame = proxy.frame(in: .global)
        Color.clear
            .preference(key: OffsetKey.self, value: frame.minY)
    }
    .frame(height: 0)
    
    // コンテンツ
}

.global はスクリーン座標、.local は GeometryReader 自身の座標空間を指します。.named("scrollView") のようにカスタム座標空間を使うこともできます。

proxy.size

親から提案されたサイズ(幅と高さ)を取得する。レイアウト計算に使う

proxy.frame(in:)

指定座標空間でのビューの位置と大きさ(CGRect)を取得する。スクロール位置の検知などに使う

GeometryReader の注意点

GeometryReader は非常に便利ですが、いくつかの癖があることを知っておく必要があります。

まず、GeometryReader は提案されたサイズいっぱいに広がるという特徴があります。通常のビューは自分のコンテンツに必要な分だけのサイズを取りますが、GeometryReader は親が与えた空間を全部占有します。そのため、意図しないレイアウト崩れが起きることがあります。

// 意図しない広がりを防ぐ
GeometryReader { proxy in
    Text("幅: \(proxy.size.width, specifier: "%.0f")")
        .frame(width: proxy.size.width, height: proxy.size.height)
}
.frame(height: 44) // 外側で高さを制限する

また、GeometryReader の子ビューはデフォルトで左上(topLeading)に配置されます。VStack のようにセンタリングされるわけではないので、必要に応じて position や frame で中央寄せを行いましょう。

overlay と組み合わせるパターン

GeometryReader のサイズ全占有を避けるテクニックとして、overlay 内で使う方法があります。

Text("Hello, World!")
    .padding()
    .background(Color.yellow)
    .overlay(
        GeometryReader { proxy in
            Color.clear
                .onAppear {
                    print("サイズ: \(proxy.size)")
                }
        }
    )

この方法なら、Text のサイズがレイアウトの基準となり、GeometryReader はサイズに影響を与えずにビューの寸法だけを取得できます。サイズを「読み取りたいだけ」で「レイアウトに影響させたくない」場面で重宝するパターンです。

iOS 16 以降の代替手段

iOS 16 以降では、GeometryReader を使わなくても .containerRelativeFrame() や Layout プロトコルでサイズを取得・制御できる場面が増えています。GeometryReader は万能ですが、より宣言的な代替手段が使える場合はそちらを優先する方が、コードの見通しがよくなるでしょう。