Con iOS 17, Apple introdujo por fin soporte nativo para keyframe animations en SwiftUI. Hasta entonces, quienes veníamos de UIKit estábamos acostumbrados a encadenar animaciones complejas con APIs de keyframes, mientras que en SwiftUI había que recurrir a soluciones poco elegantes o directamente a código imperativo.
Ahora las keyframe animations en SwiftUI permiten describir animaciones complejas como una secuencia de tramos bien definidos, cada uno con su propia curva, duración y propiedad a animar. En Juice Studio nos interesa ver esta API como una herramienta seria para organizar el motion de una app moderna.
En este artículo vamos a ver qué son las keyframe animations en SwiftUI, cómo funciona keyframeAnimator, cómo reescribir animaciones existentes, qué ventajas y limitaciones tiene la API actual y cómo introducirla de forma ordenada en un proyecto.
Qué son las keyframe animations en SwiftUI
En animación, un “keyframe” es un punto en el tiempo donde fijamos un valor concreto de una propiedad: escala, posición, opacidad, etc. El sistema interpola entre esos keyframes para producir el movimiento.
Las keyframe animations en SwiftUI llevan este concepto a la API declarativa de SwiftUI:
- Definimos una estructura con todas las propiedades que queremos animar (por ejemplo
scaleyoffset) - Usamos
keyframeAnimator(initialValue:keyframes:)o su variante contrigger: - Dentro del closure
keyframes, creamos uno o variosKeyframeTracksobre propiedades concretas - En cada track combinamos
LinearKeyframe,CubicKeyframe,SpringKeyframeoMoveKeyframesegún el efecto que buscamos
La idea base es que las keyframe animations en SwiftUI nos permiten dividir una animación compleja en tramos muy legibles, en lugar de depender de un único withAnimation que intenta hacerlo todo a la vez.
Ejemplo básico: círculo que crece, sube y vuelve a su sitio
Este es un ejemplo muy sencillo para entender las keyframe animations en SwiftUI: un círculo rojo que:
- Primero crece de tamaño
- Después salta hacia arriba
- Finalmente cae y vuelve a su escala original
El timeline sería algo así:
- Tiempo 0 → 0,2: el círculo pasa de escala 1.0 a 1.2
- Tiempo 0,2 → 0,5: el círculo se mantiene en la nueva escala, pero se desplaza 100 puntos hacia arriba
- Tiempo 0,5 → 0,7: el círculo desciende y vuelve a escala 1.0
En código, el patrón básico para implementar estas keyframe animations en SwiftUI es:
- Definir una estructura de valores animados, por ejemplo:
struct CircleValues { var scale = 1.0 var offset = 0.0 } - Aplicar
keyframeAnimatoralCircle():initialValue: CircleValues()- En el closure
content, aplicar.scaleEffect(value.scale)y.offset(y: -value.offset) - En
keyframes, crear dosKeyframeTrack: uno parascaley otro paraoffsetcon la secuencia deCubicKeyframeySpringKeyframeque describe la animación
Este ejemplo de keyframe animations en SwiftUI es simple, pero ilustra la idea clave: separar la lógica de la animación (tracks y tiempos) de la vista que se está animando.
Circle()
.foregroundColor(.red)
.frame(width: 100)
.keyframeAnimator(initialValue: CircleValues()) { content, value in
content
.scaleEffect(value.scale)
.offset(y: -value.offset)
} keyframes: { _ in
KeyframeTrack(\.scale) {
CubicKeyframe(1.2, duration: 0.2)
CubicKeyframe(1.2, duration: 0.3)
CubicKeyframe(1, duration: 0.2)
}
KeyframeTrack(\.offset) {
SpringKeyframe(0, duration: 0.2, spring: .bouncy)
SpringKeyframe(100, duration: 0.3, spring: .bouncy)
SpringKeyframe(0, duration: 0.2, spring: .bouncy)
}
}

Del withAnimation tradicional a keyframeAnimator
Antes, para conseguir algo parecido a las keyframe animations en SwiftUI había que encadenar varios estados y withAnimation, o incluso recurrir a Timer y propiedades animables personalizadas.
En el caso del botón “wiggle” del tutorial:
- Se usaban varias propiedades
@State(t,tForBg) - Se disparaban dos
withAnimationcon curvas distintas para fondo y escala - Se añadía lógica manual para mapear de
0 → 1y luego1 → 0y conseguir un efecto de ida y vuelta
Con las keyframe animations en SwiftUI este patrón se simplifica:
- Definimos
AnimationValuescon los campos relevantes (growingScale,bgWiggle, etc.) - Usamos un único
keyframeAnimatorsobre la imagen del botón - Dentro, añadimos dos
KeyframeTrack: uno para la escala del botón y otro para el fondo “wiggle” - Cada track define sus tramos con curvas independientes (por ejemplo,
CubicKeyframepara la escala ySpringKeyframepara el fondo)
Resultado: las keyframe animations en SwiftUI permiten:
- Dar a cada propiedad su propia curva temporal
- Mantener todo el motion de un componente en un único bloque de código
- Reducir estados auxiliares y fórmulas manuales de interpolación
struct AnimationValues {
var growingScale = 1.0
var bgWiggle = 0.0
}
@available(iOS 17.0, *)
public struct KeyframeWiggleButton: View {
...
public var body: some View {
image
.keyframeAnimator(initialValue: AnimationValues(), trigger: isSelected) { content, value in
ZStack {
...
}
} keyframes: { _ in
// <--- 1
KeyframeTrack(\.growingScale) {
CubicKeyframe(1.3, duration: 0.1)
CubicKeyframe(1.4, duration: 0.1)
CubicKeyframe(1.3, duration: 0.1)
}
KeyframeTrack(\.bgWiggle) {
SpringKeyframe(0.0, duration: 0.05)
SpringKeyframe(3.0, duration: 0.1)
SpringKeyframe(1.0, duration: 0.1)
SpringKeyframe(2.0, duration: 0.1)
SpringKeyframe(0.0, duration: 0.1)
}
// —--> 1
}
}
}

Ventajas prácticas de las keyframe animations en SwiftUI
Al integrar keyframe animations en SwiftUI en un proyecto real, las ventajas más claras son:
1. Control fino por propiedad y por tramo
Cada KeyframeTrack controla una propiedad concreta, y dentro definimos tantos tramos temporales como necesitemos. Esto permite:
- Escala con una curva suave
- Offset vertical con una curva de rebote
- Opacidad con una transición lineal
La API de keyframe animations en SwiftUI admite distintos tipos de keyframe (LinearKeyframe, CubicKeyframe, SpringKeyframe, MoveKeyframe), que se combinan según el tipo de efecto que buscamos.
2. Código más legible y mantenible
En lugar de lógica dispersa en varios withAnimation, timers y propiedades @State, el patrón de keyframe animations en SwiftUI agrupa:
- Modelo de valores animados
- Vista que los consume
- Definición de tracks y tiempos
Esto facilita:
- Revisar y ajustar el diseño de motion en equipo
- Reutilizar patrones de animación entre componentes
- Documentar efectos complejos sin depender de comentarios extensos
3. Encaje con el resto del sistema de animación de SwiftUI
keyframeAnimator convive con:
withAnimationpara casos sencillosPhaseAnimatorpara transiciones entre estados discretos- Modificadores clásicos de animación en vistas individuales
Las keyframe animations en SwiftUI aportan la pieza que faltaba para animaciones que dependen del tiempo continuo y no solo del cambio de estado.
4. Mejor alineación con diseño de producto
Con las keyframe animations en SwiftUI es más fácil traducir especificaciones de diseño tipo “motion spec”:
- 0–200 ms: botón crece
- 200–500 ms: contenido rebota
- 500–700 ms: vuelve a su estado estable
Cada tramo se traduce en un keyframe o conjunto de keyframes, sin tener que “aproximar” la secuencia a un único bloque de animación.
Limitaciones actuales de las keyframe animations en SwiftUI
La API de keyframe animations en SwiftUI es potente, pero todavía tiene matices que conviene conocer.
1. Ausencia de lógica condicional dentro de los tracks
Ni keyframeAnimator ni KeyframeTrack permiten introducir if/else dentro de la definición de keyframes. En el ejemplo del botón:
- Queremos una animación “espectacular” al seleccionar
- Y una animación más discreta al deseleccionar
La solución actual pasa por:
- Incluir un track adicional para la escala de “vuelta”
- Gestionar desde el cuerpo de la vista qué valor leer (
growingScaleoshrinkingScale) según un@StatecomoisGrowing
Esto implica que ciertas partes de las keyframe animations en SwiftUI se calculan sin usarse cuando la rama lógica no está activa, lo que complica algo el código.
struct AnimationValues {
var growingScale = 1.0
var bgWiggle = 0.0
var shrinkingScale = 1.3 // <--- 3
}
@available(iOS 17.0, *)
public struct KeyframeWiggleButton: View {
...
@State private var isGrowing = true // <--- 3 new parameter to distinguish between growing and shrinking animations
public var body: some View {
image
.keyframeAnimator(initialValue: AnimationValues(), trigger: isSelected) { content, value in
ZStack {
KeyframeWiggleButtonBg(t: isGrowing ? value.bgWiggle : 0) // <--- 3 use the new parameter
.opacity(0.4)
.mask(maskImage)
content
}
.scaleEffect(isGrowing ? value.growingScale : value.shrinkingScale) // <--- 3 use the new parameter
} keyframes: { _ in
// same growingScale and bgWiggle
KeyframeTrack(\.shrinkingScale) { // <--- 3 start the shrinking, do it fast and without any extra effects
SpringKeyframe(0.9, duration: 0.2)
}
}
.onChange(of: isSelected) { _, newValue in
isGrowing = newValue // <--- 3 define if we need growing or shrinking animation
}
}
}
2. Comportamiento con la variante trigger
La variante keyframeAnimator(initialValue:trigger:content:keyframes:) dispara las keyframe animations en SwiftUI cuando cambia el valor de un Equatable. En la práctica, se han observado casos donde:
- El trigger se actualiza correctamente
- Pero la animación no se reproduce, aparentemente por el estado interno del animator
En el tutorial se recurre a un pequeño “hack”: usar un Timer para forzar la actualización de un estado y resetear la animación. Es un recordatorio de que la implementación de keyframe animations en SwiftUI aún está evolucionando y puede requerir workarounds puntuales.
3. Complejidad en animaciones muy grandes
Cuando una animación incluye muchas propiedades y tracks, es fácil que el bloque de keyframes crezca demasiado. En esos casos, al diseñar keyframe animations en SwiftUI es recomendable:
- Extraer tracks en funciones auxiliares
- Agrupar propiedades relacionadas en estructuras más pequeñas
- Documentar bien los tramos temporales más críticos

Buenas prácticas para diseñar keyframe animations en SwiftUI
A partir del tutorial y de la documentación oficial, hay una serie de patrones que funcionan bien con keyframe animations en SwiftUI.
Definir un modelo claro de valores animados
- Crear un struct específico por componente (
AnimationValues,CircleValues, etc.) - Agrupar solo las propiedades que se animan en la misma secuencia
- Evitar structs gigantes que mezclan estados de distintas animaciones
Esto hace que las keyframe animations en SwiftUI sean más previsibles y testables.
Separar tracks por intención
- Un track para escala
- Otro para desplazamiento
- Otro para opacidad o rotación
Cada track de keyframe animations en SwiftUI debería tener un propósito claro. Esto facilita iterar sobre curvas y duraciones sin romper el resto del efecto.
Empezar simple y luego refinar
- Prototipar primero con
LinearKeyframe - Cambiar después tramos concretos a
CubicKeyframeoSpringKeyframe - Añadir solo la complejidad necesaria para lograr el efecto de diseño
Las keyframe animations en SwiftUI invitan a sobrecargar la animación; es mejor partir de lo mínimo y añadir detalle poco a poco.
Medir impacto en rendimiento
En componentes muy usados (por ejemplo, celdas de listas grandes), conviene revisar:
- Cantidad de animaciones activas simultáneamente
- Coste de redibujar vistas complejas con cada frame
Aunque SwiftUI y la implementación de keyframe animations en SwiftUI están optimizadas, una cantidad excesiva de animaciones puede afectar a la fluidez, especialmente en dispositivos antiguos.

Cómo introducir keyframe animations en SwiftUI en un proyecto existente
Una forma razonable de empezar a trabajar con keyframe animations en SwiftUI es hacerlo por etapas.
Exploración de casos y prototipos
- Revisar animaciones actuales que se apoyan en varios
withAnimationo código complejo - Identificar 1–2 componentes donde el cambio a keyframe animations en SwiftUI pueda simplificar la lógica (botones interactivos, loaders, transiciones entre pantallas)
- Crear prototipos aislados en un target de pruebas, sin tocar todavía la app principal
Migración selectiva
- Reescribir una animación compleja usando
keyframeAnimatoryKeyframeTrack - Validar que el resultado visual respeta la intención de diseño original
- Medir rendimiento y estabilidad en dispositivos reales
- Documentar el patrón para el resto del equipo (ejemplo de struct de valores, estructura de tracks, convenciones de nombres)
En esta fase, la adopción de keyframe animations en SwiftUI debería ir acompañada de pequeñas guías internas y ejemplos reutilizables.
Estandarización
- Definir qué tipos de animación se resuelven con keyframe animations en SwiftUI y cuáles siguen con
withAnimation - Crear una pequeña “biblioteca interna” de animaciones reutilizables: buttons, cards, transiciones
- Revisar el sistema de diseño para alinear motion, tiempos y curvas entre componentes
El objetivo es que las keyframe animations en SwiftUI dejen de ser un experimento aislado y pasen a formar parte del lenguaje visual de la app.
Conclusión
Las keyframe animations en SwiftUI cubren una pieza que faltaba en el stack de animación de SwiftUI: control detallado sobre el tiempo y las curvas de cada propiedad sin salir del modelo declarativo. Permiten reescribir animaciones complejas con menos estados, menos código “hacky” y una estructura mucho más legible.
Al mismo tiempo, la API de keyframe animations en SwiftUI todavía tiene limitaciones claras (condicionales, comportamiento de trigger, complejidad en animaciones muy grandes), por lo que merece la pena introducirla de forma gradual, empezando por componentes clave y midiendo resultados.
Desde la perspectiva de diseño de producto, el valor está en poder traducir especificaciones de motion a código casi 1:1, manteniendo la intención original del equipo de diseño y evitando degradaciones cuando la animación pasa de Figma a Xcode. Ahí es donde las keyframe animations en SwiftUI marcan la diferencia.