Animaciones básicas en SwiftUI: cuándo usar .animation y cuándo withAnimation
Arturo Rivas Arias
SwiftUI ha simplificado enormemente la creación de interfaces con animaciones. Lo que en UIKit requería bloques de animación, cálculos de estado intermedio y coordinación 100% manual, ahora puede conseguirse con apenas unas líneas de código.
De todos modos, a medida que una aplicación crece, surge una duda habitual: ¿debería utilizar .animation(_:value:) o withAnimation(_:_:)?
Aunque ambas opciones producen resultados visualmente similares, su funcionamiento y sus casos de uso son distintos. Comprender estas diferencias ayuda a construir interfaces más predecibles, fáciles de mantener y con menos efectos secundarios inesperados.
Cómo funcionan las animaciones en SwiftUI
SwiftUI basa toda su arquitectura en el concepto de estado. Cuando un valor cambia, la vista se vuelve a calcular y el framework decide qué elementos deben actualizarse. Una animación no es más que una instrucción que indica a SwiftUI cómo interpolar entre el estado anterior y el nuevo estado.
Por ejemplo, imaginemos una tarjeta que puede expandirse para mostrar más información.
struct StatsCard: View {
@State private var expanded = false
var body: some View {
VStack(alignment: .leading) {
Text("Ventas del mes")
.font(.headline)
if expanded {
Text("42.380 €")
.font(.largeTitle.bold())
}
}
.padding()
.onTapGesture {
expanded.toggle()
}
}
}
Sin animación, el contenido aparece y desaparece instantáneamente.
Animaciones implícitas con .animation
La forma más sencilla de animar cambios consiste en asociar una animación a un valor concreto.
struct StatsCard: View {
@State private var expanded = false
var body: some View {
VStack(alignment: .leading) {
Text("Ventas del mes")
.font(.headline)
if expanded {
Text("42.380 €")
.font(.largeTitle.bold())
}
}
.padding()
.frame(height: expanded ? 180 : 80)
.animation(.easeInOut(duration: 0.3), value: expanded)
.onTapGesture {
expanded.toggle()
}
}
}
Cada vez que expanded cambie, SwiftUI animará automáticamente las propiedades afectadas por dicho cambio. La principal ventaja es la simplicidad. La vista describe qué debe animarse y SwiftUI se encarga del resto.
El problema de las animaciones implícitas
Si varias propiedades dependen del mismo estado, todas ellas heredarán la animación. En aplicaciones sencillas esto suele ser lo deseable, pero en pantallas complejas puede provocar comportamientos difíciles de prever y sobretodo de depurar. Por ello, en muchos proyectos se decide utilizar las animaciones implícitas únicamente para componentes muy localizados.
Animaciones explícitas con withAnimation
La alternativa consiste en animar directamente el cambio de estado.
Button("Expandir") {
withAnimation(.spring(duration: 0.5)) {
expanded.toggle()
}
}
Ahora la animación está asociada al momento exacto en el que se modifica el estado. Este enfoque suele resultar más fácil de mantener porque deja claro dónde comienza la transición. Como contrapartida, tener que incluir el código dentro de un bloque de ejecución resulta menos elegante y nos aleja un poco de la reactividad de la que presume el framework.
Animando múltiples cambios a la vez
Pero quizás merece la pena porque una de las ventajas de withAnimation es que permite agrupar varias modificaciones dentro de la misma transacción.
withAnimation(.spring) {
expanded = true
showDetails = true
selectedTab = .analytics
}
SwiftUI tratará todas estas actualizaciones como una única animación coherente.
Curvas de animación disponibles
Ease In Out
.easeInOut(duration: 0.3)
Comienza despacio, acelera y desacelera al final.
Spring
.spring(
duration: 0.5,
bounce: 0.3
)
Aporta una sensación física muy natural.
Smooth
.smooth(duration: 0.3)
Ofrece una transición fluida alineada con el comportamiento de las últimas versiones de los sistemas.
Bouncy
.bouncy(duration: 0.5)
Introduce rebotes que puede ser cuánto más pronunciados para elementos que necesitan llamar la atención.
Detectar cuándo termina una animación
Las versiones recientes de SwiftUI permiten ejecutar código cuando la animación finaliza.
withAnimation(.easeInOut(duration: 0.4)) {
expanded = true
} completion: {
print("Animación finalizada")
}
Esto elimina la necesidad de recurrir a temporizadores o retrasos artificiales.
withAnimation(.easeInOut(duration: 0.4)) {
showBanner = false
} completion: {
loadNextScreen()
}
La siguiente acción no se ejecutará hasta que la transición haya terminado realmente.
Buenas prácticas
Las mejores animaciones son aquellas que ayudan al usuario a comprender qué ha cambiado en la interfaz. Algunos buenos candidatos son:
- Aparición y desaparición de contenido.
- Cambios de tamaño.
- Cambios de posición.
- Indicadores de selección.
- Estados de carga.
¡Ojo! Conviene no abusar de ellas porque podríamos acabar con un galimatías importante donde el usuario, además de su tiempo, pierde el foco con tanto movimiento. También conviene mantener duraciones cortas. En la mayoría de aplicaciones, entre 0.2 y 0.4 segundos suele ofrecer una experiencia agradable.
Conclusión
Como regla general, .animation(_:value:) resulta ideal para animaciones locales y sencillas, mientras que withAnimation proporciona un control más preciso sobre cuándo y cómo se producen las transiciones.
A medida que una aplicación crece, este segundo enfoque suele ofrecer una mayor claridad y reducir comportamientos inesperados. Comprender ambas técnicas permite sacar mucho más partido al sistema de animaciones de SwiftUI y construir interfaces más fluidas y mantenibles.