@ViewBuilderは、宣言的なView階層の構築のために設計されたSwiftUIのresult builderアノテーションです。Apple Developer Documentation, 2024によると、@ViewBuilderは複数の式と条件ロジックを含むコードブロックを、Swiftコンパイラが理解できる単一のView型に変換します。このアノテーションがなければ、if/elseやbody内の複数の要素を使用した、おなじみの宣言的SwiftUI構文を使うことは不可能です。
重要なポイント
@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が管理します。
Result builderは、静的メソッドbuildBlock、buildOptional、buildEitherなどを介して式の並びを単一の複合値に変換するSwiftのメカニズムです。コンパイラが@ViewBuilderアノテーションを検出すると、コンパイル中に自動的にこれらのメソッドをコードブロックに適用します。
@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
buildEither(first/second)はif/else構文を処理します。各分岐は対応するメソッドに渡され、結果はConditionalContentにラップされます。これは、特定の分岐型を隠蔽し、SwiftUIに統一されたインターフェースを提供する型です。
SwiftUIでは、bodyプロパティはすでに暗黙的に@ViewBuilderでアノテーションされています。コード上でそのアノテーションを目にすることはありませんが、コンパイラが自動的に適用します。ただし、複数のViewを返すカスタムプロパティやクロージャパラメータの場合は、アノテーションを明示的に指定する必要があります。
制限1 — 1ブロックに10要素。 これは@ViewBuilderの最もよく知られた制限です。同じレベルに10を超える要素を表示する必要がある場合、コンパイラはエラーを出力します。回避策としては、Group、ForEach、List、またはサブコンポーネントへの分割があります。Groupは視覚的なネストを追加しませんが、各Groupは1要素としてカウントされます。
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の必要なオーバーロードの欠如などです。
パターン1: if/elseによる条件付き表示。 @ViewBuilderの最も一般的なユースケースです。三項演算子やファクトリメソッドを使用せずに、状態に基づいて異なるViewを表示できます。
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コンポーネントの標準的なパターンです。
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でアノテーションされ、some Viewを返すユーザー定義の関数またはプロパティです。このような関数により、複雑な表示ロジックをカプセル化し、アプリケーションのさまざまな部分で再利用できます。
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関数に分割することで、パフォーマンスを犠牲にすることなく可読性が向上します。
よくある質問
@ViewBuilderは、複数の式と条件を含むコードブロックを単一のView型に変換するresult builderアノテーションです。SwiftUIの宣言的UI内で、おなじみのSwift構文(if/else、switch、オプショナル式)を使用できます。
制限はbuildBlockの実装に起因します。1から10までの各引数の数に対して、メソッドの個別のオーバーロードが存在します。Swiftは可変長ジェネリクスをサポートしていないため、オーバーロードの数は固定されています。回避するには、Group、ForEach、またはサブコンポーネントを使用してください。
いいえ、Viewプロトコルはbodyプロパティに@ViewBuilderを暗黙的に適用します。ただし、複数のViewを返すカスタムプロパティ、メソッド、クロージャパラメータについては、アノテーションを明示的に指定する必要があります。指定しない場合、コンパイラは複数の式を処理できません。
オプショナル式には、buildIfメソッドが使用されます。これはオプショナルなViewを受け取り、値が存在すればそれを返します。値がnilの場合、buildIfはnilを返し、要素は表示されません。これにより、body内でif letを使用できます。
はい、Swift 5.9以降、@ViewBuilderはbuildExpressionメソッドを介してswitchをサポートしています。コンパイラは各case分岐を対応するbuildEither呼び出しに変換します。switchのサポートにより、ネストされたif/else構文と比較してコードがより読みやすくなります。
まとめ
ターンキー方式のモバイルアプリケーションを開発します
IT Sectrは2017年からスタートアップや企業向けにiOS・Androidアプリケーションを開発しています。私たちがご相談に乗り、最適なソリューションをご提案します。