Realización de animaciones de iOS en vistas con UIKit y UIView

Publicado: 2022-03-10
Resumen rápido ↬ Este artículo pretende ser una introducción a las animaciones de iOS que cubre exhaustivamente las diferentes formas de hacerlo. Comenzamos por comprender los conceptos básicos de las animaciones, pasamos a Core Frameworks construyendo un solo ejemplo usando los diferentes métodos ofrecidos y finalmente buscando formas de ajustar el rendimiento.

He sido desarrollador de iOS durante más de una década y rara vez he visto artículos que consoliden todas las formas posibles de realizar animaciones en iOS. Este artículo pretende ser una introducción a las animaciones de iOS con la intención de cubrir exhaustivamente las diferentes formas de hacer lo mismo.

Dada la amplitud del tema, cubriríamos cada parte sucintamente a un nivel bastante alto. El objetivo es educar al lector con un conjunto de opciones para agregar animaciones a su aplicación iOS.

Antes de comenzar con temas relacionados con iOS, echemos un breve vistazo a la velocidad de la animación.

Animación a 60FPS

Por lo general, en los videos, cada fotograma está representado por una imagen y la velocidad de fotogramas determina el número de imágenes volteadas en la secuencia. Esto se denomina 'fotogramas por segundo' o FPS.

FPS determina la cantidad de imágenes fijas volteadas en un segundo, lo que literalmente significa que cuanto mayor sea la cantidad de imágenes/fotogramas, más detalles/información se mostrarán en el video. Esto también es válido para las animaciones.

FPS se usa típicamente para determinar la calidad de las animaciones. Existe la opinión popular de que cualquier buena animación debe ejecutarse a 60 fps o más; cualquier cosa por debajo de 60 fps se sentiría un poco fuera de lugar.

¿Quieres ver la diferencia entre 30FPS y 60FPS? ¡Mira esto!

¿Notaste la diferencia? Los ojos humanos definitivamente pueden sentir la inestabilidad a fps más bajos. Por lo tanto, siempre es una buena práctica asegurarse de que cualquier animación que cree se adhiera a la regla básica de ejecutarse a 60 FPS o más. Esto lo hace sentir más realista y vivo.

Habiendo visto FPS, profundicemos ahora en los diferentes marcos de trabajo principales de iOS que nos brindan una forma de realizar animaciones.

¡Más después del salto! Continúe leyendo a continuación ↓

Marcos básicos

En esta sección, abordaremos los marcos del SDK de iOS que se pueden usar para crear animaciones de vista. Haremos un recorrido rápido por cada uno de ellos, explicando su conjunto de características con un ejemplo relevante.

Animaciones UIKit/UIView

UIView es la clase base para cualquier vista que muestre contenido en aplicaciones de iOS.

UIKit, el marco que nos brinda UIView, ya nos brinda algunas funciones básicas de animación que hacen que sea conveniente para los desarrolladores lograr más haciendo menos.

La API, UIView.animate , es la forma más sencilla de animar vistas, ya que las propiedades de cualquier vista se pueden animar fácilmente proporcionando los valores de propiedad en la sintaxis basada en bloques.

En las animaciones de UIKit, se recomienda modificar solo las propiedades animables de UIVIew; de lo contrario, habrá repercusiones en las que las animaciones podrían hacer que la vista termine en un estado inesperado.

animación (withDuration: animaciones: finalización)

Este método toma en cuenta la duración de la animación, un conjunto de cambios de propiedades animables de la vista que deben animarse. El bloque de finalización devuelve una llamada cuando la vista termina de realizar la animación.

Casi cualquier tipo de animación como mover, escalar, rotar, atenuar, etc. en una vista se puede lograr con esta única API.

Ahora, considere que desea animar un cambio de tamaño de botón o desea una vista particular para hacer zoom en la pantalla. Así es como podemos hacerlo usando la API UIView.animate :

 let newButtonWidth: CGFloat = 60 UIView.animate(withDuration: 2.0) { //1 self.button.frame = CGRect(x: 0, y: 0, width: newButtonWidth, height: newButtonWidth) //2 self.button.center = self.view.center //3 }

Esto es lo que estamos haciendo aquí:

  1. Llamamos al método UIView.animate con un valor de duración pasado que representa cuánto tiempo debe ejecutarse la animación, descrita dentro del bloque.
  2. Establecemos el nuevo marco del botón que debe representar el estado final de la animación.
  3. Configuramos el centro del botón con el center de su supervista para que permanezca en el centro de la pantalla.

El bloque de código de animación anterior debería activar la animación del cuadro del botón que cambia del cuadro actual:

Width = 0, Height = 0

Al cuadro final:

Width = Height = newButtonWidth

Y así es como se vería la animación:

animarConDuración

Este método es como una extensión del método de animación donde puede hacer todo lo que puede realizar en la API anterior con algunos comportamientos físicos agregados a las animaciones de vista.

Por ejemplo, si desea lograr efectos de amortiguación de resorte en la animación que hemos hecho anteriormente, así es como se vería el código:

 let newButtonWidth: CGFloat = 60 UIView.animate(withDuration: 1.0, //1 delay: 0.0, //2 usingSpringWithDamping: 0.3, //3 initialSpringVelocity: 1, //4 options: UIView.AnimationOptions.curveEaseInOut, //5 animations: ({ //6 self.button.frame = CGRect(x: 0, y: 0, width: newButtonWidth, height: newButtonWidth) self.button.center = self.view.center }), completion: nil)

Aquí está el conjunto de parámetros que usamos:

  1. duration
    Representa la duración de la animación que determina cuánto tiempo debe ejecutarse el bloque de código.
  2. delay
    Representa el retraso inicial que queremos tener antes del inicio de la animación.
  3. SpringWithDamping
    Representa el valor del efecto elástico que queremos que se comporte en la vista. El valor debe estar entre 0 y 1. Cuanto menor sea el valor, mayor será la oscilación del resorte.
  4. velocity
    Representa la velocidad a la que debe comenzar la animación.
  5. options
    Tipo de curva de animación que desea aplicar a su animación de vista.
  6. Finalmente, el bloque de código donde establecemos el marco del botón que necesita ser animado. Es lo mismo que la animación anterior.

Y así es como se vería la animación con la configuración de animación anterior:

UIViewPropertyAnimator

Para tener un poco más de control sobre las animaciones, UIViewPropertyAnimator es útil porque nos brinda una forma de pausar y reanudar las animaciones. Puede tener un tiempo personalizado y hacer que su animación sea interactiva e interrumpible. Esto es muy útil cuando se realizan animaciones que también interactúan con las acciones del usuario.

El clásico gesto 'Deslizar para desbloquear' y la animación de descartar/expandir la vista del reproductor (en la aplicación Música) son ejemplos de animaciones interactivas e interrumpibles. Puede comenzar a mover una vista con el dedo, luego soltarlo y la vista volverá a su posición original. Alternativamente, puede captar la vista durante la animación y continuar arrastrándola con el dedo.

El siguiente es un ejemplo simple de cómo podríamos lograr la animación usando UIViewPropertyAnimator :

 let newButtonWidth: CGFloat = 60 let animator = UIViewPropertyAnimator(duration:0.3, curve: .linear) { //1 self.button.frame = CGRect(x: 0, y: 0, width: newButtonWidth, height: newButtonWidth) self.button.center = self.view.center } animator.startAnimation() //2

Esto es lo que estamos haciendo:

  1. Llamamos a la API UIViewProperty pasando la duración y la curva de animación.
  2. A diferencia de las dos API UIView.animate anteriores, la animación no comenzará a menos que lo especifiques tú mismo, es decir, tienes el control total del proceso/flujo de animación completo.

Ahora, digamos que desea tener aún más control sobre las animaciones. Por ejemplo, desea diseñar y controlar todos y cada uno de los fotogramas de la animación. Hay otra API para eso, animateKeyframes . Pero antes de profundizar en ello, veamos rápidamente qué es un cuadro, en una animación.

¿Qué es un frame ?

Una colección de cambios/transiciones de fotogramas de la vista, desde el estado inicial hasta el estado final, se define como animation y cada posición de la vista durante la animación se denomina frame .

animar fotogramas clave

Esta API proporciona una forma de diseñar la animación de tal manera que pueda definir varias animaciones con diferentes tiempos y transiciones. Publique esto, la API simplemente integra todas las animaciones en una experiencia perfecta.

Digamos que queremos mover nuestro botón en la pantalla de forma aleatoria. Veamos cómo podemos usar la API de animación de fotogramas clave para hacerlo.

 UIView.animateKeyframes(withDuration: 5, //1 delay: 0, //2 options: .calculationModeLinear, //3 animations: { //4 UIView.addKeyframe( //5 withRelativeStartTime: 0.25, //6 relativeDuration: 0.25) { //7 self.button.center = CGPoint(x: self.view.bounds.midX, y: self.view.bounds.maxY) //8 } UIView.addKeyframe(withRelativeStartTime: 0.5, relativeDuration: 0.25) { self.button.center = CGPoint(x: self.view.bounds.width, y: start.y) } UIView.addKeyframe(withRelativeStartTime: 0.75, relativeDuration: 0.25) { self.button.center = start } })

Aquí está el desglose:

  1. duration
    Llame a la API pasando la duración de la animación.
  2. delay
    Duración del retraso inicial de la animación.
  3. options
    El tipo de curva de animación que desea aplicar a su animación de vista.
  4. animations
    Bloque que toma todas las animaciones de fotogramas clave diseñadas por el desarrollador/usuario.
  5. addKeyFrame
    Llame a la API para diseñar todas y cada una de las animaciones. En nuestro caso, hemos definido cada movimiento del botón. Podemos tener tantas animaciones como necesitemos, añadidas al bloque.
  6. relativeStartTime
    Define la hora de inicio de la animación en la colección del bloque de animación.
  7. relativeDuration
    Define la duración general de esta animación específica.
  8. center
    En nuestro caso, simplemente cambiamos la propiedad central del botón para mover el botón por la pantalla.

Y así es como se ven las animaciones finales:

CoreAnimation

Cualquier animación basada en UIKit se traduce internamente en animaciones principales. Por lo tanto, el marco de Core Animation actúa como una capa de respaldo o columna vertebral para cualquier animación UIKit. Por lo tanto, todas las API de animación de UIKit no son más que capas encapsuladas de las API de animación principales de una manera conveniente o fácilmente consumible.

Las API de animación de UIKit no brindan mucho control sobre las animaciones que se han realizado en una vista, ya que se usan principalmente para las propiedades animables de la vista. Por lo tanto, en tales casos, en los que pretende tener control sobre cada cuadro de la animación, es mejor usar directamente las API de animación principales subyacentes. Alternativamente, tanto las animaciones de UIView como las animaciones principales también se pueden usar en conjunto.

UIView + Animación principal

Veamos cómo podemos recrear la misma animación de cambio de botón junto con la especificación de la curva de tiempo usando las API de UIView y Core Animation.

Podemos usar las funciones de temporización de CATransaction , que le permiten especificar y controlar la curva de animación.

Veamos un ejemplo de una animación de cambio de tamaño de botón con su radio de esquina utilizando la función de tiempo de CATransaction y una combinación de animaciones UIView:

 let oldValue = button.frame.width/2 let newButtonWidth: CGFloat = 60 /* Do Animations */ CATransaction.begin() //1 CATransaction.setAnimationDuration(2.0) //2 CATransaction.setAnimationTimingFunction(CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeInEaseOut)) //3 // View animations //4 UIView.animate(withDuration: 1.0) { self.button.frame = CGRect(x: 0, y: 0, width: newButtonWidth, height: newButtonWidth) self.button.center = self.view.center } // Layer animations let cornerAnimation = CABasicAnimation(keyPath: #keyPath(CALayer.cornerRadius)) //5 cornerAnimation.fromValue = oldValue //6 cornerAnimation.toValue = newButtonWidth/2 //7 button.layer.cornerRadius = newButtonWidth/2 //8 button.layer.add(cornerAnimation, forKey: #keyPath(CALayer.cornerRadius)) //9 CATransaction.commit() //10

Aquí está el desglose:

  1. begin
    Representa el inicio del bloque de código de animación.
  2. duration
    Duración general de la animación.
  3. curve
    Representa la curva de tiempo que debe aplicarse a la animación.
  4. UIView.animate
    Nuestra primera animación para cambiar el marco del botón.
  5. CABasicAnimation
    Creamos el objeto CABasicAnimation haciendo referencia al cornerRadius del botón como la ruta clave, ya que eso es lo que queremos animar. De manera similar, si desea tener un control de nivel granular sobre las animaciones de fotogramas clave, puede usar la clase CAKeyframeAnimation .
  6. fromValue
    Representa el valor inicial de la animación, es decir, el valor inicial de cornerRadius del botón desde donde debe comenzar la animación.
  7. toValue
    Representa el valor final de la animación, es decir, el valor final de cornerRadius del botón donde debe terminar la animación.
  8. cornerRadius
    Debemos establecer la propiedad cornerRadius del botón con el valor final de la animación; de lo contrario, el valor de cornerRadius del botón se revertirá automáticamente a su valor inicial después de que se complete la animación.
  9. addAnimation
    Adjuntamos el objeto de animación que contiene la configuración de todo el proceso de animación a la capa representando el Keypath para el que se debe realizar la animación.
  10. commit
    Representa el final del bloque de código de animación y comienza la animación.

Así es como se vería la animación final:

Este blog es una gran lectura para ayudar a crear animaciones más avanzadas, ya que lo guía claramente a través de la mayoría de las API del marco de Core Animation con instrucciones que lo guían en cada paso del camino.

UIKitDynamics

UIKit Dynamics es el motor de física para UIKit que le permite agregar cualquier comportamiento físico como colisión, gravedad, empuje, chasquido, etc., a los controles de UIKit.

UIKitDynamicAnimator

Esta es la clase de administrador del marco UIKit Dynamics que regula todas las animaciones desencadenadas por cualquier control de IU dado.

UIKitDynamicBehavior

Le permite agregar cualquier comportamiento físico a un animador que luego le permite actuar en la vista adjunta.

Los diferentes tipos de comportamientos para UIKitDynamics incluyen:

  • UIAttachmentBehavior
  • UICollisionBehavior
  • UIFieldBehavior
  • UIGravityBehavior
  • UIPushBehavior
  • UISnapBehavior

La arquitectura de UIKitDynamics se parece a esto. Tenga en cuenta que los elementos 1 a 5 se pueden reemplazar con una sola vista.

Apliquemos un comportamiento físico a nuestro botón. Veremos cómo aplicar gravedad al botón para que nos dé la sensación de estar ante un objeto real.

 var dynamicAnimator : UIDynamicAnimator! var gravityBehavior : UIGravityBehavior! dynamicAnimator = UIDynamicAnimator(referenceView: self.view) //1 gravityBehavior = UIGravityBehavior(items: [button]) //2 dynamicAnimator.addBehavior(gravityBehavior) //3

Aquí está el desglose:

  1. UIKitDynamicAnimator
    Hemos creado un objeto UIKitDynamicAnimator que actúa como orquestador para realizar animaciones. También hemos pasado la supervista de nuestro botón como vista de referencia.
  2. UIGravityBehavior
    Hemos creado un objeto UIGravityBehavior y pasamos nuestro botón a los elementos de la matriz en los que se inyecta este comportamiento.
  3. addBehavior
    Hemos agregado el objeto de gravedad al animador.

    Esto debería crear una animación como se muestra a continuación:
    Observe cómo el botón cae desde el centro (su posición original) de la pantalla hacia abajo y más allá.
    Deberíamos decirle al animador que considere la parte inferior de la pantalla como el suelo. Aquí es donde UICollisionBehavior entra en escena.

     var dynamicAnimator : UIDynamicAnimator! var gravityBehavior : UIGravityBehavior! var collisionBehavior : UICollisionBehavior! dynamicAnimator = UIDynamicAnimator(referenceView: self.view) //1 gravityBehavior = UIGravityBehavior(items: [button]) //2 dynamicAnimator.addBehavior(gravityBehavior) //3 collisionBehavior = UICollisionBehavior(items: [button]) //4 collisionBehavior.translatesReferenceBoundsIntoBoundary = true //5 dynamicAnimator.addBehavior(collisionBehavior) //6
  4. UICollisionBehavior
    Hemos creado un objeto UICollisionBehavior y pasado el botón para que el comportamiento se agregue al elemento.
  5. translatesReferenceBoundsIntoBoundary
    Habilitar esta propiedad le dice al animador que tome el límite de las vistas de referencia como el final, que es la parte inferior de la pantalla en nuestro caso.
  6. addBehavior
    Hemos agregado un comportamiento de colisión al animador aquí.

    Ahora, nuestro botón debe tocar el suelo y quedarse quieto como se muestra a continuación:

    Eso es bastante bueno, ¿no?

    Ahora, intentemos agregar un efecto de rebote para que nuestro objeto se sienta más real. Para hacer eso, usaremos la clase UIDynamicItemBehavior .
     var dynamicAnimator : UIDynamicAnimator! var gravityBehavior : UIGravityBehavior! var collisionBehavior : UICollisionBehavior! var bouncingBehavior : UIDynamicItemBehavior! dynamicAnimator = UIDynamicAnimator(referenceView: self.view) //1 gravityBehavior = UIGravityBehavior(items: [button]) //2 dynamicAnimator.addBehavior(gravityBehavior) //3 collisionBehavior = UICollisionBehavior(items: [button]) //4 collisionBehavior.translatesReferenceBoundsIntoBoundary = true //5 dynamicAnimator.addBehavior(collisionBehavior) //6 //Adding the bounce effect bouncingBehavior = UIDynamicItemBehavior(items: [button]) //7 bouncingBehavior.elasticity = 0.75 //8 dynamicAnimator.addBehavior(bouncingBehavior) //9
  7. UIDynamicItemBehavior
    Hemos creado un objeto UIDynamicItemBehavior y pasamos el botón para que el comportamiento se agregue al elemento.
  8. elasticity
    El valor debe estar entre 0 y 1, representa la elasticidad, es decir, el número de veces que el objeto debe rebotar en el suelo y fuera de él cuando es golpeado. Aquí es donde ocurre la magia: al modificar esta propiedad, puede diferenciar entre diferentes tipos de objetos como pelotas, botellas, objetos duros, etc.
  9. addBehavior
    Hemos agregado un comportamiento de colisión al animador aquí.

Ahora, nuestro botón debería rebotar cuando toca el suelo como se muestra a continuación:

Este repositorio es bastante útil y muestra todos los comportamientos de UIKitDynamics en acción. También proporciona código fuente para jugar con cada comportamiento. ¡Eso, en mi opinión, debería servir como una lista extensa de formas de realizar animaciones de iOS en las vistas!

En la siguiente sección, analizaremos brevemente las herramientas que nos ayudarán a medir el rendimiento de las animaciones. También le recomendaría que busque formas de optimizar su compilación de Xcode, ya que le ahorrará una gran cantidad de tiempo de desarrollo.

La optimización del rendimiento

En esta sección, veremos formas de medir y ajustar el rendimiento de las animaciones de iOS. Como desarrollador de iOS, es posible que ya haya utilizado instrumentos de Xcode, como fugas de memoria y asignaciones, para medir el rendimiento de la aplicación en general. Del mismo modo, existen instrumentos que se pueden utilizar para medir el rendimiento de las animaciones.

Instrumento Core Animation

Pruebe el instrumento Core Animation y debería poder ver el FPS que ofrece la pantalla de su aplicación. Esta es una excelente manera de medir el rendimiento/velocidad de cualquier animación renderizada en su aplicación iOS.

Dibujo

El FPS se reduce considerablemente en la aplicación que muestra contenido pesado como imágenes con efectos como sombras. En tales casos, en lugar de asignar la imagen directamente a la propiedad de imagen de UIImageView , intente dibujar la imagen por separado en un contexto utilizando las API de gráficos principales. Esto reduce demasiado el tiempo de visualización de la imagen al realizar la lógica de descompresión de la imagen de forma asincrónica cuando se realiza en un subproceso separado en lugar del subproceso principal.

Rasterización

La rasterización es un proceso que se utiliza para almacenar en caché información de capas complejas para que estas vistas no se vuelvan a dibujar cada vez que se representan. Redibujar las vistas es la principal causa de la reducción de FPS y, por lo tanto, es mejor aplicar la rasterización en las vistas que se van a reutilizar varias veces.

Terminando

Para concluir, también he resumido una lista de recursos útiles para las animaciones de iOS. Puede encontrar esto muy útil cuando trabaja en animaciones de iOS. Además, también puede encontrar útil este conjunto de herramientas de diseño como un paso (de diseño) antes de profundizar en las animaciones.

Espero haber podido cubrir tantos temas como sea posible en torno a las animaciones de iOS. Si hay algo que me haya perdido en este artículo, házmelo saber en la sección de comentarios a continuación y con gusto lo agregaré.