【SwiftUI】GeometryReaderで座標を使ってViewを配置する

未分類

SwiftUIではVStackなどで位置関係だけを指定し、座標を使わずにViewを配置する事が多いです。
こうする事で画面サイズが変わってもレイアウトが崩れ難くなる為です。

しかし、座標を使ってViewを配置したい場合もあります。
そう言った場合にGeometryReaderを使います。

GeometryReaderを使う事で自身のサイズや座標値を取得し、
その値を使ってViewを配置していく事ができます。

GeometryReaderを使ってみる

まずはシンプルにGeometryReaderを使って座標を取得してみましょう。

struct GeometryReaderSampleView: View {
    var body: some View {
        GeometryReader{ geometry in
            Text("width:\(geometry.size.width), height:\(geometry.size.height)")
        }
    }
}

使い方としてはGeometryReaderで囲って中にViewを配置するだけです。
サイズや座標値を受け取るgeometry inが必要になります。

このgeometryでsizeが取得できます。
まずは単純に表示をしています。

このようにViewのサイズを表示する事ができました。
GeometryReaderの中では必ず左上(原点)に合わせて配置されます。

これだけだと、どこまでがGeometryReaderか分かりにくいので色と枠線をつけてみましょう。

struct GeometryReaderSample2View: View {
    var body: some View {
        GeometryReader{ geometry in
            Text("width:\(geometry.size.width), height:\(geometry.size.height)")
        }
        .border(.black ,width: 2)
        .background(Color.mint)
    }
}

枠線がGeometryReaderの範囲です。
標準ではSafeAreaは含まれていません。

ただし、今回はbodyにそのまま配置し、親Viewも無い状態なので背景は全体に広がりました。
GeometryReaderはFormなどと同様に可能な限り大きく表示されるViewです。
VStackやTextなどの可能な限り小さく表示されるViewとは異なるので注意して下さい。

Viewを配置する

次はViewを配置して行きましょう。

まずは先程のTextを2つに分けました。

struct PileView: View {
    var body: some View {
        GeometryReader{ geometry in
            Text("width:\(geometry.size.width)")
            Text("height:\(geometry.size.height)")
        }
        .border(.black ,width: 2)
        .background(Color.mint)
    }
}

GeometryReaderでは全て左上に配置されるので重なってしまいました。
当然ですがZStackを使っていません。
問題なく見えるようにするには何らかの方法でずらさないといけません。

座標を使って表示したいのでoffset Modifierを使います。
このoffsetはViewの表示位置をずらす事が出来るModifierです。
GeometryReader直下では原点基準でViewが配置されるので座標で配置する事と同等になります。

struct OffsetView: View {
    var body: some View {
        GeometryReader{ geometry in
            Text("width:\(geometry.size.width)")
            Text("height:\(geometry.size.height)").offset(x:20,y:30)
        }
        .border(.black ,width: 2)
        .background(Color.mint)
    }
}

2つ目のTextにoffsetを追加しました。
xyで指定する事ができ、どちらかを省略する事もできます。
またCGSizeも使用できるので変数に値を保持する場合はこちらが使いやすいでしょう。

座標とStackを組み合わせる

当然ですが各種Stackと組み合わせる事もできます。
またgeometryで取得した値も使用してみましょう。

struct StackAndGeometryView: View {
    var body: some View {
        GeometryReader{ geometry in
            HStack {
                Spacer()
                
                VStack {
                    Text("width:\(geometry.size.width)")
                    Text("height:\(geometry.size.height)")
                }
                .offset(y:geometry.size.height/4)
                
                Spacer()
            }
        }
        .border(.black ,width: 2)
        .background(Color.mint)
    }
}

横はHStackを使って中央にし、縦は1/4の位置を指定しました。
また2つのTextはVStackで並べています。

このように上手く組み合わせる事で自由自在にViewを配置できます。

なお、HStack使わずともgeometry.size.width/2でも良さそうに見えますが、
offsetではViewの左上が基準点となっているのでViewが右にズレます。

Global座標を扱う

先程まではGeometryReaderを基準としてsizeを扱っていました。
しかし画面基準で座標を扱いたい時もあると思います。

そう言った時はgeometry.frame(in: .global)を使います。
frameではlocalおよびglobal座標でのCGRectが取得できます。

struct GlobalView: View {
    var body: some View {
        GeometryReader{ geometry in
            VStack {
                Text("width:\(geometry.size.width)")
                Text("height:\(geometry.size.height)")

                Divider()
                
                VStack {
                    Text("frame(in: .local).minX:\(geometry.frame(in: .local).minX)")
                    Text("frame(in: .local).minY:\(geometry.frame(in: .local).minY)")
                    Text("frame(in: .local).maxX:\(geometry.frame(in: .local).maxX)")
                    Text("frame(in: .local).maxY:\(geometry.frame(in: .local).maxY)")
                }

                Divider()

                VStack {
                    Text("frame(in: .global).minX:\(geometry.frame(in: .global).minX)")
                    Text("frame(in: .global).minY:\(geometry.frame(in: .global).minY)")
                    Text("frame(in: .global).maxX:\(geometry.frame(in: .global).maxX)")
                    Text("frame(in: .global).maxY:\(geometry.frame(in: .global).maxY)")
                }
            }
        }
        .border(.black ,width: 2)
        .background(Color.mint)
    }
}

数字を見ると分かりますがglobalではSafeAreaも込みになっている為、Y座標が異なります。
当然ですがGeometryReaderと他のViewが並んでいればそれを加味した値になります。

なお先程のsizeの代わりにframe(in: .local)を使っても構いません。
frameはCGRectなのでmidXYが取得できます。
localで使う場合は然程変わらないので用途に合わせて使用して下さい。

最後に

GeometryReaderを使ってViewを配置しました。
GeometryReaderでは原点基準の配置になる事を覚えて置いて下さい。

また今回の内容で分かる通り、GeometryReader自身に関する値が取得できます。
他のViewの値が読み取れる訳ではないので注意して下さい。

GeometryReader内でも別なViewのサイズが欲しいなら、
その位置にもう一つGeometryReaderが必要になります。

コメント

タイトルとURLをコピーしました