Desde su lanzamiento en 2019, SwiftUI no solo cambió la forma en que escribimos interfaces de usuario; cambió fundamentalmente la manera en que pensamos sobre el flujo de datos. Si vienes del mundo de UIKit, Storyboards y UIViewController, es probable que hayas sentido el choque: el patrón MVC (Model-View-Controller) ya no encaja.
En el desarrollo moderno de iOS, la pregunta más recurrente ya no es “¿Cómo centro este botón?”, sino “¿Dónde pongo la lógica de negocio?”.
Este tutorial disecciona las arquitecturas más relevantes para el ecosistema de Apple hoy en día, analiza sus pros y contras, y te ofrece un marco de decisión para tu próximo proyecto.
El Cambio de Paradigma: La Muerte del Controlador
Para entender la arquitectura en SwiftUI, primero debemos entender la ecuación fundamental que rige el framework:
V=f(S)
Donde V es la Vista (View) y S es el Estado (State).
En UIKit (Imperativo), tú eres el responsable de mutar la vista manualmente cuando los datos cambian. En SwiftUI (Declarativo), la vista es un resultado directo e inmutable del estado actual. Si el estado cambia, la vista se redibuja.
Por lo tanto, cualquier arquitectura en SwiftUI debe priorizar la gestión del estado sobre la gestión del ciclo de vida de la vista.
1. MVVM (Model – View – ViewModel)
El Estándar de la Industria
MVVM se ha convertido en la arquitectura “por defecto” para SwiftUI. Esto se debe a que el protocolo ObservableObject y el wrapper @Published parecen haber sido diseñados específicamente para encajar con este patrón.
Cómo funciona
- Model: Estructuras de datos simples (Structs) y lógica de negocio pura. No sabe nada de la UI.
- View: Estructuras de SwiftUI que observan al ViewModel. No contienen lógica compleja, solo presentación.
- ViewModel: Clases (
class) que conformanObservableObject. Transforman los datos del modelo en algo que la vista pueda mostrar y manejan las acciones del usuario (intents).
Implementación Práctica
// 1. Model
struct Tarea: Identifiable {
let id: UUID
var titulo: String
var estaCompletada: Bool
}
// 2. ViewModel
class ListaTareasViewModel: ObservableObject {
@Published var tareas: [Tarea] = []
func agregarTarea(titulo: String) {
let nueva = Tarea(id: UUID(), titulo: titulo, estaCompletada: false)
tareas.append(nueva)
}
func completarTarea(_ tarea: Tarea) {
if let index = tareas.firstIndex(where: { $0.id == tarea.id }) {
tareas[index].estaCompletada.toggle()
}
}
}
// 3. View
struct ListaTareasView: View {
@StateObject private var viewModel = ListaTareasViewModel()
var body: some View {
List(viewModel.tareas) { tarea in
HStack {
Text(tarea.titulo)
Spacer()
Image(systemName: tarea.estaCompletada ? "checkmark.circle" : "circle")
.onTapGesture {
viewModel.completarTarea(tarea)
}
}
}
}
}El Veredicto sobre MVVM
- Pros: Fácil de aprender, nativo para Apple, separa bien la lógica de la UI.
- Contras: Los ViewModels pueden volverse “Dioses” (Massive ViewModels) si no se tiene cuidado. El enlace bidireccional (
Binding) puede complicar el seguimiento de errores.
2. TCA (The Composable Architecture)
La Potencia del Flujo Unidireccional
Creada por Point-Free, TCA es una librería que trae los conceptos de Redux (populares en React) a Swift. Es una arquitectura dogmática: te dice exactamente cómo hacer las cosas.
Conceptos Clave
- State (Estado): Una estructura que describe todo el estado de tu funcionalidad.
- Action (Acción): Un
enumque lista todos los eventos posibles (botón pulsado, respuesta de API recibida). - Reducer: Una función pura que toma el estado actual y una acción, y devuelve un nuevo estado.
- Store: El objeto que mantiene el estado y recibe acciones.
¿Por qué usar TCA?
La mayor ventaja es la testabilidad. Como los reducers son funciones puras (sin efectos secundarios ocultos), probar tu lógica es trivial. Además, maneja las dependencias (API clients, fechas, UUIDs) de forma explícita.
Ejemplo Conceptual (Simplificado)
struct FeatureState: Equatable {
var count = 0
}
enum FeatureAction: Equatable {
case decrementar
case incrementar
}
struct FeatureEnvironment {}
let featureReducer = Reducer<FeatureState, FeatureAction, FeatureEnvironment> { state, action, _ in
switch action {
case .decrementar:
state.count -= 1
return .none
case .incrementar:
state.count += 1
return .none
}
}El Veredicto sobre TCA
- Pros: Testabilidad inigualable, depuración de viajes en el tiempo (Time Travel Debugging), descomposición de problemas complejos en módulos pequeños.
- Contras: Curva de aprendizaje muy alta, verbosidad (mucho código boilerplate), dependencia de una librería de terceros (aunque muy mantenida).
3. Clean Architecture + MVVM
La Opción Enterprise
Para aplicaciones grandes que deben durar años, a veces MVVM no es suficiente. Aquí es donde aplicamos los principios de Clean Architecture (Tío Bob) sobre la capa de MVVM.
La clave aquí es la Inyección de Dependencias y la separación en capas estrictas:
- Presentación: (Tus Vistas y ViewModels).
- Dominio: (Casos de Uso / Interactors y Entidades).
- Data: (Repositorios y Data Sources).
¿Cómo se ve en SwiftUI?
El ViewModel ya no contiene la lógica. El ViewModel simplemente llama a un UseCase.
// Capa de Dominio (Agnóstica de UI)
protocol GetUserUseCase {
func execute() async throws -> User
}
// Capa de Presentación
class UserViewModel: ObservableObject {
@Published var user: User?
private let getUserUseCase: GetUserUseCase // Inyectado
init(getUserUseCase: GetUserUseCase) {
self.getUserUseCase = getUserUseCase
}
func load() async {
self.user = try? await getUserUseCase.execute()
}
}El Veredicto sobre Clean Arch
- Pros: Independencia total de frameworks, código altamente reutilizable, escalabilidad máxima para equipos grandes.
- Contras: “Over-engineering” (sobre-ingeniería) para apps simples. Demasiados archivos para hacer una tarea sencilla.
4. El Patrón “Pure SwiftUI” (MV)
Menos es Más
Hay una corriente creciente que sugiere que para muchas vistas, no necesitas un ViewModel. SwiftUI tiene herramientas poderosas como @State, @Binding y @Environment que hacen que la capa intermedia sea redundante en componentes simples.
Si tu vista solo muestra datos y tiene animaciones locales, crear una clase ObservableObject extra es desperdicio de memoria y código.
Cuándo usarlo:
- Prototipos.
- Vistas hojas (Leaf views) que solo reciben datos.
- Componentes de UI reutilizables (Botones, Celdas).
Comparativa Técnica
| Característica | MVVM | TCA | Clean + MVVM | Pure SwiftUI |
| Curva de Aprendizaje | Media | Alta | Media-Alta | Baja |
| Boilerplate | Medio | Alto | Alto | Mínimo |
| Testabilidad | Buena | Excelente | Excelente | Baja (Lógica en la Vista) |
| Escalabilidad | Buena | Muy Buena | Excelente | Baja |
| Adopción | Masiva | Creciente | Enterprise | Indie / Nicho |
Export to Sheets
Cómo Elegir la Arquitectura Correcta
No existe la “mejor” arquitectura, solo la más adecuada para tu contexto. Aquí tienes mi marco de decisión:
Escenario A: Eres un Indie Developer o Startup pequeña
Recomendación: MVVM Estándar. Necesitas velocidad. MVVM es lo suficientemente robusto para no crear código espagueti, pero lo suficientemente rápido para iterar. Usa @StateObject para tus pantallas principales y rompe las vistas en componentes pequeños.
Escenario B: Aplicación Bancaria, Salud o Enterprise
Recomendación: Clean Architecture + MVVM o TCA. Aquí la corrección es más importante que la velocidad. Necesitas separar las reglas de negocio (ej. cálculo de intereses) de la interfaz. Si tu equipo está dispuesto a aprender, TCA ofrece garantías de seguridad de estado que son invaluables en estos sectores.
Escenario C: App compleja con mucho estado compartido
Recomendación: TCA. Si tu app tiene un carrito de compras que debe actualizarse desde 5 pantallas diferentes, un reproductor de música que persiste en toda la app, o flujos de navegación profundos, la gestión de estado centralizada de TCA te salvará de muchos dolores de cabeza con @EnvironmentObject.
Conclusión
SwiftUI es un framework joven y las “mejores prácticas” siguen evolucionando. Sin embargo, la lección más importante es la consistencia.
- Si eliges MVVM, asegúrate de no meter lógica de negocio en la Vista.
- Si eliges TCA, respeta el flujo unidireccional y no busques atajos.
- Si eliges Clean, mantén tus capas de dominio puras.
Una mala arquitectura seguida consistentemente es mejor que tres arquitecturas buenas mezcladas en el mismo proyecto.
Al final del día, la arquitectura debe servirte a ti y a tu equipo para moveros más rápido y con más confianza, no para frenaros con burocracia de código.










