[SwiftUI] SwiftUI の layout システムを理解する (コンテナビューに含まれる push-out / pull-in タイプビューの調停)

SwiftUI2021

SwiftUI で コンテナを使って階層化された時のレイアウトの仕組みを説明します。

環境&対象

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

  • macOS Monterey 12.1 RC
  • Xcode 13.2 RC
  • iOS 15.2

前回の復習

前回は、push-out タイプのビューが複数あるときのレイアウトされ方を確認しました。

SwiftUI2021[SwiftUI] SwiftUI の layout システムを理解する (push-out タイプビューの調停)

今回は、階層化された時のレイアウトを見ていきます。

コンテナに含まれた push-out タイプのビュー

まず、push-out タイプのビューが、コンテナに含まれた時のレイアウトを見ていきます。

# コンテナとして、VStack を使って確認していきます。他のコンテナも VStack と同等の動きをするかは未確認です。

例えば、以下のような指定をされた時に、Color.red と Color.yellow は、同じサイズの領域になるかどうかです。


VStack {
  Color.red
  VStack {
    Color.yellow
  }
}

1つの push-out タイプビューを持つ コンテナビュー

先ほどのコードを動かして、確認してみます。

2ColorsWithVStack


//
//  ContentView.swift
//
//  Created by : Tomoaki Yagishita on 2021/12/09
//  © 2021  SmallDeskSoftware
//

import SwiftUI

struct ContentView: View {
    var body: some View {
        VStack(spacing:0) {
            Color.red
                .overlay{
                    GeometryReader { geom in
                        Text("\(geom.size.width) - \(geom.size.height)")
                    }
                }
            VStack(spacing:0) {
                Color.yellow
                    .overlay{
                        GeometryReader { geom in
                            Text("\(geom.size.width) - \(geom.size.height)")
                        }
                    }
            }
        }
    }
}

レイアウト結果は、VStack に 2つの Color が含まれている時と同じです。

VStack の内側に、1つの push-out タイプのビューが含まれているときは、その VStack も push-out タイプとして扱われるようです。

2つの push-out タイプビューを持つ コンテナビュー

以下のように、VStack の内側に 2つの Color を含めてみます。


VStack {
  Color.red
  VStack {
    Color.yellow
    Color.green
  }
}

# ここから少し興味深い(?)、動きとなります。

3ColorsWithVStack


//
//  ContentView.swift
//
//  Created by : Tomoaki Yagishita on 2021/12/09
//  © 2021  SmallDeskSoftware
//

import SwiftUI

struct ContentView: View {
    var body: some View {
        VStack(spacing:0) {
            Color.red
                .overlay{
                    GeometryReader { geom in
                        Text("\(geom.size.width) - \(geom.size.height)")
                    }
                }
            VStack(spacing:0) {
                Color.yellow
                    .overlay{
                        GeometryReader { geom in
                            Text("\(geom.size.width) - \(geom.size.height)")
                        }
                    }
                Color.green
                    .overlay{
                        GeometryReader { geom in
                            Text("\(geom.size.width) - \(geom.size.height)")
                        }
                    }
            }
        }
    }
}

想像していた動作でしょうか?

以下のような振る舞いと考えると理解できるレイアウトになりそうです。

  1. Step1:一番上の VStack には、Color と VStack がある(一番上の VStack を VStack-outer と呼び、内側の VStack を VStack-inner と呼びます)
  2. Step2:VStack-inner を確認すると、push-out タイプのビューを持っているので、VStack を push-out タイプとして扱う
  3. Step3:つまり、VStack-outer には、2つの push-out タイプビューが含まれているので、領域を2つに分けて、それぞれに与える
  4. Step4:VStack-inner をレイアウトしようとすると2つの push-out タイプのビューがあるので、利用可能な領域を2つに分けて、それぞれ(Color.yellow と Color.green)に与える
  5. Step5:レイアウト完了

ここで、疑問が湧きます?素の VStack は、push-out なのか? pull-in なのか?

pull-in タイプのみを持つ VStack で確認していきます。

pull-in タイプのビューをもつコンテナビュー

今度は、内側のVStack に pull-in タイプのビューである Text のみを配置してみます。


VStack {
  Color.red
  VStack {
    Text
  }
}

結果は以下のようになります。

TextInVStack


//
//  ContentView.swift
//
//  Created by : Tomoaki Yagishita on 2021/12/09
//  © 2021  SmallDeskSoftware
//

import SwiftUI

struct ContentView: View {
    var body: some View {
        VStack(spacing:0) {
            Color.red
                .overlay{
                    GeometryReader { geom in
                        Text("\(geom.size.width) - \(geom.size.height)")
                    }
                }
            VStack(spacing:0) {
                Text("Hello").frame(height: 30).frame(maxWidth: .infinity)
                    .overlay{
                        GeometryReader { geom in
                            Text("\(geom.size.width) - \(geom.size.height)")
                        }
                    }
            }
        }
    }
}

どうやら、VStack は常に、 push-out ではなく、内側に pull-in タイプのビューのみがあるときは、pull-in として扱われるようです。

次に湧いてくる質問は、"push-out と pull-in の両方のタイプが含まれた VStack は?" ですよね。

push-out タイプのビューと pull-in タイプのビューの両方を持つコンテナビュー

両方のタイプが含まれる VStack は、どのような配置になるでしょうか?確認してみます。


VStack {
  Color.red
  VStack {
    Text
    Color.yellow
  }
}

結果は、push-out タイプの扱いでした。

TextColorInVStack

ですが、よく確認してください。Color.red と Color.yellow は、同じ高さではありません。

Color.red と Text + Color.yellow の高さが同じになっていることがわかります。

ですので、レイアウトの動きとしては、以下のような感じになっていそうです。

  1. Step1:一番上の VStack には、Color と VStack がある(一番上の VStack を VStack-outer と呼び、内側の VStack を VStack-inner と呼びます)
  2. Step2:VStack-inner を確認すると、pull-in タイプのビューと push-out タイプのビューの両方を持っているので、VStack を push-out タイプとして扱う
  3. Step3:つまり、VStack-outer には、2つの push-out タイプビューが含まれているので、領域を2つに分けて、それぞれに与える
  4. Step4:VStack-inner をレイアウトしようとすると pull-in タイプのビューと push-out タイプのビューがあるので、pull-in タイプには、必要とする領域を与え、残りを push-out タイプのビューに与える
  5. Step5:レイアウト完了

領域分割を考える対象が異なるので、以下のようなコードは、異なるレイアウトになるということです。


VStack {
  Color.red
  Text
  Color.yellow
}

VStack {
  Color.red
  VStack {
    Text
    Color.yellow
  }
}

今回は、push-out / pull-in タイプのビューがコンテナビューに含まれた時のレイアウトを確認しました。

わかったこと/わかっていないこと

  • わかったこと1:コンテナビュー(VStack のみ確認) は、push-out タイプのビューを含んでいる場合は、push-out タイプとして処理される
  • わかったこと2:コンテナビュー(VStack のみ確認) は、pull-in タイプのビューを含んでいる場合は、pull-in タイプとして処理される
  • わかったこと3:コンテナビュー(VStack のみ確認) は、push-out タイプのビューと pull-in タイプのビューの両方を含んでいる場合は、push-out タイプとして処理される
  • わかっていないこと1:VStack でのみ確認しているので、他のコンテナビューの振る舞いは不明
  • わかっていないこと2:領域が不足している時の振る舞い

まとめ: SwiftUI の layout システムを理解する (コンテナに含まれる push-out / pull-in タイプビューの調停)

SwiftUI の layout システムを理解する (push-out タイプビューの調停)
  • コンテナビュー(VStack のみ確認) は、push-out タイプのビューを含んでいる場合は、push-out タイプとして処理される
  • コンテナビュー(VStack のみ確認) は、pull-in タイプのビューを含んでいる場合は、pull-in タイプとして処理される
  • コンテナビュー(VStack のみ確認) は、push-out タイプのビューと pull-in タイプのビューの両方を含んでいる場合は、push-out タイプとして処理される

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

SwiftUI 学習におすすめの本

SwiftUI 徹底入門

SwiftUI は、グラフィカルなライブラリということもあり、文字だけのテキストよりは、画像が多く入れられた書籍を読むと理解が進みやすいです。

自分で購入した中でおすすめできるものとしては、以下のものです。

2019 年発表の SwiftUI 1.0 相当を対象にしているので、2020/2021 に追加された一部の機能は、説明されていません。

ですが、SwiftUI 入門書としては、非常によくできていますし、わかりやすいです。 この本で学習した後に、追加分を学習しても良いと思います。

SwiftUIViewsMastery

英語での説明になってしまいますが、以下の本もおすすめです。

1ページに、コードと画面が並んでいるので、非常にわかりやすいです。

View に適用できる modifier もわかりやすく説明されているので、ビューの理解だけではなく、どのような装飾ができるかも簡単にわかります。

超便利です

SwiftUIViewsMastery

販売元のページは、こちらです。

コメントを残す

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