[SwiftUI] SwiftUI の layout システムを理解する (基礎編: レイアウトシーケンス)

SwiftUI2021

SwiftUI のレイアウトを説明します。今回は、レイアウトの際の基本的な動作を説明します。

環境&対象

以下の環境で動作確認を行なっています。

  • macOS Monterery beta 4
  • Xcode 13 beta4
  • iOS 15 beta

SwiftUI の レイアウト

SwiftUI でどのようにレイアウトされるかを確認するために、シンプルな例から見ていきます。

シンプルな例

一番シンプルな例から見ていきます。


//
//  LayoutApp.swift
//
//  Created by : Tomoaki Yagishita on 2021/07/29
//  © 2021  SmallDeskSoftware
//

import SwiftUI

@main
struct LayoutApp: App {
    var body: some Scene {
        WindowGroup {
            Text("Hello, world!")
        }
    }
}

Hello, world! というテキストを表示するアプリです。

HelloWorld
HelloWorld

親ビュー と 子ビュー のやりとり

このとき、親ビュー(WindowGroup)と子ビュー(Text) とは、以下のようなやりとりがされています。

以下の例では、デバイスとして iPhone11 が使われたケースで説明しています。

sequenceDiagram autonumber WindowGroup->>Text: おすすめサイズは(414.0 x 814.0)だけど, 必要なサイズは? Text->>WindowGroup: (94.5 x 20.5) あれば、良いです WindowGroup-->>WindowGroup: Text を配置しよう

上記のやり取りを通して、レイアウトが確定します。

やりとり詳細
  1. WindowGroup が渡すサイズは、iPhone11 のスクリーンサイズです
  2. Text は、"Hello, world!" を配置するのに必要なサイズ(94.5x20.5)を返します
  3. WindowGroup は、Text から受け取った情報をもとに配置します
やりとり詳細(補足)
  1. 一番 Root にあるためにスクリーンサイズ全体が渡されます
  2. Text は、自分が必要とする以上に領域を使用しない pull-in タイプのビューであるため、上位から自分が必要とするサイズより大きな値を渡されても、余分に領域を確保するような振る舞いはとりません
  3. ドキュメントに明確な記載はありませんが、WindowGroup は、子ビューを領域の中心に配置するようです

上記のレイアウト作業の結果として、画面中央に、テキストにちょうどのサイズで Hello, world! が表示される結果となります。

Note: WindowGroup は、1つのビューしか子要素として持ちません。

どうやって、おすすめサイズの値や実際の Text のサイズを確認するのか

サンプルのコードでは内部でやり取りされて処理されてしまうために、そのままでは親ビュー(WindowGroup)からの提案したサイズや子ビュー(Text) の返したサイズは確認できません。

レイアウト確認につかう GeometryReader

GeometryReader という便利なビューがあります。Apple のドキュメントは、こちら

この GeometryReader には、上位ビューから提案されたサイズが渡されます。

.background 内で使用されると .backgroud が対象としているビューがもつサイズが渡されてきます。

以下のようにすることで、WindowGroup から Text に提案されるサイズ と Text が使用するサイズ を確認することができます。


//
//  LayoutApp.swift
//
//  Created by : Tomoaki Yagishita on 2021/07/29
//  © 2021  SmallDeskSoftware
//

import SwiftUI

@main
struct LayoutApp: App {
    var body: some Scene {
        WindowGroup {
            // (1)
            GeometryReader { geom in
                Text("Hello, world!")
                    .background(
                        // (2)
                        GeometryReader{ geom in
                            Color.clear
                                // (3)
                                .debugPrint("size in background: \(geom.size)")
                        })
                     // (4)
                    .debugPrint("size from GeometryReader:\(geom.size)")
            }
        }
    }
}
コード解説
  1. WindowGroup が Text に提案するサイズを確認するために、その間に入るように GeometryReader を配置します
  2. Text に付与した .background 内に GeometryReader を配置することで、Text が使用するサイズを確認できます
  3. (2) で定義した サイズ を print しています
  4. (1) で渡された サイズ を print しています

Xcode のコンソールには 以下のようにプリントされます。


size from GeometryReader:(414.0, 814.0)
size in background: (94.5, 20.5)

なお、GeometryReader を WindowGroup と Text の間に入れたため、最初の例とは異なり、以下のようなレイアウトになります。

GeometryReader
GeometryReader

これは、GeometryReader の子ビューをレイアウトする方法が WindowGroup とは異なるからです。

レイアウト決定時の注記事項

レイアウト決定時には、以下のようなルールが適用されています。

  • 親ビューは、サイズを提案できるが、決定するのは子ビューであり、その値を親ビューは変更できない
  • 子ビューをどこに配置するかは親ビューが決定することで、子ビューは関与できない
  • 親ビューは、子ビューを配置するときに 子ビュー向けに確保した領域で子ビュー全体が表示できるかどうかは気にしない

次回以降で、上記のルールを 実例を見ながら確認していきます。

説明は以上です。
不明な点やおかしな点ありましたら、こちらまで。

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です