@ViewBuilder: SwiftUIのViewにおけるresult builderとは

著者: IT Sectr 公開日: 2026-06-24 読了時間: 7 分

@ViewBuilderは、宣言的なView階層の構築のために設計されたSwiftUIのresult builderアノテーションです。Apple Developer Documentation, 2024によると、@ViewBuilderは複数の式と条件ロジックを含むコードブロックを、Swiftコンパイラが理解できる単一のView型に変換します。このアノテーションがなければ、if/elseやbody内の複数の要素を使用した、おなじみの宣言的SwiftUI構文を使うことは不可能です。

重要なポイント

  • @ViewBuilder — 複数のViewを余分なコンテナなしで合成するresult builder
  • buildBlock — 式の並びを最大10要素のTupleViewにラップする
  • buildEither — if/elseやswitchの分岐用にConditionalContentを作成する
  • 制限 — GroupやForEachなしでは1ブロックに最大10要素
  • 暗黙的な適用 — bodyはすでに@ViewBuilderでラップされており、カスタム関数には明示的なアノテーションが必要

SwiftUIの@ViewBuilderとは?

@ViewBuilderは、result builderパターン(SE-0289)を実装するアノテーションであり、宣言的構文を使用して複数のViewを1つの合成体にまとめることを可能にします。複数の式、条件付き構文、オプショナル値を対応する型(TupleView、ConditionalContent、OptionalContent)に自動的にラップします。

result builderが登場する前は、開発者は手動で要素をVStackやHStackでラップし、条件ロジックには三項演算子やファクトリメソッドを使用する必要がありました。@ViewBuilderはSwiftUIの構文を簡潔で読みやすくし、if/elseやループを含む通常のSwiftのようなコードを書けるようにしました。

Swift Evolution SE-0289によると、result builderはSwiftUIに限定されない一般的なメカニズムです。@ViewBuilderはこのメカニズムの実装の1つであり、文字列構築のための@StringBuilderや他のDSLのライブラリ実装も存在します。SwiftUIでは、@ViewBuilderはbodyだけでなく、コンテナ(VStack、HStack、ZStack、List)のクロージャパラメータにも使用されます。

命令型アプローチとの違い

命令型UIKitでは、明示的にUIViewを作成し、そのプロパティを構成して、addSubviewで階層に追加します。@ViewBuilderを使用したSwiftUIでは、表示するViewを宣言的に記述し、状態の変更に基づいて要素の作成、更新、削除をSwiftUIが管理します。

@ViewBuilderの仕組み:result builder

Result builderは、静的メソッドbuildBlock、buildOptional、buildEitherなどを介して式の並びを単一の複合値に変換するSwiftのメカニズムです。コンパイラが@ViewBuilderアノテーションを検出すると、コンパイル中に自動的にこれらのメソッドをコードブロックに適用します。

swift
@resultBuilder
struct ViewBuilder {
    static func buildBlock<C0, C1>(_ c0: C0, _ c1: C1) -> TupleView<(C0, C1)>
    static func buildIf<C>(_ c: C?) -> C?
    static func buildEither<T, F>(first: T) -> ConditionalContent<T, F>
    static func buildEither<T, F>(second: F) -> ConditionalContent<T, F>
}

buildBlockは1〜10個の式を受け取り、TupleViewを返します。各引数の数(式の数)には独自のbuildBlockオーバーロードがあります:buildBlockからbuildBlockまで。これが、1つの@ViewBuilderブロック内の要素数が10に制限されている理由です。

buildEither(first/second)はif/else構文を処理します。各分岐は対応するメソッドに渡され、結果はConditionalContentにラップされます。これは、特定の分岐型を隠蔽し、SwiftUIに統一されたインターフェースを提供する型です。

@ViewBuilderの暗黙的な動作

SwiftUIでは、bodyプロパティはすでに暗黙的に@ViewBuilderでアノテーションされています。コード上でそのアノテーションを目にすることはありませんが、コンパイラが自動的に適用します。ただし、複数のViewを返すカスタムプロパティやクロージャパラメータの場合は、アノテーションを明示的に指定する必要があります。

@ViewBuilderの制限と回避方法

制限1 — 1ブロックに10要素。 これは@ViewBuilderの最もよく知られた制限です。同じレベルに10を超える要素を表示する必要がある場合、コンパイラはエラーを出力します。回避策としては、Group、ForEach、List、またはサブコンポーネントへの分割があります。Groupは視覚的なネストを追加しませんが、各Groupは1要素としてカウントされます。

swift
struct ManyElementsView: View {
    var body: some View {
        Group {
            Text("1"); Text("2"); Text("3")
            Text("4"); Text("5"); Text("6")
            Text("7"); Text("8"); Text("9")
        }
        Group {
            Text("10"); Text("11"); Text("12")
        }
    }
}

制限2 — 特定の構文のサポート欠如。 @ViewBuilderはdo/catch、guard、for-in(ForEachなし)、およびその他の制御フロー構文をサポートしていません。ループには、識別可能なデータを持つForEachを使用してください。エラー処理には、Resultやオプショナル値を受け取る個別のViewを使用します。

制限3 — デバッグの複雑さ。 @ViewBuilderでエラーが発生すると、コンパイラは冗長なメッセージを生成し、根本原因を見つけるのが困難になります。典型的な問題:if/else分岐の型の不一致、10要素制限の超過、buildBlockの必要なオーバーロードの欠如などです。

@ViewBuilderの使用パターン

パターン1: if/elseによる条件付き表示。 @ViewBuilderの最も一般的なユースケースです。三項演算子やファクトリメソッドを使用せずに、状態に基づいて異なるViewを表示できます。

swift
struct StatusView: View {
    var status: LoadStatus

    @ViewBuilder
    var body: some View {
        switch status {
        case .loading:
            ProgressView("Loading...")
        case .loaded(let data):
            DataView(data: data)
        case .error(let message):
            ErrorView(message: message)
        }
    }
}

パターン2: 関数やイニシャライザのパラメータでの@ViewBuilder。 クロージャを介して子Viewを受け入れる再利用可能なコンテナを作成するために使用されます。これはライブラリやUIコンポーネントの標準的なパターンです。

swift
struct SectionCard<Content: View>: View {
    let title: String
    @ViewBuilder let content: Content

    var body: some View {
        VStack(alignment: .leading) {
            Text(title).font(.headline)
            content
        }
        .padding()
        .background(Color.gray.opacity(0.1))
        .cornerRadius(12)
    }
}

パターン3: ForEachとの合成。 @ViewBuilderはForEachと正しく連携し、データ配列から動的に要素を生成できます。ForEachの各要素は、@ViewBuilderのコンテキストで1つの式としてカウントされます。

再利用可能なコンポーネントのためのカスタムViewBuilderの作成

カスタムViewBuilderは、@ViewBuilderでアノテーションされ、some Viewを返すユーザー定義の関数またはプロパティです。このような関数により、複雑な表示ロジックをカプセル化し、アプリケーションのさまざまな部分で再利用できます。

swift
struct FormRow<Content: View>: View {
    let label: String
    @ViewBuilder let content: Content

    var body: some View {
        HStack {
            Text(label)
                .frame(width: 120, alignment: .trailing)
            content
        }
    }
}

// 使用方法:
FormRow(label: "Name") {
    TextField("Enter name", text: $name)
}

FormRow(label: "Gender") {
    Picker("Select", selection: $gender) {
        Text("男性").tag(Gender.male)
        Text("女性").tag(Gender.female)
    }
}

重要なルール: @ViewBuilderを使用するカスタム関数は、具体的な型やViewプロトコルではなく、some Viewを返す必要があります。不透明型のみが、合成の柔軟性を保ちながら具体的な実装を隠すことを可能にします。

パフォーマンス: カスタム@ViewBuilder関数は、直接のbodyコードと比較してオーバーヘッドを追加しません。コンパイラは呼び出しをインライン化し、結果のコードを最適化します。bodyを@ViewBuilder関数に分割することで、パフォーマンスを犠牲にすることなく可読性が向上します。

よくある質問

SwiftUIの@ViewBuilderとは?

@ViewBuilderは、複数の式と条件を含むコードブロックを単一のView型に変換するresult builderアノテーションです。SwiftUIの宣言的UI内で、おなじみのSwift構文(if/else、switch、オプショナル式)を使用できます。

@ViewBuilderに10個以上の要素を配置できないのはなぜ?

制限はbuildBlockの実装に起因します。1から10までの各引数の数に対して、メソッドの個別のオーバーロードが存在します。Swiftは可変長ジェネリクスをサポートしていないため、オーバーロードの数は固定されています。回避するには、Group、ForEach、またはサブコンポーネントを使用してください。

bodyの前に@ViewBuilderを明示的に指定する必要がありますか?

いいえ、Viewプロトコルはbodyプロパティに@ViewBuilderを暗黙的に適用します。ただし、複数のViewを返すカスタムプロパティ、メソッド、クロージャパラメータについては、アノテーションを明示的に指定する必要があります。指定しない場合、コンパイラは複数の式を処理できません。

@ViewBuilderはオプショナル式をどのように処理しますか?

オプショナル式には、buildIfメソッドが使用されます。これはオプショナルなViewを受け取り、値が存在すればそれを返します。値がnilの場合、buildIfはnilを返し、要素は表示されません。これにより、body内でif letを使用できます。

@ViewBuilderをswitchで使用できますか?

はい、Swift 5.9以降、@ViewBuilderはbuildExpressionメソッドを介してswitchをサポートしています。コンパイラは各case分岐を対応するbuildEither呼び出しに変換します。switchのサポートにより、ネストされたif/else構文と比較してコードがより読みやすくなります。

まとめ

  • @ViewBuilder — SwiftUIで宣言的にView階層を構築するためのresult builder
  • buildBlockは式の並びをTupleViewにラップする(最大10要素)
  • buildEitherはif/elseやswitchの分岐用にConditionalContentを作成する
  • buildIfはオプショナル式やelseなしのifを処理する
  • GroupとForEachはブロックあたり10要素の制限を回避するのに役立つ
  • カスタム@ViewBuilder関数はパフォーマンスを犠牲にせず再利用性を向上させる
  • @ViewBuilderはbodyに暗黙的に適用されるが、パラメータには明示的なアノテーションが必要

ターンキー方式のモバイルアプリケーションを開発します

IT Sectrは2017年からスタートアップや企業向けにiOS・Androidアプリケーションを開発しています。私たちがご相談に乗り、最適なソリューションをご提案します。

プロジェクトについて相談

こちらもお読みください