SwiftUIでアプリ実行中に動的にビューの大きさを取得するには、GeometryReader
と組み合わせる方法があります。
この記事では具体的な方法とコード例を解説します。
ビューのサイズをコンソールに出力する
GeometryReader
と組み合わせて、ビューの大きさを取得するには、次のようなコードを書きます。
struct ContentView: View {
var body: some View {
Text("Hello, world!")
.padding()
.background() {
GeometryReader { geometry in
Path { path in
print("Text frame size = \(geometry.size)")
}
}
}
}
}
このコードを実行すると、次のようにコンソールにText
の大きさが出力されます。
Text frame size = (126.66666666666666, 52.33333333333333)
背景にGeometryReaderを置く
SwiftUIで自分自身の大きさを知ることが出来るビューはGeometryReader
です。そこで、background
モディファイアを使って、大きさを取得したいビューのにGeometryReader
を配置します。background
モディファイアを使って配置すると、GeometryReader
はそのビューの大きさと同じ大きさになります。
geometry
はGeometryProxy
という構造体です。GeomertyProxy.size
プロパティにビューの大きさが入っているので、その値を取得します。
オーバーレイでも可能
background
モディファイアの代わりにoverlay
モディファイアを使っても同様に大きさを取得できます。状況によって使い分けるのも良いと思います。
実装例 ラベルに表示する
実際に使ったコードを実装してみます。コンソールに出力するのではなく、別のText
に値を表示するというコードを実装してみましょう。次のようなコードになります。
struct ContentView: View {
@State private var labelSize: CGSize = CGSize()
var body: some View {
VStack {
Text("Hello, world!")
.padding()
.background() {
GeometryReader { geometry in
Path { path in
let size = geometry.size
DispatchQueue.main.async {
if self.labelSize != size {
self.labelSize = size
}
}
}
}
}
Text("Label Size : \(String(format: "%.0f x %.0f", labelSize.width, labelSize.height))")
}
}
}
このコードを実行すると、次のように2つラベルが表示され、上側のラベルの大きさが、下側のラベルに表示されます。
ビュー構築中に取得したサイズを使ってビューを変更できない
Text
に取得した大きさを表示するため、取得した大きさはlabelSize
プロパティに代入しています。この値を使ってText
の表示内容を変更したいので、@State
アトリビュートを付けて、SwiftUIにlabelSize
を管理させています。
注意しなければいけないのは、GeometryReader
によってビューの大きさを取得した瞬間は、まだ、ビューの構築処理中です。labelSize
プロパティが変更されると、ビューの再構築が必要になりますが、再構築中に変更すると再構築ができず、エラーとなり、取得した値を使ってText
を表示することができません。
実際に行ってみると、Xcodeのライブプレビューでは動作するのですが、iOSシミュレータや実機で実行するとサイズが表示されません。
iOSシミュレータや実機で実行すると、Xcodeにも次のようなエラーメッセージが表示されます。
Modifying state during view update, this will cause undefined behavior.
そこで、DispatchQueue.main.async
メソッドを使って、再構築処理が終わった後にプロパティに値を代入させるようにしています。プロパティが更新されると、ビューの再構築が行われます。