Domina el timing de las animaciones en SwiftUI
Arturo Rivas Arias
SwiftUI facilita la creación de animaciones con muy poco código, pero elegir el tipo de animación adecuado marca una gran diferencia en la sensación que transmite una interfaz. Aunque muchas aplicaciones utilizan la animación por defecto, el framework ofrece un conjunto mucho más amplio de herramientas para controlar cómo evoluciona un cambio de estado.
En este artículo repasaremos los principales tipos de temporización disponibles en SwiftUI, cuándo utilizarlos y cómo personalizarlos para conseguir interfaces más naturales.
La animación por defecto
Aplicando animation(_:value:) o envolviendo un bloque de código con uno o varios cambios de estado con withAnimation podemos animar prácticamente cualquier modificación visual. Desde iOS 17, la animación predeterminada es una animación de tipo spring, elegida por Apple para ofrecer un comportamiento equilibrado.
withAnimation {
isExpanded.toggle()
}
Esto suele ser suficiente para muchas situaciones, pero existen alternativas mucho más apropiadas dependiendo del efecto que queramos conseguir.
Animaciones easing
Las animaciones easing siguen una curva matemática (Bezier cúbica) que determina cómo acelera y desacelera un elemento durante su recorrido.
.linear
Mantiene una velocidad constante durante toda la animación. Resulta útil para elementos que deben moverse de forma uniforme, como un indicador de progreso o una rotación continua.
Image(systemName: "gear")
.rotationEffect(.degrees(rotating ? 360 : 0))
.animation(.linear(duration: 3).repeatForever(autoreverses: false),
value: rotating)
.easeIn
Comienza lentamente y acelera al final. Funciona especialmente bien cuando un elemento desaparece de la pantalla.
.easeOut
Hace justo lo contrario: comienza rápido y termina suavemente. Es una de las opciones más habituales para presentar contenido nuevo.
.easeInOut
Suaviza tanto el inicio como el final del movimiento, siendo una excelente elección para desplazamientos entre dos posiciones.
Curvas personalizadas
Cuando los preajustes no son suficientes podemos definir nuestra propia curva mediante UnitCurve y timingCurve.
.animation(
.timingCurve(
.bezier(
startControlPoint: .init(x: 0.15, y: 0.9),
endControlPoint: .init(x: 0.85, y: 0.1)
),
duration: 0.8
),
value: progress
)
Esto permite adaptar la sensación de la animación exactamente a las necesidades de nuestra interfaz.
Animaciones de tipo spring
Las animaciones elásticas simulan un objeto unido a un muelle. Dependiendo del amortiguamiento pueden detenerse inmediatamente o rebotar antes de estabilizarse.
SwiftUI incluye tres variantes principales:
smooth: sin rebotes perceptibles.snappy: un pequeño rebote que aporta dinamismo.bouncy: rebote mucho más pronunciado.
Un ejemplo práctico sería mostrar una tarjeta seleccionada:
CardView(card: card)
.scaleEffect(selected ? 1.08 : 1)
.animation(.snappy, value: selected)
Personalizando un spring
Si necesitamos un mayor control podemos modificar la duración y el rebote.
.animation(
.bouncy(duration: 0.6, extraBounce: 0.25),
value: selected
)
También existen inicializadores que permiten controlar directamente parámetros como response o dampingFraction, especialmente útiles en gestos interactivos.
Creando nuestras propias animaciones
Desde iOS 17 podemos implementar el protocolo CustomAnimation para definir completamente el comportamiento temporal de una animación.
Esto permite crear efectos imposibles de conseguir con las animaciones estándar, como movimientos con retroceso inicial, aceleraciones no lineales o transiciones inspiradas en videojuegos.
La implementación consiste en calcular el valor interpolado para cada fotograma, devolviendo nil cuando la animación finaliza.
Modificadores de Animation
Una misma animación puede reutilizarse modificando su comportamiento.
delay(_:)retrasa el inicio.speed(_:)acelera o ralentiza la animación.repeatCount(_:autoreverses:)la repite un número determinado de veces.repeatForever(autoreverses:)crea animaciones continuas.
Por ejemplo, podemos hacer aparecer una lista de elementos de forma escalonada:
ForEach(Array(items.enumerated()), id: \.offset) { index, item in
Text(item.title)
.opacity(visible ? 1 : 0)
.animation(
.smooth.delay(Double(index) * 0.08),
value: visible
)
}
¿Qué animación debería utilizar?
No existe una respuesta única.
- Utiliza linear cuando la velocidad constante sea importante.
- easeOut suele ser la mejor opción para mostrar contenido.
- easeIn funciona muy bien para ocultarlo.
- easeInOut resulta ideal para movimientos entre estados.
- smooth transmite estabilidad.
- snappy aporta una respuesta rápida y moderna.
- bouncy conviene reservarla para acciones que quieran llamar la atención.
Escoger correctamente el timing hace que una interfaz parezca más fluida, coherente y agradable a la vista sin necesidad de añadir animaciones complejas. Conocer todas las posibilidades que ofrece Animation permite construir experiencias mucho más pulidas aprovechando las herramientas nativas de SwiftUI.