UIViewController: problema con la transición de rechazo personalizada.

Resumen

Tengo un contenido UIViewController que presenta una configuration UIViewController utilizando una transición personalizada. La presentación es con presentViewController:animated:completion:

Cuando más tarde dismissViewControllerAnimated:completion: las configuraciones con dismissViewControllerAnimated:completion: el controller que presenta se dismissViewControllerAnimated:completion: repente a su position inicial antes de la presentación del controller de configuration.

Tengo un trabajo para esto en el dispositivo, pero no en el simulador. Sin embargo, me gustaría saber qué estoy haciendo mal en lugar de piratear en un cuerpo que lo hace desaparecer. También planeo hacer que esta animation sea interactiva, y sospecho que estos problemas se amplificarán cuando hago esto.

Transición personalizada: abrir la campana

El efecto deseado es que el controller que se presenta se deslice hacia abajo de la pantalla, y se ve que el controller presentado se encuentra detrás de él desde donde se levanta para llenar la pantalla. La parte superior del controller de presentación permanece en pantalla durante la vida útil del controller presentado. Permanece en la parte inferior de la pantalla, pero encima del controller presentado.

Se podría imaginar levantar el capó en un automobile (el controller que presenta el frente) para ver el motor detrás (la configuration presentada), pero el capó se mantiene visible en la parte inferior para un poco de context.

Planeo refinar esto para que el controller de presentación realmente parezca levantar con perspectiva en una forma 3D, pero aún no he llegado tan lejos.

Cuando se descarta la configuration, el controller presentador original (bonnet) debe deslizarse hacia atrás en la pantalla y el controller presentado (ajustes) se hunde ligeramente (cerrando el bonete).

Código

Este es el método que activa y desactiva la configuration (solo se llama mediante un button UIB). Notará que el controller de vista de presentación se configura como <UIViewControllerTransitioningDelegate> .

 -(void) toggleSettingsViewController { const BOOL settingsAreShowing = [self presentedViewController] != nil; if(!settingsAreShowing) { UIViewController *const settingsController = [[self storyboard] instantiateViewControllerWithIdentifier: @"STSettingsViewController"]; [settingsController setTransitioningDelegate: self]; [settingsController setModalPresentationStyle: UIModalPresentationCustom]; [self presentViewController: settingsController animated: YES completion: nil]; } else { [self dismissViewControllerAnimated: YES completion: nil]; } } 

Para implementar <UIViewControllerAnimatedTransitioning> el controller de visualización de presentación también se vuelve a sí mismo como <UIViewControllerAnimatedTransitioning>

 -(id<UIViewControllerAnimatedTransitioning>) animationControllerForPresentedController:(UIViewController *)presented presentingController:(UIViewController *)presenting sourceController:(UIViewController *)source { return self; } -(id<UIViewControllerAnimatedTransitioning>) animationControllerForDismissedController:(UIViewController *)dismissed { // Test Point 1. return self; } 

Entonces, finalmente, el controller de vista de presentación recibirá animateTransition: ::

 -(void) animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext { UIViewController *const fromController = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey]; UIViewController *const toController = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey]; const BOOL isUnwinding = [toController presentedViewController] == fromController; const BOOL isPresenting = !isUnwinding; UIViewController * presentingController = isPresenting ? fromController : toController; UIViewController * presentedController = isPresenting ? toController : fromController; if(isPresenting) { // Add the presented controller (settings) to the view hierarchy _behind_ the presenting controller. [[transitionContext containerView] insertSubview: [presentedController view] belowSubview: [presentingController view]]; // Set up the initial position of the presented settings controller. Scale it down so it seems in the distance. Alpha it down so it is dark and shadowed. presentedController.view.transform = CGAffineTransformMakeScale(0.9, 0.9); presentedController.view.alpha = 0.7; [UIView animateWithDuration: [self transitionDuration: transitionContext] animations:^{ // Lift up the presented controller. presentedController.view.transform = CGAffineTransformMakeScale(1.0, 1.0); // Brighten the presented controller (out of shadow). presentedController.view.alpha = 1; // Push the presenting controller down the screen – 3d effect to be added later. presentingController.view.layer.transform = CATransform3DMakeTranslation(0,400,0); } completion: ^(BOOL finished){ [transitionContext completeTransition: ![transitionContext transitionWasCancelled]]; }]; } else { // Test Point 2. // !!!This line should not be needed!!! // It resets the presenting controller to where it ought to be anyway. presentingController.view.layer.transform = CATransform3DMakeTranslation(0,400,0); [UIView animateWithDuration: [self transitionDuration: transitionContext] animations:^{ // Bring the presenting controller back to its original position. presentingController.view.layer.transform = CATransform3DIdentity; // Lower the presented controller again and put it back in to shade. presentedController.view.transform = CGAffineTransformMakeScale(0.9, 0.9); presentedController.view.alpha = 0.4; } completion:^(BOOL finished) { [transitionContext completeTransition: ![transitionContext transitionWasCancelled]]; }]; } } -(NSTimeInterval) transitionDuration:(id<UIViewControllerContextTransitioning>)transitionContext { return 0.5; } 

Problema

En el código anterior, he indicado !!! ¡Esta línea no debería ser necesaria! .

Lo que está sucediendo es que entre el punto de testing 1 y el punto de testing 2 se restablece la position de la pantalla del controller de vista de presentación para ser los límites de pantalla completa pnetworkingeterminados. Por lo tanto, en lugar de estar en la parte inferior de la pantalla listo para animar una copy de security de nuevo sin problemas, de repente salta la pantalla hacia la position que está destinada a animar suavemente también!

He intentado varios enfoques para animar el controller de vista de presentación en la pantalla:

  • He cambiado el marco de su vista.
  • He cambiado la transformación de su vista.
  • He cambiado la transformación 3D de su capa de vista.

En todos los casos, en el punto de testing 1 , cuando se solicita el delegado de transición, el controller de presentación se configura como era de esperar. Sin embargo, en todos los casos, en el punto de testing 2 , el controller de vista de presentación ha perdido la position correcta y se ha "borrado" para tener la position de pantalla completa normal a la que quiero animarlo.

En el trabajo alnetworkingedor de arriba reubique explícitamente el controller de vista de presentación de nuevo a donde debería estar al comienzo de la animation con ¡Esta línea no debería ser necesaria! . Esto parece funcionar en el dispositivo con la versión actual de iOS 7. Sin embargo, en el simulador, el controller es visible en la position despejada para al less un cuadro.

Sospecho que estoy haciendo algo mal, y que voy a meterme en problemas con mi solución simplemente enmascarando otro problema.

¿Alguna idea de lo que está pasando? ¡Gracias!

Algunas posibles complicaciones con el rechazo de los controlleres de vista presentados de manera modal usando animaciones de transición personalizadas:

  • Agregue la vista presentada ("a") al contenedor, luego traiga el frente de vista presentado. No agregue la vista de presentación, ya que podría eliminarla de su vista de supervisión actual.
  • Al descartar, UIKit establece el alfa de la vista presentada en 0 antes de que se invoque animateTransition . Entonces querrás configurarlo a 1.0 o lo que fuera al completar el presente antes de disparar tus animaciones de rechazo.
  • Del mismo modo para la transformación de la vista presentada. Al descartar, se restablece a la identidad antes de que se llame a animateTransition.

Dado todo eso, creo que esto debería funcionar:

 -(void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext { UIViewController *fromController = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey]; UIViewController *toController = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey]; UIView *containerView = transitionContext.containerView; const BOOL isUnwinding = [toController presentedViewController] == fromController; const BOOL isPresenting = !isUnwinding; UIViewController *presentingController = isPresenting ? fromController : toController; UIViewController *presentedController = isPresenting ? toController : fromController; [containerView addSubview:presentingController.view]; [containerView bringSubviewToFront:presentingController.view]; if(isPresenting) { // Set up the initial position of the presented settings controller. Scale it down so it seems in the distance. Alpha it down so it is dark and shadowed. presentedController.view.transform = CGAffineTransformMakeScale(0.9, 0.9); presentedController.view.alpha = 0.7; [UIView animateWithDuration: [self transitionDuration: transitionContext] animations:^{ // Lift up the presented controller. presentedController.view.transform = CGAffineTransformMakeScale(1.0, 1.0); // Brighten the presented controller (out of shadow). presentedController.view.alpha = 1; // Push the presenting controller down the screen – 3d effect to be added later. presentingController.view.layer.transform = CATransform3DMakeTranslation(0,400,0); } completion: ^(BOOL finished){ [transitionContext completeTransition: ![transitionContext transitionWasCancelled]]; }]; } else { presentedController.view.transform = CGAffineTransformMakeScale(0.9, 0.9); presentedController.view.alpha = 0.7; [UIView animateWithDuration: [self transitionDuration: transitionContext] animations:^{ // Bring the presenting controller back to its original position. presentingController.view.layer.transform = CATransform3DIdentity; // Lower the presented controller again and put it back in to shade. presentedController.view.transform = CGAffineTransformMakeScale(0.9, 0.9); presentedController.view.alpha = 0.4; } completion:^(BOOL finished) { [transitionContext completeTransition: ![transitionContext transitionWasCancelled]]; }]; } } 

Inicialmente, pensé en usar CATransition para tener un efecto de transición personalizado cuando presentViewController:animated:completion: y dismissViewControllerAnimated:completion: un View Controller. Pero desea mostrar una parte de View Controller cuando se presenta la configuration View Controller, entonces creo que CATransition no ayudaría porque no tiene control total de cuánto desea mover el View Controller.

Creo que la forma más fácil es tener un solo controller de vista con dos UIView de pantalla completa. Para la primera vista de UIView (vista de View Controller, es decir, self.view), distribuya la configuration, y en la segunda UIView, es la vista normal. En ViewDidLoad, agrega la segunda vista utilizando [self.view addSubview:2ndView]; . Más tarde, cuando desee presentar la vista de configuration, puede hacer

 CGRect frame = secondView.frame; frame.origin.y = the_y_coordinate_you_like; UIView animateWithDuration:0.2 animations:^{ secondView.frame = frame; }]; 

luego, haga lo contrario para recuperar 2ndView.