Crear interfaces de usuario con SwiftUI se siente, a menudo, como magia. Escribes unas pocas líneas de código declarativo y voilà, tienes una interfaz funcional en iOS, macOS y watchOS. Pero, ¿qué sucede cuando la magia falla? ¿Qué pasa cuando tu vista no se actualiza, la animación da tirones o los datos simplemente no aparecen?
A diferencia de UIKit, donde podíamos seguir el flujo imperativo paso a paso, SwiftUI es un sistema reactivo basado en estados. Debuggear aquí requiere un cambio de mentalidad: no buscas solo “dónde falló el código”, sino “por qué el estado no es el que espero”.
En este tutorial de 2000 palabras, vamos a diseccionar las herramientas, técnicas y secretos para hacer debug en Xcode como un ingeniero senior, cubriendo desde lo básico hasta trucos avanzados de LLDB e Instruments.
1. El Cambio de Paradigma: Debuggear Estado vs. Flujo
Antes de tocar una sola tecla, debes entender el campo de batalla. En la programación imperativa (UIKit/AppKit), el bug suele estar en la secuencia de ejecución: “Llamé a la función A, pero no ejecutó la función B”.
En SwiftUI, el bug casi siempre es una desincronización de estado.
- La Vista es una función del Estado: View=f(State).
- Si la UI está mal, es porque el
State,BindingoEnvironmentes incorrecto.
Tu objetivo al debuggear no es solo ver la jerarquía de vistas, sino inspeccionar la “verdad” de tus datos en un momento específico del tiempo.
2. Xcode Previews: Tu Primera Línea de Defensa
Muchos desarrolladores cometen el error de ignorar las Previews cuando las cosas se ponen difíciles y corren directamente al simulador. Sin embargo, las Previews son un entorno de debug aislado increíblemente potente.
Debugging de Diseño Multi-Plataforma
Cuando desarrollas para iOS, macOS y watchOS simultáneamente, el layout es tu primer enemigo. En lugar de ejecutar tres simuladores, utiliza las previews para visualizar los estados límite.
struct MiVista_Previews: PreviewProvider {
static var previews: some View {
Group {
MiVista(vm: ViewModel(estado: .cargando))
.previewDisplayName("iOS Cargando")
MiVista(vm: ViewModel(estado: .error))
.previewDevice("Apple Watch Series 9 (45mm)")
.previewDisplayName("WatchOS Error")
MiVista(vm: ViewModel(estado: .exito))
.previewDevice("Mac")
.previewDisplayName("MacOS Éxito")
}
}
}El “Selectable Mode”
En la parte inferior izquierda del canvas de Xcode, tienes dos botones vitales: “Live” (el icono de play) y “Selectable” (el icono de cursor).
- Live Mode: Para probar interacciones y animaciones.
- Selectable Mode: Al hacer clic en un elemento de la preview, Xcode resalta la línea exacta de código que generó esa vista. Si tienes un
HStackanidado dentro de unVStacky no sabes por qué hay un padding extra, este modo es la forma más rápida de encontrar al culpable.
3. Inspección Visual: View Hierarchy Debugger
A veces, el problema no es el código, sino las capas. SwiftUI tiende a “aplanar” la jerarquía de vistas para optimizar el rendimiento, pero a veces crea contenedores intermedios que no esperas.
Cuando tu aplicación esté corriendo (en simulador o dispositivo real), pulsa el botón Debug View Hierarchy en la barra de control de debug de Xcode (el icono que parecen tres rectángulos apilados).
Qué buscar aquí:
- Vistas ocultas: A veces una vista tiene
frame(width: 0)o está tapada por otra vista con fondo transparente. Gira el modelo 3D para ver qué hay detrás. - Z-Index Incorrecto: En watchOS y iOS, el orden de apilamiento en un
ZStackes crucial. Si un botón no responde, verifica aquí si hay una vista transparente encima interceptando los toques. - Tamaños de Frame: Selecciona una vista en el inspector y ve al panel de propiedades (derecha). Verifica si el sistema le está dando el tamaño que crees que tiene. Un error común es un
Textque se trunca porque su contenedor padre tiene un ancho fijo.
4. El Secreto Mejor Guardado: Self._printChanges()
Si solo te llevas una cosa de este artículo, que sea esta.
Uno de los problemas más difíciles en SwiftUI es el re-renderizado excesivo. ¿Por qué se actualiza toda mi lista cuando solo cambié un ícono? Antes de iOS 15, teníamos que adivinar. Ahora, tenemos un arma secreta.
Dentro de tu propiedad body, puedes invocar un método estático (y subrayado, lo que indica que es interno/beta pero seguro para debug) que imprime en consola qué causó la actualización de la vista.
struct UserProfileView: View {
@ObservedObject var viewModel: ProfileViewModel
var body: some View {
let _ = Self._printChanges() // <--- EL ARMA SECRETA
VStack {
Text(viewModel.name)
// ... resto de la vista
}
}
}Interpretando la salida en consola:
UserProfileView: @self, changed.-> La vista se recreó porque su inicializador fue llamado con nuevos parámetros (un struct padre cambió).UserProfileView: _viewModel changed.-> El@Publisheddentro de tu ViewModel disparó el cambio.
Nota Pro: Recuerda eliminar esta línea antes de enviar a producción, ya que puede afectar ligeramente el rendimiento y ensuciar los logs.
5. Debugging “In-Line” y Breakpoints en SwiftUI
Poner breakpoints en SwiftUI es confuso porque el body es una propiedad calculada que devuelve un tipo opaco (some View). No hay un flujo lineal claro donde “parar”.
La técnica del let _ = ...
Como vimos con _printChanges, podemos ejecutar código arbitrario dentro del body asignándolo a una variable anónima.
var body: some View {
let _ = print("Renderizando Body con valor: \(valor)")
Text("Hola")
}Breakpoints de Acción (Sin Pausa)
Parar la ejecución (pausar la app) en aplicaciones reactivas a veces altera el comportamiento, especialmente si estás debuggeando animaciones o concurrencia.
- Haz clic en el número de línea (gutter) para crear un breakpoint.
- Haz clic derecho > Edit Breakpoint.
- Añade una Action de tipo “Log Message”.
- Escribe:
Valor de x: @x@. - Marca la casilla “Automatically continue after evaluating actions”.
Ahora tienes logs en tiempo real sin ensuciar tu código con print().
LLDB: po vs p vs v
Cuando el breakpoint sí pausa la ejecución, usas la consola LLDB.
po variable: (Print Object) Es el estándar, pero es lento porque evalúa la expresión completa.p variable: Similar apopero muestra la representación cruda.v variable: (View frame variable) Usa este. Lee la memoria directamente sin compilar ni evaluar código. Es instantáneo y evita timeouts comunes al inspeccionar ViewModels grandes en SwiftUI.
6. Coloreando los Marcos (The Rainbow Debugging)
Cuando el layout no tiene sentido (espacios en blanco extraños, alineaciones fallidas), la mejor herramienta es visual.
Crea una extensión de View simple pero potente para visualizar los frames:
extension View {
func debugBorder(_ color: Color = .red) -> some View {
self.border(color, width: 1)
}
func debugBackground() -> some View {
self.background(Color(
red: .random(in: 0...1),
green: .random(in: 0...1),
blue: .random(in: 0...1),
opacity: 0.3
))
}
}Úsalo en tu código:
VStack {
HeaderView().debugBorder(.blue)
Spacer().debugBackground() // ¿Por qué este spacer es tan grande?
FooterView().debugBorder(.green)
}Esto revela inmediatamente si un Spacer está empujando contenido fuera de la pantalla o si un Text tiene un frame más grande que su contenido.
7. Retos Específicos: macOS y watchOS
El núcleo de SwiftUI es compartido, pero los comportamientos de la plataforma no.
macOS: El infierno del redimensionado
En macOS, las ventanas son redimensionables por el usuario. Un bug común es asumir un tamaño fijo.
- Debug Tip: En Xcode Previews para macOS, usa el modificador
.previewLayout(.fixed(width: 500, height: 300))para probar tamaños forzados. - Usa el Environment Override mientras corres la app en Mac para cambiar el tamaño de texto dinámico y el modo oscuro al vuelo.
watchOS: La lentitud del simulador
Debuggear en el simulador de watchOS puede ser dolorosamente lento, haciendo que parezca que tu código de red tiene bugs cuando solo es latencia.
- Debug Tip: Conecta tu Apple Watch físico. En Xcode, ve a Devices and Simulators y marca “Connect via Network”. El debugging inalámbrico en el reloj ha mejorado mucho en Xcode 15+.
- Si usas
WKApplicationRefreshBackgroundTask(actualizaciones en segundo plano), usa la opción de Xcode Debug > Simulate Background Fetch para forzar el evento sin esperar horas.
8. Instruments: Cuando la UI se Congela (Hangs)
Si tu aplicación hace scroll y se siente “pesada” o el reloj muestra la rueda de carga, tienes un problema de rendimiento (Hangs). LLDB no te ayudará aquí; necesitas Instruments.
- En Xcode, presiona
Cmd + I(Profile). - Selecciona SwiftUI (si está disponible en tu versión) o Time Profiler.
El culpable habitual: Computación en el Body
El body de una vista se invoca frecuentemente. Si haces esto, matarás el rendimiento:
// ❌ MAL: Cálculo pesado dentro del body
var body: some View {
let datosProcesados = filtrarYOrdenar(datos) // Esto corre en el Main Thread
List(datosProcesados) { ... }
}Instruments te mostrará grandes barras azules en el hilo principal (Main Thread). La solución es mover ese cálculo al ViewModel y publicar el resultado final, o usar .task para calcularlo asíncronamente.
9. Debugging de Flujo de Datos: @State vs @StateObject
El error más común que veo en Code Reviews y que causa bugs difíciles de rastrear es la confusión entre StateObject y ObservedObject.
- El Bug: La vista se recarga y pierde los datos que el usuario escribió.
- La Causa: Usaste
@ObservedObjectpara inicializar un ViewModel dentro de una vista que no es la propietaria.
struct VistaPadre: View {
var body: some View {
// Cada vez que VistaPadre se redibuja, VistaHija crea un NUEVO ViewModel
VistaHija()
}
}
struct VistaHija: View {
// ❌ ERROR: Esto reinicia el VM en cada render del padre
@ObservedObject var vm = MiViewModel()
// ✅ CORRECCIÓN: Usa @StateObject para mantener la vida del objeto
// @StateObject var vm = MiViewModel()
}Para debuggear esto, pon un print("init viewModel") en el init de tu clase ViewModel. Si ves ese log múltiples veces mientras interactúas con la UI, tienes una fuga de propiedad de objetos.
Conclusión: La Paciencia es tu Mejor Herramienta
Debuggear en SwiftUI requiere dejar de pensar linealmente y empezar a pensar estructuralmente. Las herramientas que Xcode nos ofrece hoy son años luz mejores que las que teníamos en las primeras versiones de SwiftUI.
Recuerda el flujo de trabajo del experto:
- Usa Previews para iteración rápida.
- Usa View Hierarchy para problemas de layout.
- Usa
Self._printChanges()para entender por qué se actualiza la vista. - Usa Instruments solo cuando notes problemas de rendimiento.
SwiftUI es potente, y con estas herramientas, tú tienes el control total sobre lo que ocurre en la pantalla, ya sea en la muñeca, en el bolsillo o en el escritorio.
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










