@ViewBuilder: що це, result builder для View у SwiftUI

Автор: IT Sectr Опубліковано: 2026-06-24 Час читання: 7 хв

@ViewBuilder — це анотація result builder у SwiftUI, призначена для декларативної побудови ієрархії View. За даними Apple Developer Documentation, 2024, @ViewBuilder перетворює блок коду з множинними виразами та умовною логікою в єдиний тип View, зрозумілий компілятору Swift. Без цієї анотації було б неможливо використовувати звичний декларативний синтаксис SwiftUI з if/else та кількома елементами в тілі body.

Головне

  • @ViewBuilder — result builder, що збирає кілька View у композицію без зайвих контейнерів
  • buildBlock — обгортає послідовність виразів у TupleView до 10 елементів
  • buildEither — створює ConditionalContent для if/else та switch гілок
  • Обмеження — до 10 елементів в одному блоці без Group або ForEach
  • Неявне застосування — body вже обгорнутий у @ViewBuilder, користувацькі функції потребують явної анотації

Що таке @ViewBuilder у SwiftUI?

@ViewBuilder — це анотація, що реалізує патерн result builder (SE-0289), яка дозволяє SwiftUI збирати кілька View в одну композицію за допомогою декларативного синтаксису. Вона автоматично обгортає множинні вирази, умовні конструкції та опціональні значення у відповідні типи: TupleView, ConditionalContent, OptionalContent.

До появи result builder розробникам доводилося вручну обгортати елементи в VStack або HStack, а для умовної логіки використовувати тернарні оператори або фабричні методи. @ViewBuilder зробив синтаксис SwiftUI лаконічним та читабельним, дозволяючи писати код, який виглядає як звичайний Swift з if/else та циклами.

За даними Swift Evolution SE-0289, result builders — це загальний механізм, не прив'язаний до SwiftUI. @ViewBuilder є однією з реалізацій цього механізму, поряд з @StringBuilder для побудови рядків та бібліотечними реалізаціями для інших DSL. У SwiftUI @ViewBuilder використовується не тільки для body, але й для параметрів-замикань контейнерів (VStack, HStack, ZStack, List).

Відмінність від імперативного підходу

В імперативному UIKit ви імперативно створюєте UIView, налаштовуєте його властивості та додаєте на ієрархію через addSubview. У SwiftUI з @ViewBuilder ви декларативно описуєте, які View мають бути відображені, а SwiftUI сам керує створенням, оновленням та видаленням елементів на основі змін стану.

Як працює @ViewBuilder: result builder

Result builder — це механізм Swift, який перетворює послідовність виразів в одне складове значення через статичні методи buildBlock, buildOptional, buildEither та інші. Коли компілятор бачить анотацію @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. Саме тому кількість елементів в одному @ViewBuilder блоці обмежена 10.

buildEither (first/second) обробляє if/else-конструкції. Кожна гілка передається у відповідний метод, і результат обгортається в ConditionalContent — тип, який приховує конкретні типи гілок і надає єдиний інтерфейс для SwiftUI.

Неявна робота @ViewBuilder

У SwiftUI властивість body вже анотована @ViewBuilder неявно — ви не бачите цю анотацію в коді, але компілятор застосовує її автоматично. Однак для користувацьких властивостей, що повертають кілька View, або для параметрів-замикань анотацію потрібно вказувати явно.

Обмеження @ViewBuilder та як їх обходити

Обмеження 1 — 10 елементів у блоці. Це найвідоміше обмеження @ViewBuilder. Якщо потрібно відобразити більше 10 елементів на одному рівні, компілятор видасть помилку. Обійти можна через Group, ForEach, List або розбиттям на підкомпоненти. Group не додає візуальної вкладеності, але кожен Group вважається одним елементом.

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 з ідентифікованими даними. Для обробки помилок використовуйте окремі View, що приймають Result або опціональні значення.

Обмеження 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.

Створення кастомного 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 має повертати some View, а не конкретний тип або протокол View. Тільки opaque type дозволяє приховати конкретну реалізацію та зберегти гнучкість композиції.

Продуктивність: кастомні @ViewBuilder функції не додають накладних витрат порівняно з прямим кодом у body. Компілятор інлайнить виклики та оптимізує результуючий код. Розбиття body на @ViewBuilder-функції покращує читабельність без втрати продуктивності.

Часті запитання

Що таке @ViewBuilder у SwiftUI?

@ViewBuilder — це анотація result builder, яка перетворює блок коду з множинними виразами та умовами в єдиний тип View. Вона дозволяє використовувати звичний синтаксис Swift (if/else, switch, опціональні вирази) всередині декларативного UI SwiftUI.

Чому в @ViewBuilder не можна розмістити більше 10 елементів?

Обмеження пов'язане з реалізацією buildBlock — для кожної арності від 1 до 10 існує окреме перевантаження методу. Swift не підтримує variadic generics, тому кількість перевантажень фіксована. Для обходу використовуйте Group, ForEach або підкомпоненти.

Чи потрібно явно вказувати @ViewBuilder перед body?

Ні, протокол View неявно застосовує @ViewBuilder до властивості body. Однак для користувацьких властивостей, методів та параметрів-замикань, що повертають кілька View, анотацію потрібно вказувати явно. Без неї компілятор не зможе обробити множинні вирази.

Як @ViewBuilder обробляє опціональні вирази?

Для опціональних виразів використовується метод buildIf, який приймає опціональний View і повертає його, якщо значення існує. Якщо значення nil — метод buildIf повертає nil, і елемент не відображається. Це дозволяє використовувати if let у тілі body.

Чи можна використовувати @ViewBuilder з switch?

Так, з Swift 5.9 @ViewBuilder підтримує switch через метод buildExpression. Компілятор перетворює кожну case-гілку у відповідний buildEither виклик. Підтримка switch робить код більш читабельним порівняно з вкладеними if/else конструкціями.

Підсумки

  • @ViewBuilder — result builder для декларативної побудови ієрархії View у SwiftUI
  • buildBlock обгортає послідовність виразів у TupleView (до 10 елементів)
  • buildEither створює ConditionalContent для if/else та switch гілок
  • buildIf обробляє опціональні вирази та if без else
  • Group та ForEach допомагають обійти обмеження в 10 елементів на блок
  • Кастомні @ViewBuilder-функції покращують перевикористання без втрати продуктивності
  • @ViewBuilder застосовується неявно до body, але потребує явної анотації для параметрів

Ми розробимо мобільний застосунок під ключ

IT Sectr створює застосунки для iOS та Android для стартапів і бізнесу з 2017 року. Ми проконсультуємо вас і запропонуємо найкраще рішення.

Обговорити проект

Читайте також