La transición de UIKit y AppKit a SwiftUI ha sido uno de los cambios de paradigma más grandes en la historia del desarrollo para plataformas de Apple. Ya no pensamos en términos de controladores de vista imperativos, sino en estados, flujos de datos y vistas declarativas.
Sin embargo, la libertad que ofrece SwiftUI puede ser un arma de doble filo. Sin una estructura sólida, es fácil terminar con “Massive Views”, problemas de rendimiento y lógica de negocio acoplada a la interfaz. Si estás desarrollando para iOS, macOS y watchOS, la consistencia es clave.
A continuación, presento las 10 mejores prácticas fundamentales que todo desarrollador moderno de Apple debe implementar en sus proyectos de Xcode para garantizar escalabilidad, mantenibilidad y un rendimiento óptimo.
1. Adopta MVVM (Model-View-ViewModel) Estrictamente
SwiftUI está diseñado, casi por naturaleza, para funcionar con el patrón MVVM. Intentar forzar MVC (Model-View-Controller) aquí suele resultar en dolor de cabeza.
¿Por qué?
SwiftUI es reactivo. La vista (View) es simplemente una función de su estado. Necesitas un componente que posea ese estado, procese la lógica de negocio y publique los cambios. Ese es el ViewModel.
La Regla de Oro
La Vista no debe tomar decisiones. La Vista solo debe:
- Mostrar datos.
- Capturar interacciones del usuario.
- Pasar esas interacciones al ViewModel.
Código de Ejemplo:
// MODELO
struct User: Identifiable {
let id: UUID
let name: String
}
// VIEWMODEL (La fuente de la verdad lógica)
@MainActor
class UserListViewModel: ObservableObject {
@Published var users: [User] = []
func fetchUsers() async {
// Simulación de carga
self.users = [User(id: UUID(), name: "Ana"), User(id: UUID(), name: "Carlos")]
}
}
// VISTA
struct UserListView: View {
@StateObject private var viewModel = UserListViewModel()
var body: some View {
List(viewModel.users) { user in
Text(user.name)
}
.task {
await viewModel.fetchUsers()
}
}
}2. Gestión de Dependencias e Inyección (DI)
Para que tu aplicación sea robusta y testeable, nunca debes instanciar servicios pesados (como clientes API o gestores de bases de datos) directamente dentro de tus Vistas o ViewModels.
La Práctica
Utiliza protocolos para definir tus servicios e inyéctalos en el inicializador de tu ViewModel.
protocol DataService {
func getData() async throws -> [String]
}
class RealDataService: DataService { ... }
class MockDataService: DataService { ... } // Para Previews y Tests
class ContentViewModel: ObservableObject {
let service: DataService
// Inyección de dependencia
init(service: DataService = RealDataService()) {
self.service = service
}
}Esto te permite usar MockDataService en tus Xcode Previews, lo que acelera el desarrollo visual sin hacer llamadas de red reales.
3. Domina los Wrappers de Propiedad (@State vs @StateObject vs @ObservedObject)
El error más común en desarrolladores intermedios es confundir estos wrappers, lo que causa bugs donde la UI no se actualiza o, peor aún, el estado se reinicia inesperadamente.
La Guía Definitiva:
| Wrapper | Cuándo usarlo |
@State | Para valores simples (Bool, Int, String) que pertenecen exclusivamente a una sola vista (ej. un toggle local). |
@Binding | Para leer y escribir un valor que es propiedad de otra vista padre. |
@StateObject | Úsalo cuando la Vista crea e inicializa el ViewModel. Es el “dueño” del objeto. |
@ObservedObject | Úsalo cuando la Vista recibe un ViewModel que ya fue creado en otro lugar. No garantiza la persistencia del objeto si la vista se redibuja. |
@EnvironmentObject | Para datos globales compartidos en toda la jerarquía (ej. Auth, Temas). |
Nota Crítica: Nunca uses
@ObservedObjectpara inicializar un ViewModel (@ObservedObject var vm = ViewModel()). Si la vista se redibuja, tu ViewModel se reiniciará y perderás datos. Usa@StateObjecten su lugar.
4. Composición de Vistas: Divide y Vencerás
En UIKit, el problema era el “Massive View Controller”. En SwiftUI, es el “Massive Body Property”. Si tu propiedad body tiene más de 50 líneas o múltiples niveles de anidación, es hora de refactorizar.
Mejor Práctica
Extrae subcomponentes en Vistas pequeñas y reutilizables. El compilador de SwiftUI es extremadamente eficiente manejando jerarquías de vistas pequeñas; de hecho, a menudo es más rápido que renderizar una vista monolítica gigante.
Mal ejemplo:
var body: some View {
VStack {
// 40 líneas de código para una tarjeta de perfil
Image(...)
Text(...)
Button(...)
// ... más código
}
}Buen ejemplo:
var body: some View {
VStack {
ProfileHeaderView(user: user)
BioSectionView(bio: user.bio)
ActionButtonsView()
}
}Esto no solo mejora la legibilidad, sino que permite que el sistema de renderizado de SwiftUI (el diffing engine) sea más preciso al actualizar solo las partes que cambiaron.
5. Diseño Multiplataforma Inteligente (iOS, macOS, watchOS)
SwiftUI promete “Aprende una vez, aplica en todas partes”, no necesariamente “Escribe una vez, ejecuta en todas partes”. Un diseño de iPhone 1:1 rara vez se ve bien en un Apple Watch o una Mac.
Estrategia de Código Compartido
- Lógica Compartida: Tus Modelos, ViewModels y Servicios (Capa de Negocio) deben ser 100% compartidos en un Swift Package o target compartido.
- UI Adaptativa: Usa compilación condicional (
#if os(macOS)) solo cuando sea estrictamente necesario. - Componentes Flexibles: Usa
ViewModifierpara adaptar estilos según la plataforma.
// Ejemplo de adaptación sutil
var body: some View {
VStack {
Text("Hola Mundo")
}
#if os(iOS)
.listStyle(.insetGrouped)
#elseif os(macOS)
.listStyle(.sidebar) // Estilo nativo de Mac
#endif
}Para watchOS, recuerda simplificar drásticamente la jerarquía y utilizar NavigationStack de forma que tenga sentido en una pantalla pequeña.
6. Uso Eficiente de Modificadores Personalizados (ViewModifiers)
Si te encuentras copiando y pegando el mismo bloque de .padding().background(...).cornerRadius(...) cinco veces, estás violando el principio DRY (Don’t Repeat Yourself).
Crea tus propios ViewModifier para estandarizar el diseño de tu Design System.
struct PrimaryButton: ViewModifier {
func body(content: Content) -> some View {
content
.padding()
.background(Color.blue)
.foregroundColor(.white)
.cornerRadius(10)
.font(.headline)
}
}
extension View {
func primaryStyle() -> some View {
modifier(PrimaryButton())
}
}
// Uso
Button("Guardar") { }
.primaryStyle()Esto permite cambiar el diseño de toda la app (por ejemplo, cambiar el radio de las esquinas) modificando un solo archivo.
7. Optimización de Rendimiento e Identidad
SwiftUI necesita saber qué vistas han cambiado. El uso incorrecto de id o listas no perezosas puede matar el rendimiento, especialmente en listas largas.
Prácticas Clave:
- Lazy Stacks: Usa
LazyVStackyLazyHStackdentro deScrollViewsi tienes muchos elementos.VStackcarga todo en memoria inmediatamente;Lazylo hace bajo demanda. - Identificadores Estables: En
ListoForEach, asegúrate de que tus modelos conformen aIdentifiablecon un ID estable (como UUID). No uses índices de array (0, 1, 2...) como IDs si la lista puede cambiar de orden, ya que causará animaciones extrañas y bugs de renderizado.
8. Concurrencia Moderna: Async/Await y MainActor
Olvida los completion handlers y los closures anidados. Desde Swift 5.5, async/await es el estándar. Es más legible, seguro y maneja mejor los errores.
Integración en SwiftUI
Usa el modificador .task en lugar de .onAppear cuando necesites cargar datos asíncronos al aparecer una vista. .task cancela automáticamente la tarea si la vista desaparece antes de que termine.
.task {
do {
await viewModel.loadData()
} catch {
print("Error: \(error)")
}
}9. Previsualizaciones (Previews) con Datos Simulados
Las Previews de Xcode son tu mejor herramienta de productividad. Si dependes de ejecutar la app en el simulador para ver un cambio de color, estás perdiendo horas a la semana.
Truco Pro: Preview Content
Crea un grupo de “Preview Content” en tu proyecto. Crea extensiones de tus modelos con datos estáticos de prueba.
extension User {
static let mock = User(id: UUID(), name: "Usuario Demo", email: "demo@test.com")
}
// En tu vista
#Preview {
ProfileView(user: .mock)
}Esto hace que tus vistas sean puras y aisladas. Si tu vista no funciona en el Preview, probablemente tenga dependencias ocultas que debes refactorizar.
10. Accesibilidad y Localización desde el Día 1
No dejes esto para el final. SwiftUI hace que la accesibilidad sea casi automática, pero debes guiarla.
- Dynamic Type: No uses tamaños de fuente fijos (
.font(.system(size: 16))). Usa estilos semánticos (.font(.body),.font(.headline)). Esto asegura que la app respete el tamaño de texto elegido por el usuario en los ajustes del sistema. - VoiceOver: Usa
.accessibilityLabel()y.accessibilityHint()en elementos que no sean texto estándar (como iconos o imágenes). - String Catalogs: Usa el nuevo sistema de String Catalogs de Xcode (introducido en Xcode 15) para gestionar tus textos. Evita hardcodear strings en la UI.
Text("welcome_message") // Clave localizada
.font(.title) // Soporta escalado dinámicoConclusión
Desarrollar para el ecosistema Apple con SwiftUI y Xcode es una experiencia gratificante cuando se siguen las pautas correctas. Adoptar MVVM, entender el ciclo de vida del estado, compartir código inteligentemente entre iOS, macOS y watchOS, y mantener una arquitectura limpia mediante inyección de dependencias, no solo hará que tu código sea más profesional, sino que hará que tu vida como desarrollador sea mucho más fácil a medida que el proyecto crezca.
SwiftUI evoluciona cada año (WWDC tras WWDC). Mantener estas bases sólidas te permitirá adaptarte a los cambios futuros sin tener que reescribir tu aplicación desde cero.
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










