SwiftUI.GroupをViewのbodyの直下に置いた時の挙動

開発環境

> xcodebuild -version
Xcode 14.0
Build version 14A309

モチベーション

SwiftUIのGroupを具体例を踏まえて理解しようと思って実験していたら意味わからんくなったので記事に残す。

SwiftUI.Group

Group | Apple Developer Documentation

  • HStack, VStack, Sectionのようにviewに影響を与えることなく、複数のviewを一つのインスタンスにまとめられる
  • Groupに適用したmodifierはGroupが持つ全ての子要素に対してそれぞれ影響を及ぼす

Use a group to collect multiple views into a single instance(A), without affecting the layout of those views(B), like an HStack, VStack, or Section would(C).

この文、個人的に手癖で翻訳に突っ込む前に英文読んだほうが良かった文章だった。ABに対してCがかかっているのではなく、Aに対してBCがかかっている。英語読もう。はい。Bの後のカンマがなければABに対してCがかかるようになるのかな。

これが、

import SwiftUI

struct ContentView: View {
    var body: some View {
        VStack {
            Group {
                Rectangle()
                    .frame(width: 50, height: 50)
                    .foregroundColor(.blue)
                Rectangle()
                    .frame(width: 50, height: 50)
                    .foregroundColor(.blue)
                Rectangle()
                    .frame(width: 50, height: 50)
                    .foregroundColor(.blue)
            }
        }
    }
}

こうまとめられる

import SwiftUI

struct ContentView: View {
    var body: some View {
        VStack {
            Group {
                Rectangle()
                Rectangle()
                Rectangle()
            }
            .frame(width: 50, height: 50)
            .foregroundColor(.blue)
        }
    }
}

VStackを剥がすと?

import SwiftUI

struct ContentView: View {
    var body: some View {
        Group {
            Rectangle()
            Rectangle()
            Rectangle()
        }
        .frame(width: 50, height: 50)
        .foregroundColor(.blue)
    }
}

上記を見ると、Rectangle3つの全体に対してframe modifilerがかかっているように見える。ということは必ずしもGroupに付与したmodifierはGroupの子要素に反映されるわけではない?また、上記のGroupをVStackに変えると同様の見た目になった。

import SwiftUI

struct ContentView: View {
    var body: some View {
        VStack {
            Rectangle()
            Rectangle()
            Rectangle()
        }
        .frame(width: 50, height: 50)
        .foregroundColor(.blue)
    }
}

シンプルなVStackの利用のため、この挙動は理解できる。

以上を踏まえると、公式ドキュメントの以下の記述が実情と異なる?

After creating a group, any modifier you apply to the group affects all of that group’s members.

公式ドキュメントのサンプルコード

GroupのドキュメントのOverview項にVStackを使わないGroupのサンプルコードがあるが、手元で試すとGroupの子要素に対してmodifierが効いている。

import SwiftUI


struct ContentView: View {
    var body: some View {
        Group {
            Text("SwiftUI")
            Text("Combine")
            Text("Swift System")
        }
        .font(.headline)
    }
}

また、実装中に気になったのがシミュレーターとプレビューの挙動の違い。シミュレーターはVStack風に垂直方向に要素が積み上がっているが、プレビューだとそれぞれ独立して存在する。

おわりに

本記事の内容だけで判断するとGroupをView.Bodyのルートで持った場合、そのGroupにframe modifierを設定するとGroupの子要素それぞれに対してではなく、Group全体にframe modifierがかかる。

Groupの子要素にframe modifierを適用したい場合はVStackなどで包んであげると良さそう。

参考