C'est à la fois une question et une solution partielle .
* Exemple de projet ici:
https://github.com/JosephLin/TransitionTest
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.
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).
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
)!
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:
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:]
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
[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
.
[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!
[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(); }