UIViewController – problème avec la transition de rejet personnalisé

Résumé

J'ai un contenu UIViewController qui présente un parameters UIViewController en utilisant une transition personnalisée. La présentation est avec presentViewController:animated:completion:

Lorsque plus tard, je rejette les parameters avec dismissViewControllerAnimated:completion: le controller de présentation est soudainement sauté à sa position initiale avant la présentation du controller de parameters.

J'ai un travail pour cela sur l'appareil mais pas sur le simulateur. Cependant, j'aimerais savoir ce que je fais de mal plutôt que de le pirater dans un bodge qui le fait disparaître. Je prévois également de rendre cette animation interactive, et je pense que ces problèmes vont s'amplifier quand je le ferai.

Transition personnalisée – Ouverture du capot

L'effet désiré est que le controller de présentation glisse vers le bas de l'écran, et le controller présenté est vu derrière lui d'où il se lève pour remplir l'écran. Le haut du controller de présentation rest à l'écran pendant la durée de vie du controller présenté. Il rest en bas de l'écran, mais audessus du controller présenté.

Vous pourriez imaginer lever le capot sur une voiture (le controller de présentation avant) pour voir le moteur derrière (les parameters présentés), mais le capot rest visible en bas pour un peu de context.

J'ai l'intention d'affiner cela afin que le controller présentateur semble vraiment relever avec la perspective d'une manière 3D, mais je n'ai pas encore été aussi loin.

Lorsque les réglages sont supprimés, le controller de présentation d'origine (bonnet) doit glisser vers le haut de l'écran et le controller présenté (parameters) s'enfonce légèrement (fermeture du capot).

Code

Voici la méthode qui permet de basculer les parameters sur et hors de l'écran (il est juste appelé par un UIButton). Vous remarquerez que le controller de vue présentateur se définit comme <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]; } } 

Pour implémenter <UIViewControllerAnimatedTransitioning> le controller de vue de présentation returnne lui aussi simplement <UIViewControllerAnimatedTransitioning>

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

Donc finalement, le controller de vue présentant recevra 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; } 

Problème

Dans le code ci-dessus, j'ai indiqué !!! Cette ligne ne devrait pas être nécessaire !!! .

Qu'est-ce qui se passe est que, entre le sharepoint test 1 et le sharepoint test 2, la position de l'écran du controller de vue de présentation est réinitialisé pour être les limites de plein écran par défaut. Ainsi, au lieu d'être au bas de l'écran prêt à animer de nouveau en douceur, il saute tout de suite sur l'écran pour positionner qu'il est également destiné à animer en douceur!

J'ai essayé différentes approches pour animer le controller de vue en présentation à l'écran:

  • J'ai changé le cadre de sa vue.
  • J'ai changé la transformation de sa vue.
  • J'ai changé la transformation 3d de la couche de sa vue.

Dans tous les cas, au sharepoint test 1 , lorsque le délégué de transition est demandé, le controller présentateur est configuré comme je l'aurais souhaité. Cependant, dans tous les cas, au sharepoint test 2 , le controller de vue présentant la présentation a perdu la position correcte et a été "effacé" pour avoir la position normale en plein écran à laquelle je souhaite l'animer.

Dans le travail ci-dessus, je replace explicitement le controller de vue de présentation là où il devrait être au début de l'animation avec !!! Cette ligne ne devrait pas être nécessaire !!! . Cela semble fonctionner sur l'appareil avec la version actuelle d'iOS 7. Cependant, sur le simulateur, le controller est visible à la position effacée pour au less une image.

Je soupçonne que je fais quelque chose d'autre qui ne va pas, et que je vais avoir des ennuis avec ma solution de contournement en masquant un autre problème.

Avez-vous une idée de ce qui passe? Merci!

Quelques pièges potentiels avec le rejet des controllers de vue modalement présentés utilisant des animations de transition personnalisées:

  • Ajoutez la vue présentée ("to") au conteneur, puis amenez la vue présentée devant. N'ajoutez pas la vue de présentation car vous pourriez la supprimer de son aperçu actuel.
  • En cas de rejet, UIKit définit l'alpha de la vue présentée sur 0 avant l'appel de animateTransition . Donc, vous aurez envie de le mettre à 1.0 ou quoi que ce soit à la fin du présent avant de tirer votre (s) animation (s).
  • De même pour la transformation de la vue présentée. En cas de rejet, il est réinitialisé à l'identité avant l'appel de animateTransition.

Compte tenu de tout cela, je pense que cela devrait fonctionner:

 -(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]]; }]; } } 

Initialement, j'ai pensé à utiliser CATransition pour avoir un effet de transition personnalisé quand presentViewController:animated:completion: et dismissViewControllerAnimated:completion: un View Controller. Mais vous voulez montrer une partie de View Controller quand le réglage de View Controller est présenté, alors je pense que CATransition pas parce que vous n'avez pas le contrôle total de la distance à laquelle vous voulez déplacer le View Controller.

Je pense que le moyen le plus simple est d'avoir un seul View Controller avec deux UIView en plein écran. Pour la première vue UIView (vue du controller de vue, c'est-à-dire self.view), vous configurez le paramètre, et sur le second UIView, c'est la vue normale. Dans ViewDidLoad, vous ajoutez la 2ème vue en utilisant [self.view addSubview:2ndView]; . Plus tard, quand vous voulez présenter la vue de réglage, vous pouvez faire

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

Puis faites l'inverse pour ramener le 2ndView.