「 GeometryReaderってどうやって使うの…? 」

「 なんだか使い辛くて苦手意識がある… 」

SwiftUIの学習をしていく中で、「この複雑なデザインやレイアウト、どうやって実装するんだろう?」とソースコードを見てみた時、多くの場面でGeometryReader(ジオメトリリーダー)が使われているのではないでしょうか。

GeometryReaderには他の一般的なビューコンテナとは少し異なる仕様やルールを持っていますが、うまく活用することで実装できるViewレイアウトの幅は大きく広がります。

本記事では、「GeometryReaderとは」「基本的な使い方」から、「使用時の注意点やポイント」「具体的な実装例」についても触れていきます!

[ 本記事はこんな人におすすめ ]

・SwiftUIでアプリ開発をしている

・GeometryReaderに苦手意識がある

・GeometryReaderを開発に取り入れたい

環境・バージョン

  > Swift 5.7.2

  > Xcode 14.2

  > macOS 13.0 Ventura

GeometryReaderは、SwiftUIでレイアウトを構築するためのViewプロパティの一つで、GeometryReader自身を保持しているビューコンテナ、つまり親ビューのサイズや位置を検出し、子ビューのレイアウトを調整するために使用されます

クロージャ内の引数geometryには、自身を保持するView(親ビュー)の「サイズ」と「座標空間」が格納されています。(引数は任意の命名が可能です。本記事では引数名をgeometryで進めていきます。)

【基本的な記述】

				
					struct ContentView: View {
    var body: some View {

        GeometryReader { geometry in
            // geometry.size       -> サイズを取得
            // geometry.frame(in:) -> 座標空間を取得
        }

    } // body
} // View
				
			

この引数geometryを元に、サイズを取得する場合は「geometry.size」、座標を取得する場合は「geometry.frame(in:)」を用いてレイアウトを調節します。

では、後述で詳しくGeometryReaderの使い方を見ていきましょう。

本記事のソースコード

本記事で使用しているサンプルコードはGitHubにて公開されています。

コード内容の確認にぜひご利用ください🍎

GitHubのソースコードを見に行く>>

基本的な使い方

基本1: 親ビューのサイズ情報を取得する

GeometryReaderは自身を保持するビュー、親ビューのサイズ情報を取得することができます。クロージャ内の引数を使ってgeometry.sizeとすることで親ビューのサイズ情報を取得します。

以下のコード例では、GeometryReaderの親ビューはbody、つまりContentViewであるため、geometry.sizeにはContentViewの幅(width)と高さ(height)が格納されています。

				
					struct ContentView: View {
    var body: some View {

        GeometryReader { geometry in // ⬅︎ 親ビューはContentView
            VStack {
                Text("親ビューの横幅: \(geometry.size.width)")
                Text("親ビューの高さ: \(geometry.size.height)")
            }
        }
    } // body
} // View
				
			

以下はiPhone14のシミュレータで出力を確認した結果です。

ContentViewは画面全体を描画しているため、iPhone14のセーフエリア内の横幅は390、高さは763であることがわかります。

iPhone14シミュレータ結果

以下のように、情報取得対象のビューがignoresSafeAreaなどの機能によってセーフエリア外まで描画されていた場合、セーフエリア外の高さも含めて取得します。

セーフエリア外の描画も反映される

POINT

GeometryReader内にある子ビューの配置が左上になっているのは、GeometryReaderが持つ仕様によるものです。詳しくは後述の「使用時のポイントと注意点」にて解説します。

また、GeometryReaderはStackビューをはじめとした様々なビューコンテナの情報を取得することが可能です。

例えば以下のようなコードの場合、GeometryReaderを保持する親ビューはVStackとなります。つまり、引数geometryにはVStackの情報が格納されます。

VStackの横幅300、高さ300が取得されている

GeometryReader内の子ビューの配置原点は自身を保持するビューの左上となるので、この場合VStackが持つ範囲(緑枠)の左上に配置されます。

このように、どのViewを親ビューとして情報を取得しているのかを把握することがGeometryReaderを使う上での肝となります。

基本2: 親ビューを元にした座標情報を取得する

geometry.frame(in:)とすることで、自身を保持する親ビューを元にした座標情報を取得することができます。

取得値にはX軸(横)とY軸(縦)があり、それぞれ「min(始点)」「mid(中間点)」max(終点)」の座標取得が可能です。

座標取得にはローカル座標グローバル座標の二つから選択可能で、これら二つのプロパティは座標計測時の始点に違いがあります。

それぞれの使い方を見ていきましょう。

・ローカル座標の取得(.local)

geometry.frame(in: .local)とすることで、GeometryReaderを保持しているビューの左上端を計測始点として、ある座標地点までの距離を取得します。

【ローカル座標の計測始点】

以下のコードは、geometry.frame(in: .local)でX軸のローカル座標を計測した例です。

座標取得対象である親ビューVStackにはframe(width: 300, height: 300)が設定されており、この場合GeometryReaderが取得したローカル座標minXは0、midXは150、maxXは300となります。

また、ローカル座標取得においての座標計測始点とmin座標の地点は常に同じ位置である(Viewの左上端)ことから、「minX」「minY」の値は0となります。

・グローバル座標の取得(.global)

geometry.frame(in: .global)とすることで、画面の左上端を計測始点としてビューの座標を取得します。つまり、画面上から見たビューの位置座標を知ることができます。

【グローバル座標の計測始点】

以下のコードはgeometry.frame(in: .global)を使用してX軸のグローバル座標値を計測した例です。

 この場合、親ビューVStack(緑枠)のグローバル座標minXは45、midXは195、maxXは345となります。

geometry.frame(in: .global)によるグローバル座標値は、ビューの画面移動があった場合もリアルタイムで現在の座標値が取得されます。この機能を活用することで、動的で複雑なアニメーションやレイアウトが実装できます。

ScrollViewによる座標の変化

使用時のポイントと注意点

GeometryReaderはSwiftUIにおける他のビューコンテナとは少し異なる仕様やルールがあり、使用時に注意しておくポイントが存在します。

GeometryReaderに対して苦手意識がある方の多くは、おそらくこれらの特徴によるものではないかと思います。

以下に挙げるポイントを押さえておきましょう。

ポイント1: 自身を保持するView(親ビュー)の情報を取得する

前項目でも解説したように、GeometryReaderを使うときはどのビューの情報(サイズ、座標)を取得しているのかを把握することが重要であり、対象ビューが変われば取得値も大きく変わります。これは本記事全体においての肝となります。

以下のコードの場合だと、GeometryReaderを保持している親ビューはbody、つまりContentViewなので、GeometryReaderはContentViewの情報を取得します。

				
					struct ContentView: View {
    var body: some View {

        GeometryReader { geometry in
            // geometry.size       -> ContentViewのサイズ情報
            // geometry.frame(in:) -> ContentViewの座標情報
        }

    } // body
} // View
				
			

次に以下のようなコードの場合、自身を保持している親ビューはVStackとなるので、GeometryReaderは親ビューであるVStackの情報を取得します。つまり、引数geometryにはVStackコンテナのサイズと座標情報が格納されます。

				
					struct ContentView: View {
    var body: some View {

        VStack {
            GeometryReader { geometry in
                // geometry.size       -> VStackのサイズ情報
                // geometry.frame(in:) -> VStackの座標情報
            }
        }

    } // body
} // View
				
			

また、以下のようにカスタムViewパーツ自身にGeometryReaderが定義されているようなパターンもよく使われる方法です。

この場合、GeometryReaderを保持している親ビューはContentView側のVStackとなります。

				
					struct ContentView: View {
    var body: some View {

        VStack { // ⬅︎ CustomRectangleの親ビュー
            CustomRectangle()
        }
        .frame(width: 300, height: 200)

    } // body
} // View

// カスタムViewパーツ
struct CustomRectangle: View {
    var body: some View {

        GeometryReader { geometry in
            Rectangle()
                .foregroundColor(.green)
                .frame(width: geometry.size.width,
                       height: geometry.size.height)
        }
    }
}
				
			

親ビューVStackに指定されているサイズ値をカスタムViewパーツ側のGeometryReaderが取得していることがわかりますね。

カスタムViewパーツ側からの情報取得

このように、定義する場所によって自由に任意のViewのサイズや座標を取得できるのがGeometryReaderの強力な点であり、理解していないとつまづくポイントの一つです。

ポイント2: 子ビューの配置原点が変化する

GeometryReaderで囲まれたView(子ビュー)は、親ビューの左上端が配置の原点なります。

「GeometryReaderを使うと配置がズレてしまう!」という場合の多くは、この仕様が影響していると思われます。

【GeometryReaderを使わない時】

【GeometryReader使用時】

通常時は親ビューの中央に置かれるビューが、GeometryReaderで囲まれたことによって配置原点が左上端に移動していることがわかりますね。

GeometryReaderに囲まれた子ビューの配置を中央に持っていきたい場合は、主に以下のような方法があります。

方法1. 親ビューのサイズ値を子ビューのframeに渡す

GeometryReaderが取得した親ビューのサイズ情報を子ビューのframe値として渡すことで、子ビューの配置が中央になります。

以下の例では、GeometryReaderの子ビューであるVStackコンテナに対して、親ビューのサイズ情報geometry.sizeを渡しています。

ちなみに親ビューの横幅と高さは、ローカル座標からの取得も可能です。

ローカル座標はビューの左上原点「min」地点から、ある座標地点までの距離を取得することから、サイズ取得と似た性質であると言えます。

				
					// ⬇︎同じ値
geometry.size.width
geometry.frame(in: .local).maxX

// ⬇︎同じ値
geometry.size.height
geometry.frame(in: .local).maxY

// ⬇︎同じ値
geometry.size.width / 2
geometry.frame(in: .local).midX

// ⬇︎同じ値
geometry.size.height / 2
geometry.frame(in: .local).midY
				
			

方法2. positionモディファイアで座標を指定する

.position(x:,y:)モディファイアは、親の座標空間において左上端を原点とし、指定された座標にビューの中心を位置づけます。

以下の例では、引数(x, y)それぞれに親ビューの幅と高さの半分値を設定することで、親であるContentViewの中央に配置させています。

GeometryReaderの実装例

グローバル座標を用いた動的なアニメーション

以下の実装例はSwiftUIのScrollViewとグローバル座標を組み合わせたビューのアニメーションです。

ScrollView内の要素それぞれがGeometryReaderを持っており、自身のグローバル座標(画面から見た自身の座標)をもとにした回転アニメーションが付与されています。

.rotation3DEffectの回転角度としてグローバル座標のminY値を渡しており、図形それぞれが持つグローバル座標値の差分によって連動的な回転アニメーションが表現されています。

このように、GeometryReaderによる動的な座標取得を活用することで複雑なアニメーションを作ることが可能です。

複数要素それぞれがGeometryReaderを持つ場合のビュー同士の間隔について

上記で紹介した実装例では、複数のビュー要素それぞれがGeometryReaderを持っていましたね。

このような場合、ビュー同士の間隔を設定するにはGeometryReader自身に対してフレーム値を設定する必要があります。

実装例では、GeometryReader自身にフレーム値を設定することで各ビュー要素に高さ40の間隔を取っています。

GeometryReader自身にフレーム値を設定

GeometryReader自身にフレーム値が設定されていなかった場合、以下のようにビュー同士が十分な間隔を開けることなく、重なったような状態になります。

GeometryReaderにフレーム値が設定されていない場合ビュー同士は十分な間隔を取らない

GeometryReader内の子ビューに対してフレーム値を設定した場合、ビュー単体のレイアウト自体は指定した高さを取りますが、ビュー同士の間隔にフレーム値は反映されず無視されます。

子ビューに対してフレームを設定してもビュー同士の間隔には反映されない

GeometryReaderを持つビュー同士の間隔を広げることができない」という場合は、GeometryReader自身に間隔値を設定する必要があるかどうかを検討してみてください。

ちなみに上記の例ではフレーム値によって間隔を取りましたが、.paddingなどの機能によっても有効に反映されます。

まとめ

以上、SwiftUIのView機能「GeometryReader」についてでした!

要点をおさらいしておきます。

自身を保持するView(親ビュー)の情報を取得する

・「サイズ」と「座標」を使ってレイアウトを調整する

・自身が持つ子ビューの配置原点が左上端に変化する

GeometryReaderは、その特徴的な仕様や振る舞いから苦手意識を持つ方も少なくないかも知れませんが、本記事の内容を理解しておけば以降のGeometryReader使用ソースコードの解読が捗るかと思います!

本記事がお役に立てれば幸いです。

\  SHARE  /

Twitter
Facebook
Email

✏️ アプリ開発が学べる勉強会を開催中! 

CodeCandyではアプリ開発を学ぶための勉強会を定期開催しています。
学習する習慣を身につけたい、他の参加者と作業したい、アプリ開発の基本をマスターしたい、という方のために無料で学べる勉強会です。
グループにメンバー登録して頂くと、イベント開催時にメールで通知されます。

▶️グループのメンバーとして参加する

徹底した基礎学習からマスターするiPhoneアプリ開発集中オンライン講座開講!

本書「iPhoneアプリ開発集中講座」を執筆している現役エンジニア講師陣が直接に指導!
基礎、課題実習で実践力を鍛えて、オリジナルアプリ公開までチャレンジ!
充実した転職支援もあるので、エンジニアへ転職したい人にもおすすめです!

まずは、現役エンジニアに相談できる無料相談をご利用ください。

By 中川 賢亮

2022年2月よりSwift学習を始め、4月からiOSアプリ開発オンラインスクール「CodeCandy」にてアプリ開発を学ぶ。 2023年10月に個人開発アプリ「unico」をリリース。現在はアプリの機能アップデートをしながら、スクール運営の技術ブログの執筆や、出版書籍の入稿チェック・デバッグにも携わる。