@ViewBuilder es una anotación result builder en SwiftUI diseñada para la construcción declarativa de jerarquías de View. Según Apple Developer Documentation, 2024, @ViewBuilder transforma un bloque de código con múltiples expresiones y lógica condicional en un único tipo View comprensible para el compilador de Swift. Sin esta anotación, sería imposible usar la sintaxis declarativa familiar de SwiftUI con if/else y múltiples elementos en el body.
Puntos clave
@ViewBuilder es una anotación que implementa el patrón result builder (SE-0289), que permite a SwiftUI componer múltiples Views en una sola composición mediante sintaxis declarativa. Automáticamente envuelve múltiples expresiones, construcciones condicionales y valores opcionales en sus tipos correspondientes: TupleView, ConditionalContent, OptionalContent.
Antes de los result builders, los desarrolladores tenían que envolver manualmente los elementos en VStack o HStack, y usar operadores ternarios o métodos de fábrica para la lógica condicional. @ViewBuilder hizo que la sintaxis de SwiftUI fuera concisa y legible, permitiendo escribir código que parece Swift normal con if/else y bucles.
Según Swift Evolution SE-0289, los result builders son un mecanismo general no vinculado a SwiftUI. @ViewBuilder es una implementación de este mecanismo, junto con @StringBuilder para la construcción de cadenas y las implementaciones de bibliotecas para otros DSL. En SwiftUI, @ViewBuilder se usa no solo para body, sino también para los parámetros de cierre de contenedores (VStack, HStack, ZStack, List).
En UIKit imperativo, creas explícitamente un UIView, configuras sus propiedades y lo agregas a la jerarquía mediante addSubview. En SwiftUI con @ViewBuilder, describes declarativamente qué Views deben mostrarse, y SwiftUI maneja la creación, actualización y eliminación de elementos según los cambios de estado.
Result builder es un mecanismo de Swift que transforma una secuencia de expresiones en un único valor compuesto mediante los métodos estáticos buildBlock, buildOptional, buildEither y otros. Cuando el compilador ve la anotación @ViewBuilder, aplica automáticamente estos métodos al bloque de código durante la compilación.
@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 acepta de 1 a 10 expresiones y devuelve un TupleView. Cada aridad (número de expresiones) tiene su propia sobrecarga de buildBlock: desde buildBlock
buildEither (first/second) maneja las construcciones if/else. Cada rama se pasa al método correspondiente, y el resultado se envuelve en ConditionalContent, un tipo que oculta los tipos específicos de las ramas y proporciona una interfaz unificada para SwiftUI.
En SwiftUI, la propiedad body ya está anotada implícitamente con @ViewBuilder — no ves esta anotación en el código, pero el compilador la aplica automáticamente. Sin embargo, para propiedades personalizadas que devuelven múltiples Views, o para parámetros de cierre, la anotación debe especificarse explícitamente.
Limitación 1 — 10 elementos en un bloque. Esta es la limitación más conocida de @ViewBuilder. Si necesitas mostrar más de 10 elementos en el mismo nivel, el compilador emitirá un error. Las soluciones incluyen Group, ForEach, List o dividir en subcomponentes. Group no agrega anidamiento visual, pero cada Group cuenta como un 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")
}
}
}
Limitación 2 — falta de soporte para ciertas construcciones. @ViewBuilder no admite do/catch, guard, for-in (sin ForEach) y otras construcciones de control de flujo. Para bucles, usa ForEach con datos identificables. Para el manejo de errores, usa Views separadas que acepten Result o valores opcionales.
Limitación 3 — complejidad de depuración. Cuando ocurren errores en @ViewBuilder, el compilador produce mensajes extensos donde es difícil encontrar la causa raíz. Problemas típicos: falta de coincidencia de tipos en ramas if/else, exceder el límite de 10 elementos o falta de sobrecargas necesarias de buildBlock.
Patrón 1: visualización condicional mediante if/else. El caso de uso más común de @ViewBuilder. Permite mostrar diferentes Views según el estado sin usar operadores ternarios o 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)
}
}
}
Patrón 2: @ViewBuilder en parámetros de funciones e inicializadores. Se usa para crear contenedores reutilizables que aceptan Views hijas mediante un cierre. Este es el patrón estándar para bibliotecas y 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)
}
}
Patrón 3: composición con ForEach. @ViewBuilder funciona correctamente con ForEach, permitiendo la generación dinámica de elementos a partir de un array de datos. Cada elemento de ForEach cuenta como una expresión en el contexto de @ViewBuilder.
ViewBuilder personalizado es una función o propiedad definida por el usuario anotada con @ViewBuilder que devuelve some View. Estas funciones permiten encapsular lógica de visualización compleja y reutilizarla en diferentes partes de la aplicación.
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("Hombre").tag(Gender.male)
Text("Mujer").tag(Gender.female)
}
}
Regla importante: una función personalizada con @ViewBuilder debe devolver some View, no un tipo concreto ni el protocolo View. Solo un tipo opaco permite ocultar la implementación concreta preservando la flexibilidad de composición.
Rendimiento: las funciones personalizadas @ViewBuilder no añaden sobrecarga en comparación con el código directo en body. El compilador inlinea las llamadas y optimiza el código resultante. Dividir body en funciones @ViewBuilder mejora la legibilidad sin sacrificar el rendimiento.
Preguntas frecuentes
@ViewBuilder es una anotación result builder que transforma un bloque de código con múltiples expresiones y condiciones en un único tipo View. Permite usar la sintaxis familiar de Swift (if/else, switch, expresiones opcionales) dentro de la UI declarativa de SwiftUI.
La limitación proviene de la implementación de buildBlock — existe una sobrecarga separada del método para cada aridad de 1 a 10. Swift no admite genéricos variádicos, por lo que el número de sobrecargas es fijo. Para evitarlo, usa Group, ForEach o subcomponentes.
No, el protocolo View aplica implícitamente @ViewBuilder a la propiedad body. Sin embargo, para propiedades personalizadas, métodos y parámetros de cierre que devuelvan múltiples Views, la anotación debe especificarse explícitamente. Sin ella, el compilador no podrá manejar múltiples expresiones.
Para expresiones opcionales, se usa el método buildIf, que acepta un View opcional y lo devuelve si existe un valor. Si el valor es nil, buildIf devuelve nil y el elemento no se muestra. Esto permite usar if let dentro del body.
Sí, desde Swift 5.9 @ViewBuilder admite switch a través del método buildExpression. El compilador transforma cada rama case en la correspondiente llamada buildEither. El soporte de switch hace que el código sea más legible en comparación con las construcciones if/else anidadas.
Resumen
Desarrollaremos una aplicación móvil llave en mano
IT Sectr crea aplicaciones para iOS y Android para startups y empresas desde 2017. Le asesoraremos y le propondremos la mejor solución.
Lea también