Pop up! Transcrito no iOS

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() // 1 @IBAction func openDidPress(_ sender: Any) { let child = ChildViewController() child.transitioningDelegate = transition // 2 child.modalPresentationStyle = .custom // 3 present(child, animated: true) } } 

  1. Crie um objeto que descreva a transição. transitioningDelegate marcado como weak , portanto, você deve armazenar a transition separadamente pelo link strong .
  2. Definimos nossa transição para transitioningDelegate .
  3. 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:


 // PresentationController.swift override func presentationTransitionWillBegin() { super.presentationTransitionWillBegin() containerView?.addSubview(presentedView!) } 

Ainda precisa colocar um quadro para o presentedView . containerViewDidLayoutSubviews é o melhor lugar, pois dessa forma podemos responder à rotação da tela:


 // PresentationController.swift override func containerViewDidLayoutSubviews() { super.containerViewDidLayoutSubviews() presentedView?.frame = frameOfPresentedViewInContainerView } 

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 { // transitionContext.view    ,   let to = transitionContext.view(forKey: .to)! let finalFrame = transitionContext.finalFrame(for: transitionContext.viewController(forKey: .to)!) //   ,     PresentationController //      to.frame = finalFrame.offsetBy(dx: 0, dy: finalFrame.height) let animator = UIViewPropertyAnimator(duration: duration, curve: .easeOut) { to.frame = finalFrame //   ,     } animator.addCompletion { (position) in //  ,      transitionContext.completeTransition(!transitionContext.transitionWasCancelled) } return animator } } 

O UIViewPropertyAnimator não funciona no iOS 9

A 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.

Source: https://habr.com/ru/post/pt463527/


All Articles