Executando animações iOS em visualizações com UIKit e UIView

Publicados: 2022-03-10
Resumo rápido ↬ Este artigo pretende ser uma cartilha sobre animações iOS cobrindo exaustivamente diferentes maneiras de fazê-lo. Começamos entendendo o básico das animações, passamos para o Core Frameworks construindo um único exemplo usando os diferentes métodos oferecidos e, finalmente, procurando maneiras de ajustar o desempenho.

Sou desenvolvedor iOS há mais de uma década e raramente vejo artigos que consolidam todas as formas possíveis de realizar animações no iOS. Este artigo pretende ser uma cartilha sobre animações iOS com a intenção de cobrir exaustivamente as diferentes maneiras de fazer o mesmo.

Dada a extensão do tópico, cobriríamos cada parte sucintamente em um nível bastante alto. O objetivo é educar o leitor com um conjunto de opções para adicionar animações ao seu aplicativo iOS.

Antes de começarmos com os tópicos relacionados ao iOS, vamos dar uma breve olhada na velocidade da animação.

Animando a 60FPS

Geralmente em vídeos, cada quadro é representado por uma imagem e a taxa de quadros determina o número de imagens invertidas na sequência. Isso é denominado como 'quadros por segundo' ou FPS.

O FPS determina o número de imagens estáticas invertidas em um segundo, o que significa literalmente que quanto maior o número de imagens/quadros, mais detalhes/informações são exibidos no vídeo. Isso vale para animações também.

FPS é normalmente usado para determinar a qualidade das animações. Existe uma opinião popular de que qualquer boa animação deve rodar a 60fps ou superior – qualquer coisa abaixo de 60fps seria um pouco ruim.

Você quer ver a diferença entre 30FPS e 60FPS? Verifique isso!

Você notou a diferença? Os olhos humanos podem definitivamente sentir o jitter em fps mais baixos. Portanto, é sempre uma boa prática certificar-se de que qualquer animação que você criar esteja de acordo com a regra básica de rodar a 60FPS ou superior. Isso faz com que pareça mais realista e vivo.

Depois de analisar o FPS, vamos agora nos aprofundar nos diferentes frameworks principais do iOS que nos fornecem uma maneira de executar animações.

Mais depois do salto! Continue lendo abaixo ↓

Estruturas principais

Nesta seção, abordaremos as estruturas do SDK do iOS que podem ser usadas para criar animações de visualização. Faremos um rápido passeio por cada um deles, explicando seu conjunto de recursos com um exemplo relevante.

Animações UIKit/UIView

UIView é a classe base para qualquer exibição que exiba conteúdo em aplicativos iOS.

UIKit, o framework que nos dá o UIView, já nos fornece algumas funções básicas de animação que tornam conveniente para os desenvolvedores conseguir mais fazendo menos.

A API, UIView.animate , é a maneira mais fácil de animar visualizações, pois as propriedades de qualquer visualização podem ser facilmente animadas fornecendo os valores de propriedade na sintaxe baseada em bloco.

Em animações UIKit, é recomendado modificar apenas as propriedades animáveis ​​de UIVIew, caso contrário haverá repercussões onde as animações podem fazer com que a visualização termine em um estado inesperado.

animação(withDuration: animações: conclusão)

Esse método considera a duração da animação, um conjunto de alterações de propriedades animáveis ​​da exibição que precisam ser animadas. O bloco de conclusão fornece um retorno de chamada quando a exibição é concluída com a execução da animação.

Quase qualquer tipo de animação como movimento, dimensionamento, rotação, desvanecimento, etc. em uma visualização pode ser obtido com esta única API.

Agora, considere que você deseja animar uma mudança de tamanho de botão ou deseja que uma visualização específica seja ampliada na tela. É assim que podemos fazer isso usando a 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 }

Aqui está o que estamos fazendo aqui:

  1. Chamamos o método UIView.animate com um valor de duração passado para ele que representa quanto tempo a animação, descrita dentro do bloco, deve ser executada.
  2. Definimos o novo quadro do botão que deve representar o estado final da animação.
  3. Definimos o centro do botão com o center de sua supervisão para que permaneça no centro da tela.

O bloco de código de animação acima deve acionar a animação do quadro do botão mudando do quadro atual:

Width = 0, Height = 0

Para o quadro final:

Width = Height = newButtonWidth

E aqui está como a animação ficaria:

animar com duração

Este método é como uma extensão do método animate onde você pode fazer tudo o que pode realizar na API anterior com alguns comportamentos físicos adicionados às animações de visualização.

Por exemplo, se você deseja obter efeitos de amortecimento de mola na animação que fizemos acima, é assim que o código se pareceria:

 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)

Aqui está o conjunto de parâmetros que usamos:

  1. duration
    Representa a duração da animação determinando por quanto tempo o bloco de código deve ser executado.
  2. delay
    Representa o atraso inicial que queremos ter antes do início da animação.
  3. SpringWithDamping
    Representa o valor do efeito elástico que queremos que a vista se comporte. O valor deve estar entre 0 e 1. Quanto menor o valor, maior a oscilação da mola.
  4. velocity
    Representa a velocidade na qual a animação deve começar.
  5. options
    Tipo de curva de animação que você deseja aplicar à sua animação de exibição.
  6. Por fim, o bloco de código onde definimos o frame do botão que precisa ser animado. É o mesmo que a animação anterior.

E aqui está a aparência da animação com a configuração de animação acima:

UIViewPropertyAnimator

Para um pouco mais de controle sobre as animações, o UIViewPropertyAnimator é útil quando nos fornece uma maneira de pausar e retomar as animações. Você pode ter um tempo personalizado e fazer com que sua animação seja interativa e interrompível. Isso é muito útil ao executar animações que também podem interagir com as ações do usuário.

O gesto clássico 'Slide to Unlock' e a animação de descartar/expandir a visualização do player (no aplicativo Música) são exemplos de animações interativas e interrompíveis. Você pode começar a mover uma visualização com o dedo, soltá-lo e a visualização voltará à sua posição original. Como alternativa, você pode capturar a visualização durante a animação e continuar arrastando-a com o dedo.

A seguir está um exemplo simples de como podemos obter a animação 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

Aqui está o que estamos fazendo:

  1. Chamamos a API UIViewProperty passando a duração e a curva de animação.
  2. Ao contrário de ambas as APIs UIView.animate acima, a animação não será iniciada a menos que você a especifique por conta própria, ou seja, você está no controle total do processo/fluxo de animação completo.

Agora, digamos que você queira ainda mais controle sobre as animações. Por exemplo, você deseja projetar e controlar cada quadro na animação. Há outra API para isso, animateKeyframes . Mas antes de nos aprofundarmos nisso, vamos ver rapidamente o que é um quadro em uma animação.

O que é um frame ?

Uma coleção de mudanças/transições de quadro da visualização, do estado inicial ao estado final, é definida como animation e cada posição da visualização durante a animação é chamada de frame .

animar quadros-chave

Essa API fornece uma maneira de projetar a animação de forma que você possa definir várias animações com diferentes tempos e transições. Poste isso, a API simplesmente integra todas as animações em uma experiência perfeita.

Digamos que queremos mover nosso botão na tela de forma aleatória. Vamos ver como podemos usar a API de animação de quadro-chave para fazer isso.

 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 } })

Aqui está o desdobramento:

  1. duration
    Chame a API passando a duração da animação.
  2. delay
    Duração inicial do atraso da animação.
  3. options
    O tipo de curva de animação que você deseja aplicar à sua animação de exibição.
  4. animations
    Bloco que recebe todas as animações de keyframe projetadas pelo desenvolvedor/usuário.
  5. addKeyFrame
    Chame a API para projetar cada animação. No nosso caso, definimos cada movimento do botão. Podemos ter quantas animações precisarmos, adicionadas ao bloco.
  6. relativeStartTime
    Define a hora de início da animação na coleção do bloco de animação.
  7. relativeDuration
    Define a duração geral desta animação específica.
  8. center
    No nosso caso, simplesmente alteramos a propriedade center do botão para mover o botão pela tela.

E é assim que as animações finais se parecem:

CoreAnimation

Qualquer animação baseada em UIKit é traduzida internamente em animações principais. Assim, a estrutura Core Animation atua como uma camada de apoio ou backbone para qualquer animação UIKit. Portanto, todas as APIs de animação UIKit nada mais são do que camadas encapsuladas das APIs principais de animação de uma maneira facilmente consumível ou conveniente.

As APIs de animação UIKit não fornecem muito controle sobre as animações que foram executadas em uma exibição, pois são usadas principalmente para propriedades animáveis ​​da exibição. Portanto, nesses casos, em que você pretende ter controle sobre cada quadro da animação, é melhor usar diretamente as APIs principais de animação subjacentes. Como alternativa, as animações UIView e as animações principais também podem ser usadas em conjunto.

UIView + Animação principal

Vamos ver como podemos recriar a mesma animação de alteração de botão junto com a especificação da curva de tempo usando as APIs UIView e Core Animation.

Podemos usar as funções de temporização do CATransaction , que permitem especificar e controlar a curva de animação.

Vejamos um exemplo de uma animação de mudança de tamanho de botão com seu raio de canto utilizando a função de tempo do CATransaction e uma combinação de animações 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

Aqui está o desdobramento:

  1. begin
    Representa o início do bloco de código de animação.
  2. duration
    Duração geral da animação.
  3. curve
    Representa a curva de tempo que precisa ser aplicada à animação.
  4. UIView.animate
    Nossa primeira animação para mudar o frame do botão.
  5. CABasicAnimation
    Criamos o objeto CABasicAnimation referindo o cornerRadius do botão como o keypath, pois é isso que queremos animar. Da mesma forma, se você quiser ter controle de nível granular sobre as animações de quadro-chave, poderá usar a classe CAKeyframeAnimation .
  6. fromValue
    Representa o valor inicial da animação, ou seja, o valor cornerRadius inicial do botão de onde a animação deve começar.
  7. toValue
    Representa o valor final da animação, ou seja, o valor final cornerRadius do botão onde a animação deve terminar.
  8. cornerRadius
    Devemos definir a propriedade cornerRadius do botão com o valor final da animação, caso contrário o valor cornerRadius do botão será revertido automaticamente para seu valor inicial após a conclusão da animação.
  9. addAnimation
    Anexamos o objeto de animação que contém a configuração de todo o processo de animação à camada, representando o Keypath para o qual a animação precisa ser executada.
  10. commit
    Representa o final do bloco de código de animação e inicia a animação.

É assim que a animação final ficaria:

Este blog é uma ótima leitura para ajudar a criar animações mais avançadas, pois orienta você na maioria das APIs da estrutura Core Animation com instruções que o orientam em todas as etapas do caminho.

UIKitDynamics

O UIKit Dynamics é o mecanismo de física do UIKit que permite adicionar quaisquer comportamentos físicos, como colisão, gravidade, empurrar, encaixar, etc., aos controles do UIKit.

UIKitDynamicAnimator

Esta é a classe de administração da estrutura UIKit Dynamics que regula todas as animações acionadas por qualquer controle de interface do usuário.

UIKitDynamicBehavior

Ele permite que você adicione qualquer comportamento físico a um animador, o que permite que ele seja executado na visualização anexada a ele.

Diferentes tipos de comportamentos para UIKitDynamics incluem:

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

A arquitetura do UIKitDynamics se parece com isso. Observe que os itens 1 a 5 podem ser substituídos por uma única visualização.

Vamos aplicar algum comportamento físico ao nosso botão. Veremos como aplicar a gravidade ao botão para que nos dê a sensação de lidar com um objeto real.

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

Aqui está o desdobramento:

  1. UIKitDynamicAnimator
    Criamos um objeto UIKitDynamicAnimator que atua como um orquestrador para executar animações. Também passamos a superview do nosso botão como view de referência.
  2. UIGravityBehavior
    Criamos um objeto UIGravityBehavior e passamos nosso botão para os elementos do array nos quais esse comportamento é injetado.
  3. addBehavior
    Adicionamos o objeto de gravidade ao animador.

    Isso deve criar uma animação como mostrado abaixo:
    Observe como o botão cai do centro (sua posição original) da tela para a parte inferior e além.
    Devemos dizer ao animador para considerar a parte inferior da tela como o chão. É aqui que UICollisionBehavior entra em cena.

     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
    Criamos um objeto UICollisionBehavior e passamos o botão para que o comportamento seja adicionado ao elemento.
  5. translatesReferenceBoundsIntoBoundary
    Habilitar esta propriedade diz ao animador para tomar o limite das vistas de referência como o final, que é a parte inferior da tela em nosso caso.
  6. addBehavior
    Adicionamos o comportamento de colisão ao animador aqui.

    Agora, nosso botão deve atingir o chão e ficar parado como mostrado abaixo:

    Isso é muito legal, não é?

    Agora, vamos tentar adicionar um efeito de salto para que nosso objeto pareça mais real. Para fazer isso, usaremos a classe 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
    Criamos um objeto UIDynamicItemBehavior e passamos o botão para que o comportamento seja adicionado ao elemento.
  8. elasticity
    O valor deve estar entre 0-1, representa a elasticidade, ou seja, o número de vezes que o objeto deve saltar para dentro e para fora do chão quando é atingido. É aqui que a mágica acontece – ajustando essa propriedade, você pode diferenciar entre diferentes tipos de objetos, como bolas, garrafas, objetos rígidos e assim por diante.
  9. addBehavior
    Adicionamos o comportamento de colisão ao animador aqui.

Agora, nosso botão deve saltar quando atingir o chão, como mostrado abaixo:

Este repositório é bastante útil e mostra todos os comportamentos do UIKitDynamics em ação. Ele também fornece código-fonte para brincar com cada comportamento. Isso, na minha opinião, deve servir como uma extensa lista de maneiras de realizar animações iOS em views!

Na próxima seção, daremos uma breve olhada nas ferramentas que nos ajudarão a medir o desempenho das animações. Eu também recomendo que você procure maneiras de otimizar sua compilação do Xcode, pois isso economizará muito do seu tempo de desenvolvimento.

Ajuste de desempenho

Nesta seção, veremos maneiras de medir e ajustar o desempenho das animações do iOS. Como desenvolvedor iOS, você já deve ter usado instrumentos Xcode, como vazamentos de memória e alocações, para medir o desempenho geral do aplicativo. Da mesma forma, existem instrumentos que podem ser usados ​​para medir o desempenho das animações.

Instrumento Core Animation

Experimente o instrumento Core Animation e você poderá ver o FPS que a tela do seu aplicativo oferece. Essa é uma ótima maneira de medir o desempenho/velocidade de qualquer animação renderizada em seu aplicativo iOS.

Desenho

O FPS é bastante reduzido no aplicativo que exibe conteúdo pesado como imagens com efeitos como sombras. Nesses casos, em vez de atribuir a Image diretamente à propriedade image do UIImageView , tente desenhar a imagem separadamente em um contexto usando APIs de gráficos principais. Isso reduz excessivamente o tempo de exibição da imagem executando a lógica de descompactação da imagem de forma assíncrona quando feita em um thread separado em vez do thread principal.

Rasterização

A rasterização é um processo usado para armazenar em cache informações de camadas complexas para que essas exibições não sejam redesenhadas sempre que forem renderizadas. O redesenho de visualizações é a principal causa da redução no FPS e, portanto, é melhor aplicar a rasterização em visualizações que serão reutilizadas várias vezes.

Empacotando

Para concluir, também resumi uma lista de recursos úteis para animações iOS. Você pode achar isso muito útil ao trabalhar em animações do iOS. Além disso, você também pode achar este conjunto de ferramentas de design útil como uma etapa (de design) antes de se aprofundar nas animações.

Espero ter conseguido cobrir o maior número possível de tópicos em torno das animações do iOS. Se houver algo que eu possa ter perdido neste artigo, por favor, deixe-me saber na seção de comentários abaixo e eu ficaria feliz em fazer a adição!