Olá Habr! Todo mundo gosta de aplicativos responsivos. Ainda melhor quando eles têm animações relevantes. Neste artigo, mostrarei e mostrarei com toda a "carne" como mostrar, ocultar, torcer, girar e fazer tudo corretamente com telas pop-up.

Inicialmente, eu queria escrever um artigo afirmando que no iOS 10 apareceu um conveniente UIViewPropertyAnimator
que resolve o problema de animações interrompidas. Agora eles podem ser parados, invertidos, continuados ou cancelados. A Apple chama essa interface de Fluido .
Mas então eu percebi: é difícil falar em interromper a animação dos controladores sem uma descrição de como essas transições são corretamente animadas. Portanto, haverá dois artigos. Nisto, descobriremos como mostrar e ocultar corretamente a tela e sobre interrupções na próxima.
Como eles funcionam
UIViewController
possui uma propriedade transitioningDelegate
. Este é um protocolo com funções diferentes, cada um retorna um objeto:
animationController
para animação,interactionController
para interromper animações,presentationController
para exibição: hierarquia, quadro etc.

Com base nisso, criaremos um painel pop-up:

Controladores de cozinha
Você pode animar a transição para controladores modais e para UINavigationController
(funciona através de UINavigationControllerDelegate
).
Vamos considerar transições modais. A configuração do controlador antes do show é um pouco incomum:
class ParentViewController: UIViewController { private let transition = PanelTransition()
- Crie um objeto que descreva a transição.
transitioningDelegate
marcado como weak
, portanto, você deve armazenar a transition
separadamente pelo link strong
. - Definimos nossa transição para
transitioningDelegate
. - Para controlar o método de exibição no
presentationController
você precisa especificar .custom
para modalPresentationStyle.
.
O controlador mostrado não sabe como é mostrado. E isso é bom.
Mostrar em meia tela
Vamos começar o código para PanelTransition
com presentationController
. Você trabalhou com ele se criou pop-ups através do UIPopoverController
. PresentationController
controla a exibição do controlador: quadro, hierarquia, etc. Ele decide como exibir popovers no iPad: com qual quadro, qual lado do botão exibir, adiciona desfoque ao fundo da janela e escurece sob ele.

Nossa estrutura é semelhante: vamos escurecer o fundo, colocar o quadro em tela cheia:

Para começar, no presentationController(forPresented:, presenting:, source:)
, retorne a classe PresentationController
:
class PanelTransition: NSObject, UIViewControllerTransitioningDelegate { func presentationController(forPresented presented: UIViewController, presenting: UIViewController?, source: UIViewController) -> UIPresentationController? { return presentationController = PresentationController(presentedViewController: presented, presenting: presenting ?? source) }
Por que 3 controladores são transmitidos e o que é fonte?Source
é o controlador no qual chamamos de animação do show. Mas o controlador que participará da tranche é o primeiro da hierarquia com definesPresentationContext = true
. Se o controlador mudar, o controlador indicador real estará no parâmetro de presenting.
Agora você pode implementar a classe PresentationController
. Para iniciantes, vamos enquadrar o futuro controlador. Existe um método frameOfPresentedViewInContainerView
para frameOfPresentedViewInContainerView
. Deixe o controlador ocupar a metade inferior da tela:
class PresentationController: UIPresentationController { override var frameOfPresentedViewInContainerView: CGRect { let bounds = containerView!.bounds let halfHeight = bounds.height / 2 return CGRect(x: 0, y: halfHeight, width: bounds.width, height: halfHeight) } }
Você pode iniciar o projeto e tentar mostrar a tela, mas nada acontecerá. Isso ocorre porque agora gerenciamos a hierarquia de visualizações e precisamos adicionar a visualização do controlador manualmente:
Ainda precisa colocar um quadro para o presentedView
. containerViewDidLayoutSubviews
é o melhor lugar, pois dessa forma podemos responder à rotação da tela:
Agora você pode correr. A animação será padrão para UIModalTransitionStyle.coverVertical
, mas o quadro terá metade do tamanho.
Escurecer o fundo
A próxima tarefa é escurecer o controlador de segundo plano para focar no que é mostrado.
PanelTransition
do PresentationController
e o substituiremos por uma nova classe no arquivo PanelTransition
. Na nova classe, haverá apenas um código para escurecer.
class DimmPresentationController: PresentationController
Crie uma exibição que sobreporemos sobre:
private lazy var dimmView: UIView = { let view = UIView() view.backgroundColor = UIColor(white: 0, alpha: 0.3) view.alpha = 0 return view }()
alpha
visualizações alpha
acordo com a animação de transição. Existem 4 métodos:
presentationTransitionWillBegin
presentationTransitionDidEnd
dismissalTransitionWillBegin
dismissalTransitionDidEnd
O primeiro é o mais difícil. Adicione dimmView
à hierarquia, abaixe o quadro e inicie a animação:
override func presentationTransitionWillBegin() { super.presentationTransitionWillBegin() containerView?.insertSubview(dimmView, at: 0) performAlongsideTransitionIfPossible { [unowned self] in self.dimmView.alpha = 1 } }
A animação é iniciada usando uma função auxiliar:
private func performAlongsideTransitionIfPossible(_ block: @escaping () -> Void) { guard let coordinator = self.presentedViewController.transitionCoordinator else { block() return } coordinator.animate(alongsideTransition: { (_) in block() }, completion: nil) }
Definimos o quadro para dimmView
em containerViewDidLayoutSubviews
(como da última vez):
override func containerViewDidLayoutSubviews() { super.containerViewDidLayoutSubviews() dimmView.frame = containerView!.frame }
A animação pode ser interrompida e cancelada e, se cancelada, o dimmView
deve ser removido da hierarquia:
override func presentationTransitionDidEnd(_ completed: Bool) { super.presentationTransitionDidEnd(completed) if !completed { self.dimmView.removeFromSuperview() } }
O processo inverso inicia nos métodos de ocultação. Mas agora você precisa remover o dimmView
apenas se a animação estiver concluída.
override func dismissalTransitionWillBegin() { super.dismissalTransitionWillBegin() performAlongsideTransitionIfPossible { [unowned self] in self.dimmView.alpha = 0 } } override func dismissalTransitionDidEnd(_ completed: Bool) { super.dismissalTransitionDidEnd(completed) if completed { self.dimmView.removeFromSuperview() } }
Agora o fundo está escurecendo.
Nós controlamos a animação
Mostramos o controlador abaixo
Agora podemos animar a aparência do controlador. Na classe PanelTransition
, retorne a classe que controlará a animação da aparência:
func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? { return PresentAnimation() }
A implementação do protocolo é simples:
extension PresentAnimation: UIViewControllerAnimatedTransitioning { func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval { return duration } func animateTransition(using transitionContext: UIViewControllerContextTransitioning) { let animator = self.animator(using: transitionContext) animator.startAnimation() } func interruptibleAnimator(using transitionContext: UIViewControllerContextTransitioning) -> UIViewImplicitlyAnimating { return self.animator(using: transitionContext) } }
O código da chave é um pouco mais complicado:
class PresentAnimation: NSObject { let duration: TimeInterval = 0.3 private func animator(using transitionContext: UIViewControllerContextTransitioning) -> UIViewImplicitlyAnimating {
O UIViewPropertyAnimator não funciona no iOS 9A solução alternativa é bastante simples: você não deve usar o animador no código animateTransition
, mas a antiga API UIView.animate…
Por exemplo, assim:
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) { let to = transitionContext.view(forKey: .to)! let finalFrame = transitionContext.finalFrame(for: transitionContext.viewController(forKey: .to)!) to.frame = finalFrame.offsetBy(dx: 0, dy: finalFrame.height) UIView.animate(withDuration: duration, delay: 0, usingSpringWithDamping: 1, initialSpringVelocity: 0, options: [.curveEaseOut], animations: { to.frame = finalFrame }) { (_) in transitionContext.completeTransition(!transitionContext.transitionWasCancelled) } } , `interruptibleAnimator(using transitionContext:)`
Se você não tornar uma interrupção, o método interruptibleAnimator poderá ser omitido. A descontinuidade será considerada no próximo artigo, inscreva-se.
Esconda o controlador para baixo
Tudo é o mesmo, apenas na direção oposta. Em Panel Transition
crie a classe DismissAnimation
:
func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? { return DismissAnimation() }
E nós percebemos isso. Classe de DismissAnimation
:
class DismissAnimation: NSObject { let duration: TimeInterval = 0.3 private func animator(using transitionContext: UIViewControllerContextTransitioning) -> UIViewImplicitlyAnimating { let from = transitionContext.view(forKey: .from)! let initialFrame = transitionContext.initialFrame(for: transitionContext.viewController(forKey: .from)!) let animator = UIViewPropertyAnimator(duration: duration, curve: .easeOut) { from.frame = initialFrame.offsetBy(dx: 0, dy: initialFrame.height) } animator.addCompletion { (position) in transitionContext.completeTransition(!transitionContext.transitionWasCancelled) } return animator } } extension DismissAnimation: UIViewControllerAnimatedTransitioning { func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval { return duration } func animateTransition(using transitionContext: UIViewControllerContextTransitioning) { let animator = self.animator(using: transitionContext) animator.startAnimation() } func interruptibleAnimator(using transitionContext: UIViewControllerContextTransitioning) -> UIViewImplicitlyAnimating { return self.animator(using: transitionContext) } }
Nesse local, você pode experimentar as partes:
- um cenário alternativo pode aparecer abaixo;
- à direita - navegação rápida no menu;
- acima - mensagem informativa:

Pizza Dodo , Lanche e Savey
Da próxima vez, adicionamos um fechamento interativo com um gesto e depois interrompemos a animação. Se você não pode esperar, o projeto completo já está no github.
E aqui chegou a segunda parte do artigo.
Inscreva- se no canal Dodo Pizza Mobile.