@ViewBuilder — to adnotacja result builder w SwiftUI, przeznaczona do deklaratywnego budowania hierarchii View. Według Apple Developer Documentation, 2024, @ViewBuilder przekształca blok kodu z wieloma wyrażeniami i logiką warunkową w jeden typ View zrozumiały dla kompilatora Swift. Bez tej adnotacji niemożliwe byłoby używanie znanego deklaratywnego składni SwiftUI z if/else i wieloma elementami w ciele body.
Najważniejsze
@ViewBuilder — to adnotacja implementująca wzorzec result builder (SE-0289), która pozwala SwiftUI zbierać wiele View w jedną kompozycję za pomocą deklaratywnej składni. Automatycznie zawija wiele wyrażeń, konstrukcje warunkowe i wartości opcjonalne w odpowiednie typy: TupleView, ConditionalContent, OptionalContent.
Przed pojawieniem się result builder programiści musieli ręcznie zawijać elementy w VStack lub HStack, a do logiki warunkowej używać operatorów trójargumentowych lub metod fabrycznych. @ViewBuilder uczynił składnię SwiftUI zwięzłą i czytelną, umożliwiając pisanie kodu, który wygląda jak zwykły Swift z if/else i pętlami.
Według Swift Evolution SE-0289, result builders to ogólny mechanizm nieprzywiązany do SwiftUI. @ViewBuilder jest jedną z implementacji tego mechanizmu, obok @StringBuilder do budowania ciągów znaków i bibliotecznych implementacji dla innych DSL. W SwiftUI @ViewBuilder jest używany nie tylko dla body, ale także dla parametrów-zamknięć kontenerów (VStack, HStack, ZStack, List).
W imperatywnym UIKit tworzysz imperatywnie UIView, konfigurujesz jego właściwości i dodajesz do hierarchii przez addSubview. W SwiftUI z @ViewBuilder deklaratywnie opisujesz, które View mają być wyświetlone, a SwiftUI sam zarządza tworzeniem, aktualizacją i usuwaniem elementów na podstawie zmian stanu.
Result builder — to mechanizm Swift, który przekształca sekwencję wyrażeń w jedną wartość złożoną za pomocą statycznych metod buildBlock, buildOptional, buildEither i innych. Gdy kompilator widzi adnotację @ViewBuilder, automatycznie stosuje te metody do bloku kodu w procesie kompilacji.
@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 przyjmuje od 1 do 10 wyrażeń i zwraca TupleView. Każda arność (liczba wyrażeń) ma swoje przeciążenie buildBlock: od buildBlock<C0> do buildBlock<C0, C1, ..., C9>. Dlatego właśnie liczba elementów w jednym bloku @ViewBuilder jest ograniczona do 10.
buildEither (first/second) obsługuje konstrukcje if/else. Każda gałąź jest przekazywana do odpowiedniej metody, a wynik jest zawijany w ConditionalContent — typ, który ukrywa konkretne typy gałęzi i zapewnia jednolity interfejs dla SwiftUI.
W SwiftUI właściwość body jest już domyślnie adnotowana @ViewBuilder — nie widzisz tej adnotacji w kodzie, ale kompilator stosuje ją automatycznie. Jednak dla właściwości użytkownika zwracających wiele View lub dla parametrów-zamknięć adnotację należy wskazać jawnie.
Ograniczenie 1 — 10 elementów w bloku. To najbardziej znane ograniczenie @ViewBuilder. Jeśli trzeba wyświetlić więcej niż 10 elementów na jednym poziomie, kompilator zgłosi błąd. Można to ominąć przez Group, ForEach, List lub podział na podkomponenty. Group nie dodaje wizualnego zagnieżdżenia, ale każdy Group jest liczony jako jeden element.
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")
}
}
}
Ograniczenie 2 — brak obsługi niektórych konstrukcji. @ViewBuilder nie obsługuje do/catch, guard, for-in (bez ForEach) i innych konstrukcji sterujących. Do pętli używaj ForEach z identyfikowalnymi danymi. Do obsługi błędów używaj oddzielnych View przyjmujących Result lub wartości opcjonalne.
Ograniczenie 3 — trudność debugowania. Przy błędach w @ViewBuilder kompilator generuje rozwlekłe komunikaty, w których trudno znaleźć przyczynę źródłową. Typowe problemy: niezgodność typów w gałęziach if/else, przekroczenie limitu 10 elementów lub brak wymaganego przeciążenia buildBlock.
Wzorzec 1: warunkowe wyświetlanie przez if/else. Najczęstszy scenariusz użycia @ViewBuilder. Pozwala pokazywać różne View w zależności od stanu bez używania operatorów trójargumentowych lub metod fabrycznych.
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)
}
}
}
Wzorzec 2: @ViewBuilder w parametrach funkcji i inicjalizatorów. Używany do tworzenia kontenerów wielokrotnego użytku przyjmujących dziecięce View przez zamknięcie. To standardowy wzorzec dla bibliotek i komponentów 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)
}
}
Wzorzec 3: kompozycja z ForEach. @ViewBuilder działa poprawnie z ForEach, umożliwiając dynamiczne generowanie elementów z tablicy danych. Każdy element ForEach jest liczony jako jedno wyrażenie w kontekście @ViewBuilder.
Własny ViewBuilder — to funkcja użytkownika lub właściwość adnotowana @ViewBuilder, która zwraca some View. Takie funkcje pozwalają hermetyzować złożoną logikę wyświetlania i używać jej ponownie w różnych częściach aplikacji.
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
}
}
}
// Użycie:
FormRow(label: "Name") {
TextField("Enter name", text: $name)
}
FormRow(label: "Gender") {
Picker("Select", selection: $gender) {
Text("Mężczyzna").tag(Gender.male)
Text("Kobieta").tag(Gender.female)
}
}
Ważna zasada: własna funkcja z @ViewBuilder musi zwracać some View, a nie konkretny typ lub protokół View. Tylko typ opaque pozwala ukryć konkretną implementację i zachować elastyczność kompozycji.
Wydajność: własne funkcje @ViewBuilder nie dodają narzutu w porównaniu z bezpośrednim kodem w body. Kompilator inline'uje wywołania i optymalizuje wynikowy kod. Dzielenie body na funkcje @ViewBuilder poprawia czytelność bez utraty wydajności.
Często zadawane pytania
@ViewBuilder — to adnotacja result builder przekształcająca blok kodu z wieloma wyrażeniami i warunkami w jeden typ View. Pozwala używać znanej składni Swift (if/else, switch, wyrażenia opcjonalne) wewnątrz deklaratywnego interfejsu SwiftUI.
Ograniczenie wynika z implementacji buildBlock — dla każdej arności od 1 do 10 istnieje oddzielne przeciążenie metody. Swift nie obsługuje variadic generics, dlatego liczba przeciążeń jest stała. Aby to ominąć, użyj Group, ForEach lub podkomponentów.
Nie, protokół View niejawne stosuje @ViewBuilder do właściwości body. Jednak dla właściwości użytkownika, metod i parametrów-zamknięć zwracających wiele View adnotację należy wskazać jawnie. Bez niej kompilator nie będzie w stanie obsłużyć wielu wyrażeń.
Do wyrażeń opcjonalnych używana jest metoda buildIf, która przyjmuje opcjonalny View i zwraca go, jeśli wartość istnieje. Jeśli wartość to nil — metoda buildIf zwraca nil i element nie jest wyświetlany. Umożliwia to używanie if let w ciele body.
Tak, od Swift 5.9 @ViewBuilder obsługuje switch przez metodę buildExpression. Kompilator przekształca każdą gałąź case w odpowiednie wywołanie buildEither. Obsługa switch sprawia, że kod jest bardziej czytelny w porównaniu z zagnieżdżonymi konstrukcjami if/else.
Podsumowanie
Opracujemy aplikację mobilną pod klucz
IT Sectr tworzy aplikacje na iOS i Androida dla startupów i firm od 2017 roku. Doradzimy Ci i zaproponujemy najlepsze rozwiązanie.
Przeczytaj również