【Xcode/SwiftUI】@ViewBuilderをざっくり理解する

1. はじめに

SwiftUI = コード見ただけで画面のイメージ湧くくらいスッキリしてる。

VStack {
    Text("Hello")
    Image(systemName: "star")
    Button("Tap me") {
        print("Tapped!")
    }
}

↑こんな感じで、UIがまるでHTMLやスクリプトみたいに宣言的に書ける。
UIKitの頃の「View作って、addSubviewして、AutoLayout書いて…」ってのが不要になってかなり楽になった。

でも、これがどうして可能なのかというそもそもの疑問:

Swiftの通常の関数 = 基本1つの値しか返せない

でも上のコード、TextもImageもButtonも返してる。

ここで登場するのが――@ViewBuilderという機能

2. @ViewBuilderとは?

一言で言うと

複数のViewをまとめて“返せるようにする”ための属性(アノテーション)

正式には @ViewBuilder は SwiftUI が使ってる
Result Builder(結果ビルダー)」という機能の一種。

SwiftUIでのUIコードを、あの宣言的なスタイルに見せかけてくれてるのがこれ。


なんで必要?

Swiftの関数やクロージャは、普通は1つの値しかreturnできない。

でも、SwiftUIは VStack {} の中に複数のViewを返してる
その “複数のView” をいい感じに組み立てて1つのViewとして返してくれてるのが、@ViewBuilder の仕事。

VStack {
    Text("Hello")
    Text("World")
}

↑これは裏で @ViewBuilder が「Text(“Hello”)とText(“World”)をくっつけて、1つのViewにまとめといたぞ」ってやってくれてる。

どこで使われてるのか?

  • VStack, HStack, ZStack, NavigationStack
  • ForEach, ifswitch を使った条件Viewの切り替え
  • 自分でカスタムViewの中にView返す関数作るとき

3. @ViewBuilderの特徴

Swiftの関数は基本「1つだけ」しか返せない

Swiftでは関数やクロージャの戻り値は、原則1つだけ

func makeView() -> some View {
    Text("Hello")
    Text("World") // ❌ エラー
}

↑みたいに、複数のViewを直列で返すことはできない

SwiftUIのViewは「ツリー構造」

SwiftUIでは画面全体がViewの木(View Tree)になってる。
つまり、「親Viewが子Viewを持って」「その子がまた子を持って」っていうツリー構造

VStack {
    Text("A")
    HStack {
        Text("B")
        Text("C")
    }
}

↑こんな感じで、親の VStack が子として Text, HStack を持って、
HStack がさらに子を持って…って構造になる。

でもこのツリーをコードで自然に書けるのは、裏で @ViewBuilder が「いい感じに木を組み立ててくれてる」から。

if / ForEach も書ける理由

VStack {
    if isLoggedIn {
        Text("Welcome back!")
    } else {
        Text("Please sign in")
    }

    ForEach(items) { item in
        Text(item.title)
    }
}

↑こんな「ロジック入りのUIコード」が書けるのも、
@ViewBuilderifやForEachの中のViewをうまく合成してくれるから。

なかったら、いちいちViewをArrayにしたり、Groupでくくったりして大変。

4. 実際の使用例

✅ 1. VStackで複数のViewを返す例

VStack {
    Text("Hello")
    Text("World")
}

このクロージャ、実は中身が @ViewBuilder になってるから
Textを2つ並べてもOK。


✅ 2. 条件分岐 (if) の例

VStack {
    if showGreeting {
        Text("Hey there!")
    }

    Text("Always here")
}

通常の関数なら、if の中でreturn分けるとかは不可能。
@ViewBuilder なら **「条件がfalseなら何も表示しない」**ってのも自然に扱える。


✅ 3. ループ (ForEach) の例

VStack {
    ForEach(0..<3) { i in
        Text("Item \(i)")
    }
}

これも、ForEach@ViewBuilder によって、複数のTextをViewツリーに展開してくれてる感じ。


✅ 4. カスタムView(関数)で使う例

@ViewBuilder
func greetingView(isHappy: Bool) -> some View {
    if isHappy {
        Text("😁 Happy!")
    } else {
        Text("😐 Meh...")
    }
}

@ViewBuilder を関数につけると、中で複数のViewをreturnできるようになる

これでロジック付きの再利用可能なView関数が作れて、UIがもっと柔軟になる、便利。

5. @ViewBuilderがないとどうなる?

コンパイルエラーになる例

Swiftは普通、関数の戻り値は1つだけ
だから @ViewBuilder なしで複数のViewを返そうとすると…容赦なくエラー

func brokenView() -> some View {
    Text("Hello")
    Text("World") // ❌ エラー:「Unexpected non-void return value in void function」
}

書きづらくなる例

もし @ViewBuilder がなかったら、複数Viewを返すには GroupAnyView
無理やり使って手動でViewを包む必要がある。

func messyView() -> some View {
    Group {
        Text("Hello")
        Text("World")
    }
}

Before / After 比較

Before(@ViewBuilderなし)

func customView(isActive: Bool) -> some View {
    Group {
        if isActive {
            Text("Active")
        } else {
            Text("Inactive")
        }
    }
}

After(@ViewBuilderあり)

@ViewBuilder
func customView(isActive: Bool) -> some View {
    if isActive {
        Text("Active")
    } else {
        Text("Inactive")
    }
}

6. 注意点

1. Viewの数制限(Swift 5.9以前)

@ViewBuilder で返せるViewの数は、最大10個まで(※ Swift 5.9未満)
それ以上になるとコンパイルエラー出るから注意。

VStack {
    Text("1")
    Text("2")
    ...
    Text("11") // ❌ ここで怒られる、→ Swift 5.9以降は緩和されてるけど、10個超えたらViewをまとめる工夫はする
}

🧠 2. 複雑すぎるロジックは避ける

@ViewBuilder の中でガチガチのロジック書くのは避ける。

@ViewBuilder
var body: some View {
    if user.isActive && !user.isBlocked && settings.isFeatureEnabled {
        // ❌ 複雑すぎて読めない
        Text("Welcome!")
    }
}

// → ビジネスロジックはなるべく外に出して、Viewの中は**“UIだけ”にするのが吉。

🚨 3. カスタムViewに乱用しない

なんでもかんでも @ViewBuilder つけるのは危険かも。

struct CustomView: View {
    let content: () -> some View

    var body: some View {
        content()
    }
}

↑この場合、content@ViewBuilder をつけるかどうかは呼び出し側のコード次第
無意味にBuilderつけると、予期せぬバグや型エラーの温床になることもある。

必要な場面だけに絞って使うのがGood

7. まとめ

  • @ViewBuilder は、SwiftUIの宣言的で自然なUIコードを支える最重要パーツ!
  • 複数のView、条件分岐、ループ…全部を1つのViewに合成できるのが魅力。
  • でも、使いすぎや複雑なロジックは注意