@ViewBuilder — це анотація result builder у SwiftUI, призначена для декларативної побудови ієрархії View. За даними Apple Developer Documentation, 2024, @ViewBuilder перетворює блок коду з множинними виразами та умовною логікою в єдиний тип View, зрозумілий компілятору Swift. Без цієї анотації було б неможливо використовувати звичний декларативний синтаксис SwiftUI з if/else та кількома елементами в тілі body.
Головне
@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 сам керує створенням, оновленням та видаленням елементів на основі змін стану.
Result builder — це механізм Swift, який перетворює послідовність виразів в одне складове значення через статичні методи buildBlock, buildOptional, buildEither та інші. Коли компілятор бачить анотацію @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 — 10 елементів у блоці. Це найвідоміше обмеження @ViewBuilder. Якщо потрібно відобразити більше 10 елементів на одному рівні, компілятор видасть помилку. Обійти можна через Group, ForEach, List або розбиттям на підкомпоненти. Group не додає візуальної вкладеності, але кожен Group вважається одним елементом.
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.
Патерн 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.
Кастомний 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 має повертати some View, а не конкретний тип або протокол View. Тільки opaque type дозволяє приховати конкретну реалізацію та зберегти гнучкість композиції.
Продуктивність: кастомні @ViewBuilder функції не додають накладних витрат порівняно з прямим кодом у body. Компілятор інлайнить виклики та оптимізує результуючий код. Розбиття body на @ViewBuilder-функції покращує читабельність без втрати продуктивності.
Часті запитання
@ViewBuilder — це анотація result builder, яка перетворює блок коду з множинними виразами та умовами в єдиний тип View. Вона дозволяє використовувати звичний синтаксис Swift (if/else, switch, опціональні вирази) всередині декларативного UI SwiftUI.
Обмеження пов'язане з реалізацією buildBlock — для кожної арності від 1 до 10 існує окреме перевантаження методу. Swift не підтримує variadic generics, тому кількість перевантажень фіксована. Для обходу використовуйте Group, ForEach або підкомпоненти.
Ні, протокол View неявно застосовує @ViewBuilder до властивості body. Однак для користувацьких властивостей, методів та параметрів-замикань, що повертають кілька View, анотацію потрібно вказувати явно. Без неї компілятор не зможе обробити множинні вирази.
Для опціональних виразів використовується метод buildIf, який приймає опціональний View і повертає його, якщо значення існує. Якщо значення nil — метод buildIf повертає nil, і елемент не відображається. Це дозволяє використовувати if let у тілі body.
Так, з Swift 5.9 @ViewBuilder підтримує switch через метод buildExpression. Компілятор перетворює кожну case-гілку у відповідний buildEither виклик. Підтримка switch робить код більш читабельним порівняно з вкладеними if/else конструкціями.
Підсумки
Ми розробимо мобільний застосунок під ключ
IT Sectr створює застосунки для iOS та Android для стартапів і бізнесу з 2017 року. Ми проконсультуємо вас і запропонуємо найкраще рішення.
Читайте також