animation de transición interactiva personalizada

Quiero implementar una transición interactiva entre dos controlleres de vista. Me gustaría que fuera una transición modal o presente.

  • Quiero que la aplicación comience en el primer controller de vista y permita que el usuario deslice hacia abajo para traer el segundo controller de vista
  • El segundo controller de vista debe entrar y cubrir el actual (primer controller de vista) en lugar de moverlo fuera del path

Entiendo que necesitaría usar lo siguiente.

transiciónDelegate

animationController (forPresented: presentando: Source 🙂

interactionControllerForPresentation (Utilizando 🙂

UIPercentDrivenInteractiveTransition

Tengo problemas para descubrir cómo implementar todo esto. No puedo encontrar nada útil o ningún ejemplo de trabajo en swift 3. Por ahora, he creado una aplicación simple de vista única con dos controlleres de vista VC1 (background azul) y VC2 (background amarillo) para probar fácilmente posibles soluciones.

Vea Transiciones personalizadas de video WWDC 2013 que utilizan controlleres de vista para la discusión del delegado de transición, el controller de animation y el controller de interacción. Vea los videos de WWDC 2014 Vea los Avances del Controlador en iOS 8 y los Controladores de Presentación A Look Inside para la introducción a los controlleres de presentación (que también debería usar).

La idea básica es crear un object delegado de transición que identifique qué controller de animation, controller de interacción y controller de presentación se usará para la transición personalizada:

class TransitioningDelegate: NSObject, UIViewControllerTransitioningDelegate { /// Interaction controller /// /// If gesture triggers transition, it will set will manage its own /// `UIPercentDrivenInteractiveTransition`, but it must set this /// reference to that interaction controller here, so that this /// knows whether it's interactive or not. weak var interactionController: UIPercentDrivenInteractiveTransition? func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? { return PullDownAnimationController(transitionType: .presenting) } func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? { return PullDownAnimationController(transitionType: .dismissing) } func presentationController(forPresented presented: UIViewController, presenting: UIViewController?, source: UIViewController) -> UIPresentationController? { return PresentationController(presentedViewController: presented, presenting: presenting) } func interactionControllerForPresentation(using animator: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning? { return interactionController } func interactionControllerForDismissal(using animator: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning? { return interactionController } } 

A continuación, debe especificar que se está utilizando una transición personalizada y qué delegado de transición se debe utilizar. Puede hacer eso cuando crea una instancia del controller de vista de destino, o puede especificarlo como parte de la init para el controller de vista de destino, como por ejemplo:

 class SecondViewController: UIViewController { let customTransitionDelegate = TransitioningDelegate() requinetworking init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) modalPresentationStyle = .custom transitioningDelegate = customTransitionDelegate } ... } 

El controller de animation especifica los detalles de la animation (cómo animar, la duración que se utilizará para las transiciones no interactivas, etc.):

 class PullDownAnimationController: NSObject, UIViewControllerAnimatedTransitioning { enum TransitionType { case presenting case dismissing } let transitionType: TransitionType init(transitionType: TransitionType) { self.transitionType = transitionType super.init() } func animateTransition(using transitionContext: UIViewControllerContextTransitioning) { let inView = transitionContext.containerView let toView = transitionContext.view(forKey: .to)! let fromView = transitionContext.view(forKey: .from)! var frame = inView.bounds switch transitionType { case .presenting: frame.origin.y = -frame.size.height toView.frame = frame inView.addSubview(toView) UIView.animate(withDuration: transitionDuration(using: transitionContext), animations: { toView.frame = inView.bounds }, completion: { finished in transitionContext.completeTransition(!transitionContext.transitionWasCancelled) }) case .dismissing: toView.frame = frame inView.insertSubview(toView, belowSubview: fromView) UIView.animate(withDuration: transitionDuration(using: transitionContext), animations: { frame.origin.y = -frame.size.height fromView.frame = frame }, completion: { finished in transitionContext.completeTransition(!transitionContext.transitionWasCancelled) }) } } func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval { return 0.5 } } 

El controller de animation anterior maneja tanto la presentación como la desactivación, pero si eso se siente demasiado complicado, teóricamente podría dividirlo en dos classs, una para presentar y otra para rechazar. Pero no me gusta tener dos classs diferentes tan estrechamente acopladas, así que soportaré el costo de la ligera complejidad de animateTransition para asegurarme de que esté todo bien encapsulado en una class.

De todos modos, el próximo object que deseamos es el controller de presentación. En este caso, el controller de presentación nos dice que eliminemos la vista del controller de vista de origen de la jerarquía de vista. (Hacemos esto, en este caso, porque la escena a la que está pasando pasa a ocupar toda la pantalla, por lo que no es necesario mantener la vista anterior en la jerarquía de la vista). Si estaba agregando algún otro cromado adicional (por ejemplo, agregando oscurecimiento / desdibujado, etc.) que pertenecerían al controller de presentación.

De todos modos, en este caso, el controller de presentación es bastante simple:

 class PresentationController: UIPresentationController { override var shouldRemovePresentersView: Bool { return true } } 

Finalmente, presumiblemente quiere un gestor reconocedor que:

  • instancia la UIPercentDrivenInteractiveTransition ;
  • inicia la transición misma;
  • actualiza UIPercentDrivenInteractiveTransition medida que UIPercentDrivenInteractiveTransition el gesto;
  • cancela o finaliza la transición interactiva cuando se realiza el gesto; y
  • elimina la UIPercentDrivenInteractiveTransition cuando se hace (para asegurarse de que no se detenga para que no interfiera con ninguna transición no interactiva que desees hacer más tarde … este es un punto sutil y fácil de pasar por alto).

Entonces, el controller de vista "presentador" podría tener un reconocimiento de gestos que podría hacer algo como:

 class ViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() let panDown = UIPanGestureRecognizer(target: self, action: #selector(handleGesture(_:))) view.addGestureRecognizer(panDown) } var interactionController: UIPercentDrivenInteractiveTransition? // pan down transitions to next view controller func handleGesture(_ gesture: UIPanGestureRecognizer) { let translate = gesture.translation(in: gesture.view) let percent = translate.y / gesture.view!.bounds.size.height if gesture.state == .began { let controller = storyboard!.instantiateViewController(withIdentifier: "SecondViewController") as! SecondViewController interactionController = UIPercentDrivenInteractiveTransition() controller.customTransitionDelegate.interactionController = interactionController show(controller, sender: self) } else if gesture.state == .changed { interactionController?.update(percent) } else if gesture.state == .ended || gesture.state == .cancelled { let velocity = gesture.velocity(in: gesture.view) if (percent > 0.5 && velocity.y == 0) || velocity.y > 0 { interactionController?.finish() } else { interactionController?.cancel() } interactionController = nil } } } 

Probablemente también quieras cambiar esto para que solo reconozca los gestos descendentes (en lugar de los anteriores), pero con suerte esto ilustra la idea.

Y es probable que desee que el controller de vista "presentado" tenga un reconocimiento de gestos para descartar la escena:

 class SecondViewController: UIViewController { let customTransitionDelegate = TransitioningDelegate() requinetworking init?(coder aDecoder: NSCoder) { // as shown above } override func viewDidLoad() { super.viewDidLoad() let panUp = UIPanGestureRecognizer(target: self, action: #selector(handleGesture(_:))) view.addGestureRecognizer(panUp) } // pan up transitions back to the presenting view controller var interactionController: UIPercentDrivenInteractiveTransition? func handleGesture(_ gesture: UIPanGestureRecognizer) { let translate = gesture.translation(in: gesture.view) let percent = -translate.y / gesture.view!.bounds.size.height if gesture.state == .began { interactionController = UIPercentDrivenInteractiveTransition() customTransitionDelegate.interactionController = interactionController dismiss(animated: true) } else if gesture.state == .changed { interactionController?.update(percent) } else if gesture.state == .ended { let velocity = gesture.velocity(in: gesture.view) if (percent > 0.5 && velocity.y == 0) || velocity.y < 0 { interactionController?.finish() } else { interactionController?.cancel() } interactionController = nil } } @IBAction func didTapButton(_ sender: UIButton) { dismiss(animated: true) } } 

Consulte https://github.com/robertmryan/SwiftCustomTransitions para ver una demostración del código anterior.

Parece que:

demostración interactiva de gestos

Pero, en resumidas counts, las transiciones personalizadas son un poco complicadas, así que vuelvo a referirte a esos videos originales. Asegúrese de verlos con cierto detalle antes de publicar más preguntas. La mayoría de sus preguntas probablemente serán respondidas en esos videos.