Se você é um desenvolvedor iOS que fez uma quantidade razoável de trabalho de interface do usuário e é apaixonado por ele, você tem que amar o poder do UIKit quando se trata de animações. Animar um UIView é tão fácil quanto um bolo. Você não precisa pensar muito sobre como fazê-lo desaparecer, girar, mover ou encolher / expandir ao longo do tempo. No entanto, fica um pouco complicado se você quiser encadear animações e configurar dependências entre elas. Seu código pode acabar sendo bastante prolixo e difícil de seguir, com muitos fechamentos aninhados e níveis de indentação.
ponto de venda baseado em android
Neste artigo, explorarei como aplicar o poder de uma estrutura reativa como RxSwift para fazer o código parecer muito mais limpo, bem como mais fácil de ler e seguir. A ideia surgiu quando eu estava trabalhando em um projeto para um cliente. Esse cliente em particular era muito experiente em IU (o que combinava perfeitamente com minha paixão)! Eles queriam que a IU de seu aplicativo se comportasse de uma maneira muito particular, com muitas transições e animações muito elegantes. Uma de suas ideias era ter uma introdução ao aplicativo que contasse a história do que o aplicativo tratava. Eles queriam que a história fosse contada por meio de uma sequência de animações, em vez de reproduzir um vídeo pré-renderizado para que pudesse ser facilmente ajustado e ajustado. RxSwift acabou sendo a escolha perfeita para um problema como esse, como espero que você perceba assim que terminar o artigo.
A programação reativa está se tornando um grampo e foi adotada na maioria das linguagens de programação modernas. Existem muitos livros e blogs que explicam detalhadamente por que a programação reativa é um conceito tão poderoso e como ela ajuda a encorajar um bom design de software ao impor certos princípios e padrões de design. Ele também fornece um kit de ferramentas que pode ajudá-lo a reduzir significativamente a confusão de código.
Eu gostaria de tocar em um aspecto que eu realmente gosto - a facilidade com que você pode encadear operações assíncronas e expressá-las de forma declarativa e fácil de ler.
Quando se trata de Swift, existem dois frameworks concorrentes que ajudam a transformá-lo em uma linguagem de programação reativa: ReactiveSwift e RxSwift. Usarei RxSwift em meus exemplos não porque seja melhor, mas porque estou mais familiarizado com ele. Presumirei que você, leitor, também esteja familiarizado com ele, para que eu possa ir direto ao ponto principal.
Digamos que você queira girar uma vista 180 ° e então desvanece-se. Você pode fazer uso do completion
encerramento e fazer algo assim:
UIView.animate(withDuration: 0.5, animations: { self.animatableView.transform = CGAffineTransform(rotationAngle: .pi/2) }, completion: { _ in UIView.animate(withDuration: 0.5) { self.animatableView.alpha = 0 } })
É um pouco volumoso, mas ainda está bom. Mas e se você quiser inserir mais uma animação no meio, digamos, deslocar a visualização para a direita depois de girar e antes que desapareça? Aplicando a mesma abordagem, você vai acabar com algo assim:
UIView.animate(withDuration: 0.5, animations: { self.animatableView.transform = CGAffineTransform(rotationAngle: .pi/2) }, completion: { _ in UIView.animate(withDuration: 0.5, animations: { self.animatableView.frame = self.animatableView.frame.offsetBy(dx: 50, dy: 0) }, completion: { _ in UIView.animate(withDuration: 0.5, animations: { self.animatableView.alpha = 0 }) }) })
Quanto mais etapas você adicionar, mais confuso e complicado ele se torna. E então, se você decidir alterar a ordem de certas etapas, terá que realizar algumas sequências não triviais de recortar e colar, que estão sujeitas a erros.
Bem, a Apple obviamente pensou nisso - eles oferecem uma maneira melhor de fazer isso, usando uma API de animações baseada em quadros-chave. Com essa abordagem, o código acima poderia ser reescrito assim:
UIView.animateKeyframes(withDuration: 1.5, delay: 0, options: [], animations: { UIView.addKeyframe(withRelativeStartTime: 0, relativeDuration: 0.33, animations: { self.animatableView.transform = CGAffineTransform(rotationAngle: .pi/2) }) UIView.addKeyframe(withRelativeStartTime: 0.33, relativeDuration: 0.33, animations: { self.animatableView.frame = self.animatableView.frame.offsetBy(dx: 50, dy: 0) }) UIView.addKeyframe(withRelativeStartTime: 0.66, relativeDuration: 0.34, animations: { self.animatableView.alpha = 0 }) })
Essa é uma grande melhoria, com as principais vantagens sendo:
como funcionam os motores de física
A desvantagem dessa abordagem é que você precisa pensar em termos de durações relativas e se torna difícil (ou pelo menos não muito simples) alterar o tempo absoluto ou a ordem das etapas. Pense em quais cálculos você teria que passar e que tipo de mudanças você teria que fazer na duração geral e durações / tempos de início relativos para cada uma das animações se você decidisse fazer a visualização desaparecer em 1 segundo em vez de meio segundo, mantendo todo o resto igual. O mesmo acontece se você quiser alterar a ordem das etapas - você terá que recalcular seus horários de início relativos.
Dadas as desvantagens, não acho nenhuma das abordagens acima boa o suficiente. A solução ideal que procuro deve satisfazer os seguintes critérios:
Descobri que, usando RxSwift, posso facilmente realizar esses dois objetivos. RxSwift não é a única estrutura que você pode usar para fazer algo assim - qualquer estrutura baseada em promessa que permite que você agrupe operações assíncronas em métodos que podem ser sintaticamente encadeados sem fazer uso de blocos de conclusão. Mas RxSwift tem muito mais a oferecer com sua gama de operadoras, que falaremos um pouco mais tarde.
Aqui está o esboço de como vou fazer isso:
Observable
.flatMap
operador.É assim que minhas funções podem ser:
func rotate(_ view: UIView, duration: TimeInterval) -> Observable { return Observable.create { (observer) -> Disposable in UIView.animate(withDuration: duration, animations: { view.transform = CGAffineTransform(rotationAngle: .pi/2) }, completion: { (_) in observer.onNext(()) observer.onCompleted() }) return Disposables.create() } } func shift(_ view: UIView, duration: TimeInterval) -> Observable { return Observable.create { (observer) -> Disposable in UIView.animate(withDuration: duration, animations: { view.frame = view.frame.offsetBy(dx: 50, dy: 0) }, completion: { (_) in observer.onNext(()) observer.onCompleted() }) return Disposables.create() } } func fade(_ view: UIView, duration: TimeInterval) -> Observable { return Observable.create { (observer) -> Disposable in UIView.animate(withDuration: duration, animations: { view.alpha = 0 }, completion: { (_) in observer.onNext(()) observer.onCompleted() }) return Disposables.create() } }
E aqui está como eu coloquei tudo junto:
como testar uma classe em java
rotate(animatableView, duration: 0.5) .flatMap { [unowned self] in self.shift(self.animatableView, duration: 0.5) } .flatMap { [unowned self] in self.fade(self.animatableView, duration: 0.5) } .subscribe() .disposed(by: disposeBag)
Certamente é muito mais código do que nas implementações anteriores e pode parecer um pouco exagerado para uma sequência tão simples de animações, mas a beleza é que pode ser estendido para lidar com algumas sequências de animação bastante complexas e é muito fácil de ler devido a a natureza declarativa da sintaxe.
Depois de aprender a lidar com isso, você pode criar animações tão complexas quanto um filme e ter à sua disposição uma grande variedade de operadores RxSwift úteis que você pode aplicar para realizar coisas que seriam muito difíceis de fazer com qualquer uma das abordagens acima mencionadas.
Aqui está como podemos usar o .concat
operador para tornar meu código ainda mais conciso - a parte em que as animações são encadeadas:
Observable.concat([ rotate(animatableView, duration: 0.5), shift(animatableView, duration: 0.5), fade(animatableView, duration: 0.5) ]) .subscribe() .disposed(by: disposeBag)
Você pode inserir atrasos entre animações como este:
func delay(_ duration: TimeInterval) -> Observable { return Observable.of(()).delay(duration, scheduler: MainScheduler.instance) } Observable.concat([ rotate(animatableView, duration: 0.5), delay(0.5), shift(animatableView, duration: 0.5), delay(1), fade(animatableView, duration: 0.5) ]) .subscribe() .disposed(by: disposeBag)
Agora, vamos supor que queremos que a vista gire um certo número de vezes antes de começar a se mover. E queremos ajustar facilmente quantas vezes ele deve girar.
Primeiro, farei um método que repete a animação de rotação continuamente e emite um elemento após cada rotação. Quero que essas rotações parem assim que o observável for descartado. Eu poderia fazer algo assim:
como se tornar certificado pelo aws
func rotateEndlessly(_ view: UIView, duration: TimeInterval) -> Observable { var disposed = false return Observable.create { (observer) -> Disposable in func animate() { UIView.animate(withDuration: duration, animations: { view.transform = view.transform.rotated(by: .pi/2) }, completion: { (_) in observer.onNext(()) if !disposed { animate() } }) } animate() return Disposables.create { disposed = true } } }
E então minha bela cadeia de animações poderia ser assim:
Observable.concat([ rotateEndlessly(animatableView, duration: 0.5).take(5), shift(animatableView, duration: 0.5), fade(animatableView, duration: 0.5) ]) .subscribe() .disposed(by: disposeBag)
Você vê como é fácil controlar quantas vezes a visualização irá girar - basta alterar o valor passado para o take
operador.
Agora, gostaria de levar minha implementação um passo adiante, envolvendo cada uma das funções de animação que criei na extensão 'Reativa' do UIView (acessível por meio do sufixo .rx
). Isso o tornaria mais próximo das convenções RxSwift, em que funções reativas são geralmente acessadas por meio do .rx
sufixo para deixar claro que eles estão retornando um observável.
extension Reactive where Base == UIView { func shift(duration: TimeInterval) -> Observable { return Observable.create { (observer) -> Disposable in UIView.animate(withDuration: duration, animations: { self.base.frame = self.base.frame.offsetBy(dx: 50, dy: 0) }, completion: { (_) in observer.onNext(()) observer.onCompleted() }) return Disposables.create() } } func fade(duration: TimeInterval) -> Observable { return Observable.create { (observer) -> Disposable in UIView.animate(withDuration: duration, animations: { self.base.alpha = 0 }, completion: { (_) in observer.onNext(()) observer.onCompleted() }) return Disposables.create() } } func rotateEndlessly(duration: TimeInterval) -> Observable { var disposed = false return Observable.create { (observer) -> Disposable in func animate() { UIView.animate(withDuration: duration, animations: { self.base.transform = self.base.transform.rotated(by: .pi/2) }, completion: { (_) in observer.onNext(()) if !disposed { animate() } }) } animate() return Disposables.create { disposed = true } } } }
Com isso, posso colocá-los juntos assim:
Observable.concat([ animatableView.rx.rotateEndlessly(duration: 0.5).take(5), animatableView.rx.shift(duration: 0.5), animatableView.rx.fade(duration: 0.5) ]) .subscribe() .disposed(by: disposeBag)
Como este artigo ilustra, ao liberar o poder do RxSwift, e depois de ter seus primitivos no lugar, você pode se divertir muito com as animações. Seu código é limpo e fácil de ler, e não se parece mais com 'código' - você 'descreve' como suas animações são montadas e elas ganham vida! Se você quiser fazer algo mais elaborado do que o descrito aqui, certamente poderá fazer isso adicionando mais primitivos seus, encapsulando outros tipos de animações. Você também pode sempre aproveitar as vantagens do arsenal crescente de ferramentas e estruturas desenvolvidas por algumas pessoas apaixonadas na comunidade de código aberto.
Se você é um convertido RxSwift, continue visitando o Repositório RxSwiftCommunity regularmente: você sempre pode encontrar algo novo!
Se você estiver procurando por mais conselhos sobre a interface do usuário, tente ler Como implementar um design de interface do usuário iOS perfeito para pixels pelo colega ApeeScapeer Roman Stetsenko.
Notas da fonte (entendendo o básico)
como hackear um número de cartão de crédito Visa Master Cards
'Swift é uma linguagem de programação compilada multi-paradigma de propósito geral desenvolvida pela Apple Inc. para iOS, macOS, watchOS, tvOS, Linux ez / OS. O Swift foi projetado para funcionar com as estruturas Cocoa e Cocoa Touch da Apple e o grande corpo de código Objective-C existente escrito para produtos Apple. '
'A classe UIView define uma área retangular na tela na qual mostra o conteúdo. Ele lida com a renderização de qualquer conteúdo em sua área e também com quaisquer interações com esse conteúdo - toques e gestos. '
A programação reativa é um paradigma de programação assíncrona preocupado com fluxos de dados assíncronos e a propagação da mudança.
RxSwift é a biblioteca de programação reativa para iOS. Facilita a programação de aplicativos dinâmicos que respondem a alterações de dados e eventos do usuário.
Os observáveis fornecem suporte para a passagem de mensagens entre editores e assinantes em seu aplicativo. Os observáveis convertem fluxos de dados em sequência de eventos entregues a um ou mais assinantes de forma assíncrona.