web-dev-qa-db-fra.com

UIViewController - Problème avec la transition de renvoi personnalisée

Résumé

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

Lorsque, par la suite, je rejette les paramètres avec dismissViewControllerAnimated:completion:, le contrôleur de présentation revient soudainement à sa position initiale avant la présentation du contrôleur de paramètres.

J'ai un moyen de contourner ce problème sur l'appareil mais pas sur le simulateur. Cependant, j'aimerais savoir ce que je fais mal plutôt que de pirater une bodge qui le fait disparaître. Je prévois également de rendre cette animation interactive, et je suppose que ces problèmes vont s’amplifier lorsque je le ferai.

Transition personnalisée - Ouverture du capot

L’effet souhaité est que le contrôleur présentant glisse vers le bas de l’écran et que le contrôleur présenté semble se trouver derrière lui, de là où il se soulève pour remplir l’écran. Le haut du contrôleur de présentation reste à l'écran pendant toute la durée d'utilisation du contrôleur présenté. Il reste en bas de l'écran, mais au-dessus du contrôleur présenté.

Vous pouvez imaginer soulever le capot sur une voiture (le contrôleur de présentation avant) pour voir le moteur derrière (les réglages présentés), mais le capot reste visible en bas pour un peu de contexte.

Je prévois d’affiner la chose pour que le contrôleur de présentation apparaisse réellement avec une perspective 3D, mais je n’en suis pas encore là.

Lorsque les paramètres sont ignorés, le contrôleur de présentation d'origine (capot) doit remonter à l'écran et le contrôleur présenté (paramètres) s'affaisse légèrement (fermeture du capot).

Code

Voici la méthode qui bascule les paramètres sur et en dehors de l'écran (elle est simplement appelée par un bouton UIButton). Vous remarquerez que le contrôleur de la vue de présentation se configure en tant que <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 contrôleur de vue de présentation se retourne lui-même simplement sous le <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 contrôleur de vue de présentation 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 !!! .

Ce qui se passe est qu'entre Point de test 1 et Point de test 2 , la position de l'écran du contrôleur de la vue de présentation est réinitialisée pour devenir les limites du plein écran par défaut. Ainsi, au lieu d’être au bas de l’écran, prêt à reprendre l’animation en douceur, il saute soudainement vers le haut de l’écran pour indiquer qu’il doit être animé aussi!

J'ai essayé diverses méthodes pour animer le contrôleur de vue de présentation sur l'écran:

  • J'ai changé le cadre de sa vue.
  • J'ai changé la transformation de sa vue.
  • J'ai changé la transformation 3D du calque de sa vue.

Dans tous les cas, dans Point de test 1 , lorsque le délégué de transition est demandé, le contrôleur de présentation est configuré comme je le pensais. Cependant, dans tous les cas, dans Point de test 2 , le contrôleur de la vue présentation a perdu la position correcte et a été "effacé" pour afficher la position normale plein écran à laquelle je souhaite l'animer.

Dans le travail ci-dessus, je déplace explicitement le contrôleur de la 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. Toutefois, sur le simulateur, le contrôleur est visible à la position libérée pendant au moins une image.

Je soupçonne que je fais quelque chose de mal, et que je vais avoir des problèmes avec ma solution de contournement en masquant un autre problème.

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

25
Benjohn

Quelques pièges potentiels avec le renvoi des contrôleurs de vue à présentation modale à l'aide d'animations de transition personnalisées:

  • Ajoutez la vue présentée ("à") au conteneur, puis amenez la vue présentée en face. N'ajoutez pas la vue de présentation, car vous pourriez la supprimer de sa vue d'ensemble actuelle.
  • En cas de rejet, UIKit définit l'alpha de la vue présentée sur 0 avant que animateTransition ne s'appelle . Donc, vous voudrez le mettre à 1.0 ou quoi que ce soit à la fin du présent avant déclencher votre animation de licenciement.
  • De même pour la transformation de la vue présentée. En cas de rejet, l'identité est réinitialisée 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]];
        }];
    }
}
37
John Scalo

Initialement, j'ai pensé à utiliser CATransition pour avoir un effet de transition personnalisé lorsque presentViewController:animated:completion: et dismissViewControllerAnimated:completion: étaient un contrôleur de vue. Mais vous souhaitez afficher une partie de View Controller lorsque le paramètre View Controller est présenté, alors je pense que CATransition n’aiderait pas, car vous n’avez pas le contrôle total sur la distance à laquelle vous souhaitez déplacer le View Controller.

Je pense que le moyen le plus simple est d’avoir un seul contrôleur de vue avec deux UIView en plein écran. Pour la première UIView (la vue du contrôleur de vue, c'est-à-dire, self.view), vous mettez en page le paramètre et sur la deuxième UIView, il s'agit de la vue normale. Dans ViewDidLoad, vous ajoutez la deuxième vue à l'aide de [self.view addSubview:2ndView];. Plus tard, lorsque vous souhaitez présenter la vue des paramètres, vous pouvez faire

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

ensuite, faites l’autre façon de ramener la 2ndView.

0
Dino Tw