Contrôleur de conteneur personnalisé: transitionFromViewController: Affichage mal agencé avant l'animation

C'est à la fois une question et une solution partielle .


* Exemple de projet ici:

https://github.com/JosephLin/TransitionTest


Problème 1:

Lors de l'utilisation de transitionFromViewController:... , les mises en page effectuées par toViewController de viewWillAppear: n'apparaissent pas lorsque l'animation de transition commence. En d'autres termes, la vue pré-layout s'affiche pendant l'animation et son contenu s'aligne sur les positions post-layout après l'animation.

Problème 2:

Si je personnalise l'arrière-plan de l'UIBarButtonItem de ma barre de UIBarButtonItem , le button de la barre s'affiche avec la mauvaise taille / position avant l'animation et s'aligne sur la bonne taille / position à la fin de l'animation, similaire au problème 1.


Pour illustrer le problème, j'ai créé un controller de conteneur personnalisé minimal qui effectue certaines transitions de vue personnalisées. C'est à peu près une copy de UINavigationController qui se dissout au lieu de pousser l'animation entre les vues.

La méthode 'Push' ressemble à ceci:

 - (void)pushController:(UIViewController *)toViewController { UIViewController *fromViewController = [self.childViewControllers lastObject]; [self addChildViewController:toViewController]; toViewController.view.frame = self.view.bounds; NSLog(@"Before transitionFromViewController:"); [self transitionFromViewController:fromViewController toViewController:toViewController duration:0.5 options:UIViewAnimationOptionTransitionCrossDissolve animations:^{} completion:^(BOOL finished) { [toViewController didMoveToParentViewController:self]; }]; } 

Maintenant, DetailViewController (le controller de vue vers DetailViewController je pousse) doit mettre en page son contenu dans viewWillAppear: Il ne peut pas le faire dans viewDidLoad car il n'aurait pas l'image correcte à ce moment-là.

À des fins de démonstration, DetailViewController définit son libellé sur différents locations et colors dans viewDidLoad , viewWillAppear et viewDidAppear :

 - (void)viewDidLoad { [super viewDidLoad]; NSLog(@"%s", __PRETTY_FUNCTION__); CGRect rect = self.descriptionLabel.frame; rect.origin.y = 50; self.descriptionLabel.frame = rect; self.descriptionLabel.text = @"viewDidLoad"; self.descriptionLabel.backgroundColor = [UIColor redColor]; } - (void)viewWillAppear:(BOOL)animated { [super viewWillAppear:animated]; NSLog(@"%s", __PRETTY_FUNCTION__); CGRect rect = self.descriptionLabel.frame; rect.origin.y = 200; self.descriptionLabel.frame = rect; self.descriptionLabel.text = @"viewWillAppear"; self.descriptionLabel.backgroundColor = [UIColor yellowColor]; } - (void)viewDidAppear:(BOOL)animated { [super viewDidAppear:animated]; NSLog(@"%s", __PRETTY_FUNCTION__); CGRect rect = self.descriptionLabel.frame; rect.origin.y = 350; self.descriptionLabel.frame = rect; self.descriptionLabel.text = @"viewDidAppear"; self.descriptionLabel.backgroundColor = [UIColor greenColor]; } 

Maintenant, en poussant le DetailViewController , je m'attends à voir l'label à y =200 au début de l'animation (image de gauche), puis saute à y = 350 après que l'animation soit terminée (image de droite).

entrez la description de l'image icientrez la description de l'image ici Vue prévue avant et après l'animation.


Cependant, l'label était à y=50 , comme si la layout faite dans viewWillAppear ne l'avait pas faite avant l'animation (image de gauche). Mais notez que l'arrière-plan de l'label a été défini en jaune (la couleur spécifiée par viewWillAppear )!

entrez la description de l'image icientrez la description de l'image ici Mauvaise layout au début de l'animation. Notez que les buttons de la barre commencent également avec la mauvaise position / taille.


Journal de la console
TransitionTest [49795: c07] – [DetailViewController viewDidLoad]
TransitionTest [49795: c07] Avant transitionFromViewController:
TransitionTest [49795: c07] – [DetailViewController viewWillAppear:]
TransitionTest [49795: c07] – [DetailViewController viewWillLayoutSubviews]
TransitionTest [49795: c07] – [DetailViewController viewDidLayoutSubviews]
TransitionTest [49795: c07] – [DetailViewController viewDidAppear:]
Notez que viewWillAppear: été appelé viewWillAppear: transitionFromViewController:


Solution pour le problème 1

D'accord, voici la partie solution partielle. En appelant explicitement beginAppearanceTransition: et endAppearanceTransition toViewController , la vue aura la disposition correcte avant que l'animation de transition ait lieu:

 - (void)pushController:(UIViewController *)toViewController { UIViewController *fromViewController = [self.childViewControllers lastObject]; [self addChildViewController:toViewController]; toViewController.view.frame = self.view.bounds; [toViewController beginAppearanceTransition:YES animated:NO]; NSLog(@"Before transitionFromViewController:"); [self transitionFromViewController:fromViewController toViewController:toViewController duration:0.5 options:UIViewAnimationOptionTransitionCrossDissolve animations:^{} completion:^(BOOL finished) { [toViewController didMoveToParentViewController:self]; [toViewController endAppearanceTransition]; }]; } 

Notez que viewWillAppear: est maintenant appelé BEFORE transitionFromViewController: TransitionTest [18398: c07] – [DetailViewController viewDidLoad]
TransitionTest [18398: c07] – [DetailViewController viewWillAppear:]
TransitionTest [18398: c07] Avant transitionFromViewController:
TransitionTest [18398: c07] – [DetailViewController viewWillLayoutSubviews]
TransitionTest [18398: c07] – [DetailViewController viewDidLayoutSubviews]
TransitionTest [18398: c07] – [DetailViewController viewDidAppear:]


Mais cela ne résout pas le problème 2!

Pour une raison quelconque, les buttons de la barre de navigation commencent toujours avec la mauvaise position / taille au début de l'animation de transition. J'ai passé tellement de time à essayer de find la bonne solution, mais sans chance. Je commence à sentir que c'est un bug dans transitionFromViewController: ou UIAppearance ou autre. S'il vous plaît, toute idée que vous pouvez offrir à cette question est grandement appréciée. Merci!


D'autres solutions que j'ai essayées

  • Appelez [self.view addSubview:toViewController.view]; avant transitionFromViewController:

Il donne réellement exactement le bon résultat à l'user, corrige à la fois le problème 1 et 2. Le problème est que viewWillAppear et viewDidAppear seront tous deux appelés deux fois! C'est problématique si je veux faire une animation expansive ou un calcul dans viewDidAppear .

  • Appelez [toViewController viewWillAppear:YES]; avant transitionFromViewController:

Je pense que c'est à peu près la même chose que d'appeler beginAppearanceTransition: Il corrige le problème 1 mais pas le problème 2. De plus, le doc dit de ne pas appeler viewWillAppear directement!

  • Utilisez [UIView animateWithDuration:] au lieu de transitionFromViewController:

Comme ceci: [self addChildViewController: toViewController]; [self.view addSubview: toViewController.view]; toViewController.view.alpha = 0.0;

 [UIView animateWithDuration:0.5 animations:^{ toViewController.view.alpha = 1.0; } completion:^(BOOL finished) { [toViewController didMoveToParentViewController:self]; }]; 

Il corrige le problème 2, mais la vue a commencé avec la disposition dans viewDidAppear (label est vert, y = 350). En outre, la UIViewAnimationOptionTransitionCrossDissolve croisée n'est pas aussi efficace que l'utilisation de UIViewAnimationOptionTransitionCrossDissolve

Ok, append layoutIfNeeded à toViewController.view semble faire l'affaire – cela obtient la vue correctement présentée avant qu'elle n'apparaisse à l'écran (sans l'ajout / suppression), et pas plus bizarre double viewDidAppear: appel.

 - (void)pushController:(UIViewController *)toViewController { UIViewController *fromViewController = [self.childViewControllers lastObject]; [self addChildViewController:toViewController]; toViewController.view.frame = self.view.bounds; [toViewController.view layoutIfNeeded]; NSLog(@"Before transitionFromViewController:"); [self transitionFromViewController:fromViewController toViewController:toViewController duration:0.5 options:UIViewAnimationOptionTransitionCrossDissolve animations:^{} completion:^(BOOL finished) { }]; } 

Avait le même problème, tout ce dont vous avez besoin est de transmettre des transactions d'apparence et UIView.Animate. Cette approche corrige tous les problèmes, n'en crée pas de nouveaux. Voici un code C # (xamarin):

 var fromView = fromViewController.View; var toView = toViewController.View; fromViewController.WillMoveToParentViewController(null); AddChildViewController(toViewController); fromViewController.BeginAppearanceTransition(false, true); toViewController.BeginAppearanceTransition(true, true); var frame = fromView.Frame; frame.X = -viewWidth * direction; toView.Frame = frame; View.Add(toView); UIView.Animate(0.3f, animation: () => { toView.Frame = fromView.Frame; fromView.MoveTo(x: viewWidth * direction); }, completion: () => { fromView.RemoveFromSuperview(); fromViewController.EndAppearanceTransition(); toViewController.EndAppearanceTransition(); fromViewController.RemoveFromParentViewController(); toViewController.DidMoveToParentViewController(this); } ); 

et bien sûr, vous devez désactiver le transfert automatique des methods d'apparence et le faire manuellement:

 public override bool ShouldAutomaticallyForwardAppearanceMethods { get { return false; } } public override void ViewWillAppear(bool animated) { base.ViewWillAppear(animated); CurrentViewController.BeginAppearanceTransition(true, animated); } public override void ViewDidAppear(bool animated) { base.ViewDidAppear(animated); CurrentViewController.EndAppearanceTransition(); } public override void ViewWillDisappear(bool animated) { base.ViewWillDisappear(animated); CurrentViewController.BeginAppearanceTransition(false, animated); } public override void ViewDidDisappear(bool animated) { base.ViewDidDisappear(animated); CurrentViewController.EndAppearanceTransition(); }