@ViewBuilder — är en result builder-annotation i SwiftUI, avsedd för deklarativ uppbyggnad av View-hierarkin. Enligt Apple Developer Documentation, 2024, omvandlar @ViewBuilder ett kodblock med flera uttryck och villkorslogik till en enda View-typ som är förståelig för Swift-kompilatorn. Utan denna annotation skulle det vara omöjligt att använda SwiftUI:s välbekanta deklarativa syntax med if/else och flera element i body.
Huvudpunkter
@ViewBuilder — är en annotation som implementerar result builder-mönstret (SE-0289), som gör det möjligt för SwiftUI att samla flera View i en enda komposition med hjälp av deklarativ syntax. Den slår automatiskt in flera uttryck, villkorskonstruktioner och valfria värden i motsvarande typer: TupleView, ConditionalContent, OptionalContent.
Innan result builder kom till, var utvecklare tvungna att manuellt slå in element i VStack eller HStack, och för villkorslogik använda ternära operatorer eller fabriksmetoder. @ViewBuilder gjorde SwiftUI-syntaxen koncis och läsbar, vilket möjliggjorde kod som ser ut som vanlig Swift med if/else och loopar.
Enligt Swift Evolution SE-0289 är result builders en allmän mekanism som inte är bunden till SwiftUI. @ViewBuilder är en av implementeringarna av denna mekanism, tillsammans med @StringBuilder för att bygga strängar och biblioteksimplementeringar för andra DSL:er. I SwiftUI används @ViewBuilder inte bara för body, utan också för containerers closure-parametrar (VStack, HStack, ZStack, List).
I imperativ UIKit skapar du imperativt en UIView, konfigurerar dess egenskaper och lägger till den i hierarkin via addSubview. I SwiftUI med @ViewBuilder beskriver du deklarativt vilka View som ska visas, och SwiftUI hanterar själv skapande, uppdatering och borttagning av element baserat på tillståndsändringar.
Result builder — är en Swift-mekanism som omvandlar en sekvens av uttryck till ett enda sammansatt värde genom statiska metoder buildBlock, buildOptional, buildEither och andra. När kompilatorn ser @ViewBuilder-annotationen tillämpar den automatiskt dessa metoder på kodblocket under kompileringen.
@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 tar emot 1 till 10 uttryck och returnerar TupleView. Varje aritet (antal uttryck) har sin egen överlagring av buildBlock: från buildBlock<C0> till buildBlock<C0, C1, ..., C9>. Det är just därför antalet element i ett @ViewBuilder-block är begränsat till 10.
buildEither (first/second) bearbetar if/else-konstruktioner. Varje gren skickas till motsvarande metod och resultatet slås in i ConditionalContent — en typ som döljer de specifika typerna av grenarna och tillhandahåller ett enhetligt gränssnitt för SwiftUI.
I SwiftUI är body-egenskapen redan implicit annoterad med @ViewBuilder — du ser inte denna annotation i koden, men kompilatorn tillämpar den automatiskt. För användaregenskaper som returnerar flera View eller för closure-parametrar måste annotationen dock anges explicit.
Begränsning 1 — 10 element i ett block. Detta är den mest kända begränsningen av @ViewBuilder. Om du behöver visa mer än 10 element på en nivå kommer kompilatorn att ge ett fel. Du kan kringgå detta med Group, ForEach, List eller genom att dela upp i underkomponenter. Group lägger inte till visuell nästling, men varje Group räknas som ett 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")
}
}
}
Begränsning 2 — brist på stöd för vissa konstruktioner. @ViewBuilder stöder inte do/catch, guard, for-in (utan ForEach) och andra kontrollkonstruktioner. För loopar, använd ForEach med identifierbar data. För felhantering, använd separata View som accepterar Result eller valfria värden.
Begränsning 3 — svårighet att felsöka. Vid fel i @ViewBuilder genererar kompilatorn långa meddelanden där det är svårt att hitta grundorsaken. Typiska problem: typmatchning i if/else-grenar, överskridande av gränsen på 10 element eller avsaknad av den nödvändiga buildBlock-överlagringen.
Mönster 1: villkorlig visning genom if/else. Det vanligaste användningsscenariot för @ViewBuilder. Gör det möjligt att visa olika View beroende på tillstånd utan att använda ternära operatorer eller fabriksmetoder.
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)
}
}
}
Mönster 2: @ViewBuilder i funktions- och initieringsparametrar. Används för att skapa återanvändbara behållare som tar emot underordnade View via en closure. Detta är ett standardmönster för bibliotek och UI-komponenter.
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)
}
}
Mönster 3: komposition med ForEach. @ViewBuilder fungerar korrekt med ForEach, vilket möjliggör dynamisk generering av element från en datamatris. Varje ForEach-element räknas som ett uttryck i @ViewBuilder-sammanhanget.
Anpassad ViewBuilder — är en användarfunktion eller egenskap annoterad med @ViewBuilder som returnerar some View. Sådana funktioner gör det möjligt att kapsla in komplex visningslogik och återanvända den i olika delar av applikationen.
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
}
}
}
// Användning:
FormRow(label: "Name") {
TextField("Enter name", text: $name)
}
FormRow(label: "Gender") {
Picker("Select", selection: $gender) {
Text("Man").tag(Gender.male)
Text("Kvinna").tag(Gender.female)
}
}
Viktig regel: en anpassad funktion med @ViewBuilder måste returnera some View, inte en specifik typ eller View-protokollet. Endast opaque-typen gör det möjligt att dölja den specifika implementeringen och bevara kompositionens flexibilitet.
Prestanda: anpassade @ViewBuilder-funktioner lägger inte till overhead jämfört med direkt kod i body. Kompilatorn inline-anropar och optimerar den resulterande koden. Att dela upp body i @ViewBuilder-funktioner förbättrar läsbarheten utan prestandaförlust.
Vanliga frågor
@ViewBuilder — är en result builder-annotation som omvandlar ett kodblock med flera uttryck och villkor till en enda View-typ. Den gör det möjligt att använda välbekant Swift-syntax (if/else, switch, valfria uttryck) inuti SwiftUI:s deklarativa UI.
Begränsningen är kopplad till implementeringen av buildBlock — för varje aritet från 1 till 10 finns en separat överlagring av metoden. Swift stöder inte variadic generics, så antalet överlagringar är fast. För att kringgå detta, använd Group, ForEach eller underkomponenter.
Nej, View-protokollet tillämpar implicit @ViewBuilder på body-egenskapen. För användaregenskaper, metoder och closure-parametrar som returnerar flera View måste annotationen dock anges explicit. Utan den kommer kompilatorn inte att kunna bearbeta flera uttryck.
För valfria uttryck används metoden buildIf, som tar emot en valfri View och returnerar den om värdet finns. Om värdet är nil — returnerar buildIf nil och elementet visas inte. Detta gör det möjligt att använda if let i body.
Ja, från och med Swift 5.9 stöder @ViewBuilder switch genom metoden buildExpression. Kompilatorn omvandlar varje case-gren till ett motsvarande buildEither-anrop. Stöd för switch gör koden mer läsbar jämfört med nästlade if/else-konstruktioner.
Sammanfattning
Vi utvecklar en mobil applikation nyckelfärdigt
IT Sectr skapar iOS- och Android-applikationer för startups och företag sedan 2017. Vi ger dig råd och föreslår den bästa lösningen.
Läs också