En el ecosistema actual de desarrollo de Apple, la programación Swift ha evolucionado drásticamente. Si eres un iOS developer que viene de UIKit, probablemente estés acostumbrado a la herencia de clases y a pasar UIView o UIViewController de un lado a otro. Sin embargo, SwiftUI cambia las reglas del juego. SwiftUI no se basa en la herencia, sino en la composición.
Uno de los desafíos más comunes (y potentes) al empezar es entender cómo crear componentes reutilizables. ¿Cómo creo un contenedor que acepte cualquier contenido? La respuesta es: pasar una vista como parámetro con SwiftUI.
En este tutorial de Xcode y Swift, vamos a desglosar paso a paso cómo lograr esto de manera eficiente, performante y elegante, compatible con iOS, macOS y watchOS.
Introducción: El Poder de la Composición en SwiftUI
Antes de escribir código, debemos entender la filosofía. En UIKit, si querías un botón personalizado, creabas una subclase de UIButton. En SwiftUI, si quieres un botón personalizado, creas una struct que envuelve un botón y modifica su apariencia.
Para lograr una arquitectura limpia en SwiftUI, necesitas dominar los Generics y el atributo @ViewBuilder. Este artículo es tu guía definitiva para dejar de copiar y pegar código y empezar a construir una librería de UI modular.
¿Por qué pasar vistas como parámetros?
- Reutilización (DRY): Escribes la lógica del contenedor (bordes, sombras, padding) una vez y cambias solo el contenido.
- Consistencia: Si cambias el diseño de tu “Tarjeta” o “Modal”, se actualiza en toda la app.
- Flexibilidad: Puedes inyectar texto, imágenes, o layouts complejos dentro del mismo componente contenedor.
Nivel 1: El Enfoque Ingenuo (y por qué evitarlo)
Muchos desarrolladores principiantes en programación Swift intentan resolver este problema usando AnyView.
// ❌ NO HAGAS ESTO HABITUALMENTE
struct ContenedorIneficiente: View {
let contenido: AnyView
var body: some View {
contenido
.padding()
.background(Color.gray)
}
}
¿Por qué AnyView es malo para un iOS Developer?
Aunque funciona, AnyView utiliza algo llamado Type Erasure (borrado de tipos). SwiftUI se basa en un algoritmo de “diffing” muy rápido que compara la jerarquía de vistas para saber qué repintar. Cuando usas AnyView, le ocultas a SwiftUI la estructura interna de la vista.
- Rendimiento: SwiftUI pierde la capacidad de optimizar los renderizados.
- Estado: A veces puede causar reinicios de estado inesperados durante las actualizaciones de la vista.
Para optimizar nuestra aplicación en iOS, macOS o watchOS, debemos usar Generics.
Nivel 2: El Estándar de la Industria (Generics)
La forma correcta de pasar una vista como parámetro con SwiftUI es definiendo un tipo genérico que conforme al protocolo View.
La Sintaxis Básica
import SwiftUI
struct ContenedorGenerico<Content: View>: View {
let contenido: Content
// Inicializador simple
init(contenido: Content) {
self.contenido = contenido
}
var body: some View {
VStack {
Text("Encabezado Fijo")
.font(.headline)
// Aquí se renderiza la vista pasada por parámetro
contenido
.padding()
.border(Color.blue, width: 2)
}
}
}
Implementación
ContenedorGenerico(contenido: Text("Hola Mundo"))
Esto es mucho mejor. El compilador de Swift sabe exactamente qué tipo de vista hay dentro. Sin embargo, la sintaxis de uso es un poco tosca. No se siente “nativo” como cuando usamos un VStack { ... }. Para lograr esa elegancia, necesitamos @ViewBuilder.
Nivel 3: La Magia de @ViewBuilder
Si abres Xcode y miras la definición de un VStack, verás que su inicializador usa @ViewBuilder. Este es un Result Builder que nos permite escribir código declarativo (listas de vistas) que Swift transforma automáticamente en una Tupla de vistas.
Esta es la técnica definitiva que todo iOS developer debe tener en su cinturón de herramientas.
Paso a Paso: Creando una “CardView” Reutilizable
Vamos a crear una tarjeta estilizada que podamos usar en una app de iOS (iPhone/iPad), en el Dashboard de una app de macOS y en la pantalla pequeña de watchOS.
Paso 1: Definir la Estructura Genérica
Necesitamos una estructura que acepte un genérico <Content: View>.
struct CardView<Content: View>: View {
// Propiedades de configuración
var titulo: String
var colorFondo: Color
// El contenido es ahora un clousure que devuelve una vista
let contenido: () -> Content
// ...
}
Paso 2: El Inicializador con @ViewBuilder
Aquí es donde ocurre la magia. En lugar de pasar una vista instanciada, pasamos un bloque de código (closure).
init(titulo: String, color: Color = .white, @ViewBuilder contenido: @escaping () -> Content) {
self.titulo = titulo
self.colorFondo = color
self.contenido = contenido
}
Desglose técnico:
@ViewBuilder: Permite que el closure acepte múltiples vistas (como un Text y una Image) sin tener que envolverlas manualmente en un Group o VStack.@escaping: El closure se guardará para ejecutarse más tarde (cuando elbodyse pida), por lo que debe “escapar” del ámbito del inicializador.
Paso 3: El Cuerpo de la Vista
var body: some View {
VStack(alignment: .leading, spacing: 10) {
Text(titulo)
.font(.headline)
.foregroundStyle(.secondary)
Divider()
// Invocamos la función para obtener las vistas
contenido()
}
.padding()
.background(colorFondo)
.cornerRadius(12)
.shadow(radius: 4)
}
}
Implementación Práctica en Xcode
Ahora que hemos creado nuestro componente, veamos cómo lo usa un desarrollador en el día a día.
Ejemplo en iOS
Imagina una pantalla de perfil de usuario.
struct PerfilUsuarioView: View {
var body: some View {
ZStack {
Color(.systemGroupedBackground).ignoresSafeArea()
ScrollView {
// Uso de nuestra CardView con sintaxis de trailing closure
CardView(titulo: "Información Personal") {
HStack {
Image(systemName: "person.circle.fill")
.resizable()
.frame(width: 50, height: 50)
.foregroundColor(.blue)
VStack(alignment: .leading) {
Text("Juan Pérez")
.font(.title2)
.bold()
Text("iOS Developer Senior")
.font(.subheadline)
}
}
}
.padding()
CardView(titulo: "Habilidades", color: .blue.opacity(0.1)) {
Text("SwiftUI")
Text("UIKit")
Text("Combine")
}
.padding()
}
}
}
}
Nota cómo la sintaxis CardView(titulo: "...") { ... } es extremadamente limpia y legible. Esto es la esencia de la programación Swift moderna.
Nivel 4: Múltiples Parámetros de Vista (Slots)
A veces, pasar una vista como parámetro con SwiftUI no es suficiente; necesitas pasar dos o tres. Por ejemplo, una vista que tenga un “Header” personalizado y un “Body” personalizado.
Esto se conoce como el patrón “Slot” (ranura).
struct LayoutComplejo<Header: View, Footer: View, Content: View>: View {
let header: () -> Header
let footer: () -> Footer
let content: () -> Content
init(
@ViewBuilder header: @escaping () -> Header,
@ViewBuilder footer: @escaping () -> Footer,
@ViewBuilder content: @escaping () -> Content
) {
self.header = header
self.footer = footer
self.content = content
}
var body: some View {
VStack {
header()
.font(.largeTitle)
.padding()
ScrollView {
content()
}
footer()
.padding()
.background(Color.black.opacity(0.05))
}
}
}
Nota para expertos: Fíjate en cómo definimos tres tipos genéricos distintos (Header, Footer, Content). Si usáramos el mismo tipo genérico para todos (Content), Swift esperaría que el header, footer y content fueran exactamente el mismo tipo de vista, lo cual rara vez sucede.
Consideraciones Multiplataforma (iOS, macOS, watchOS)
Una de las promesas de SwiftUI es “Learn once, apply anywhere”. Al pasar vistas como parámetros, facilitamos enormemente la adaptación a diferentes sistemas operativos.
Adaptación para macOS
En macOS, los controles suelen ser más pequeños y la interacción es con ratón. Al usar nuestra CardView genérica, podemos cambiar el modificador .padding() o el fondo condicionalmente, pero el contenido inyectado (botones, textos) se renderizará con el estilo nativo de Mac automáticamente.
// En macOS, podríamos querer un estilo diferente
CardView(titulo: "Panel de Control") {
Toggle("Activar Notificaciones", isOn: .constant(true))
.toggleStyle(.switch) // Se ve como un switch nativo de Mac
}
Adaptación para watchOS
En el Apple Watch, el espacio es oro. Al tener una arquitectura donde pasamos la vista como parámetro, podemos reutilizar la lógica de la tarjeta pero inyectar vistas mucho más simplificadas para el reloj.
// En watchOS
CardView(titulo: "Estado") {
// Solo mostrarmos un icono grande para ahorrar espacio
Image(systemName: "checkmark.circle")
.font(.title)
}
El contenedor CardView es el mismo, pero lo que le “pasamos” cambia según la plataforma.
Consejos Avanzados para el Experto en Xcode
1. Extensiones de Conveniencia
Si tienes un contenedor que a veces no necesita contenido (por ejemplo, solo un título), puedes crear una extensión del init donde Content sea EmptyView.
2. Estilos con Protocolos
Similar a ButtonStyle, puedes crear tus propios protocolos de estilo para tus vistas contenedoras, permitiendo que otros desarrolladores inyecten no solo contenido, sino también comportamiento de estilo.
3. Rendimiento y @ViewBuilder
Recuerda que @ViewBuilder tiene un límite de 10 vistas hijas en versiones antiguas de Swift (aunque esto ha mejorado en versiones recientes de Xcode). Si pasas muchas vistas dentro del closure, agrúpalas en un Group o VStack.
Solución de Problemas Comunes
Error: “Generic parameter ‘Content’ could not be inferred”
Esto sucede cuando declaras la struct pero Swift no puede deducir qué es Content.
- Solución: Asegúrate de usar el inicializador que acepta el closure, o especifica el tipo manualmente (raro en SwiftUI).
Error: “Function declares an opaque return type…”
Si intentas devolver vistas heterogéneas (un Text si es true y una Image si es false) dentro de tu parámetro sin @ViewBuilder, fallará.
- Solución: Asegúrate de que el parámetro en el
inittenga el atributo@ViewBuilder. Esto envuelve automáticamente las vistas condicionales en un_ConditionalContent.
Conclusión
Convertirse en un iOS developer senior requiere dominar la abstracción. Pasar una vista como parámetro con SwiftUI no es solo un truco de sintaxis; es la base fundamental de la arquitectura declarativa moderna.
Al utilizar Generics y @ViewBuilder, logras:
- Código más limpio y legible.
- Mejor rendimiento en la compilación y renderizado en Xcode.
- Componentes totalmente reutilizables entre iOS, macOS y watchOS.
La próxima vez que te encuentres copiando y pegando un VStack con el mismo fondo y sombra pero diferente texto, detente. Crea una vista contenedora, usa un genérico, y eleva la calidad de tu programación Swift.
Si tienes cualquier duda sobre este artículo, contacta conmigo y estaré encantado de ayudarte 🙂. Puedes contactar conmigo en mi perfil de X o en mi perfil de Instagram










