@ViewBuilder é uma anotação result builder no SwiftUI projetada para a construção declarativa de hierarquias de View. De acordo com Apple Developer Documentation, 2024, o @ViewBuilder transforma um bloco de código com múltiplas expressões e lógica condicional em um único tipo View compreensível para o compilador Swift. Sem esta anotação, seria impossível usar a sintaxe declarativa familiar do SwiftUI com if/else e múltiplos elementos no body.
Principais conclusões
@ViewBuilder é uma anotação que implementa o padrão result builder (SE-0289), que permite ao SwiftUI compor múltiplas Views em uma única composição usando sintaxe declarativa. Ela automaticamente envolve múltiplas expressões, construções condicionais e valores opcionais em seus tipos correspondentes: TupleView, ConditionalContent, OptionalContent.
Antes dos result builders, os desenvolvedores tinham que envolver manualmente os elementos em VStack ou HStack, e usar operadores ternários ou métodos de fábrica para lógica condicional. O @ViewBuilder tornou a sintaxe do SwiftUI concisa e legível, permitindo escrever código que se parece com Swift normal com if/else e loops.
De acordo com Swift Evolution SE-0289, result builders são um mecanismo geral não vinculado ao SwiftUI. O @ViewBuilder é uma implementação deste mecanismo, junto com o @StringBuilder para construção de strings e implementações de bibliotecas para outros DSLs. No SwiftUI, @ViewBuilder é usado não apenas para body, mas também para parâmetros de closure de contêineres (VStack, HStack, ZStack, List).
No UIKit imperativo, você cria explicitamente um UIView, configura suas propriedades e o adiciona à hierarquia via addSubview. No SwiftUI com @ViewBuilder, você descreve declarativamente quais Views devem ser exibidas, e o SwiftUI gerencia a criação, atualização e remoção de elementos com base nas mudanças de estado.
Result builder é um mecanismo do Swift que transforma uma sequência de expressões em um único valor composto através dos métodos estáticos buildBlock, buildOptional, buildEither e outros. Quando o compilador vê a anotação @ViewBuilder, ele aplica automaticamente esses métodos ao bloco de código durante a compilação.
@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 aceita de 1 a 10 expressões e retorna um TupleView. Cada aridade (número de expressões) tem sua própria sobrecarga de buildBlock: de buildBlock
buildEither (first/second) lida com construções if/else. Cada ramo é passado para o método correspondente, e o resultado é envolto em ConditionalContent — um tipo que oculta os tipos específicos dos ramos e fornece uma interface unificada para o SwiftUI.
No SwiftUI, a propriedade body já está implicitamente anotada com @ViewBuilder — você não vê essa anotação no código, mas o compilador a aplica automaticamente. No entanto, para propriedades personalizadas que retornam múltiplas Views, ou para parâmetros de closure, a anotação deve ser especificada explicitamente.
Limitação 1 — 10 elementos em um bloco. Esta é a limitação mais conhecida do @ViewBuilder. Se você precisar exibir mais de 10 elementos no mesmo nível, o compilador emitirá um erro. As soluções incluem Group, ForEach, List ou divisão em subcomponentes. Group não adiciona aninhamento visual, mas cada Group conta como um elemento.
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")
}
}
}
Limitação 2 — falta de suporte para certas construções. O @ViewBuilder não suporta do/catch, guard, for-in (sem ForEach) e outras construções de fluxo de controle. Para loops, use ForEach com dados identificáveis. Para tratamento de erros, use Views separadas que aceitam Result ou valores opcionais.
Limitação 3 — complexidade de depuração. Quando ocorrem erros no @ViewBuilder, o compilador produz mensagens extensas onde é difícil encontrar a causa raiz. Problemas típicos: incompatibilidade de tipos em ramos if/else, exceder o limite de 10 elementos ou falta de sobrecargas necessárias de buildBlock.
Padrão 1: exibição condicional via if/else. O caso de uso mais comum do @ViewBuilder. Permite mostrar diferentes Views com base no estado sem usar operadores ternários ou métodos de fábrica.
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)
}
}
}
Padrão 2: @ViewBuilder em parâmetros de funções e inicializadores. Usado para criar contêineres reutilizáveis que aceitam Views filhas através de um closure. Este é o padrão padrão para bibliotecas e componentes de 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)
}
}
Padrão 3: composição com ForEach. O @ViewBuilder funciona corretamente com ForEach, permitindo a geração dinâmica de elementos a partir de um array de dados. Cada elemento do ForEach conta como uma expressão no contexto do @ViewBuilder.
ViewBuilder personalizado é uma função ou propriedade definida pelo usuário anotada com @ViewBuilder que retorna some View. Essas funções permitem encapsular lógica de exibição complexa e reutilizá-la em diferentes partes da aplicação.
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
}
}
}
// Uso:
FormRow(label: "Name") {
TextField("Enter name", text: $name)
}
FormRow(label: "Gender") {
Picker("Select", selection: $gender) {
Text("Masculino").tag(Gender.male)
Text("Feminino").tag(Gender.female)
}
}
Regra importante: uma função personalizada com @ViewBuilder deve retornar some View, não um tipo concreto ou o protocolo View. Apenas um tipo opaco permite ocultar a implementação concreta preservando a flexibilidade de composição.
Desempenho: funções personalizadas @ViewBuilder não adicionam sobrecarga em comparação com o código direto no body. O compilador inlinea as chamadas e otimiza o código resultante. Dividir body em funções @ViewBuilder melhora a legibilidade sem sacrificar o desempenho.
Perguntas frequentes
@ViewBuilder é uma anotação result builder que transforma um bloco de código com múltiplas expressões e condições em um único tipo View. Permite usar a sintaxe familiar do Swift (if/else, switch, expressões opcionais) dentro da UI declarativa do SwiftUI.
A limitação vem da implementação do buildBlock — existe uma sobrecarga separada do método para cada aridade de 1 a 10. O Swift não suporta genéricos variádicos, então o número de sobrecargas é fixo. Para contornar, use Group, ForEach ou subcomponentes.
Não, o protocolo View aplica implicitamente @ViewBuilder à propriedade body. No entanto, para propriedades personalizadas, métodos e parâmetros de closure que retornam múltiplas Views, a anotação deve ser especificada explicitamente. Sem ela, o compilador não conseguirá lidar com múltiplas expressões.
Para expressões opcionais, o método buildIf é usado, que aceita um View opcional e o retorna se existir um valor. Se o valor for nil, buildIf retorna nil e o elemento não é exibido. Isso permite usar if let dentro do body.
Sim, desde o Swift 5.9 o @ViewBuilder suporta switch através do método buildExpression. O compilador transforma cada ramo case na chamada buildEither correspondente. O suporte a switch torna o código mais legível em comparação com construções if/else aninhadas.
Resumo
Vamos desenvolver um aplicativo móvel chave na mão
A IT Sectr cria aplicativos para iOS e Android para startups e empresas desde 2017. Nós vamos aconselhá-lo e propor a melhor solução.
Leia também