Note that there are some explanatory texts on larger screens.

plurals
  1. POCustom Container Controller: transitionFromViewController: View not properly layed-out before animation
    text
    copied!<p>This is both a question and a <strong>partial</strong> solution. </p> <hr> <p>*Sample project here:</p> <p><a href="https://github.com/JosephLin/TransitionTest" rel="nofollow noreferrer">https://github.com/JosephLin/TransitionTest</a></p> <hr> <h2><strong>Problem 1:</strong></h2> <p>When using <code>transitionFromViewController:...</code>, layouts done by the <code>toViewController</code>'s <code>viewWillAppear:</code> doesn't show up when the transition animation begins. In other words, the pre-layout view shows during the animation, and it's contents snap to the post-layout positions after the animation.</p> <h2><strong>Problem 2:</strong></h2> <p>If I customize the background of my navbar's <code>UIBarButtonItem</code>, the bar button shows up with the wrong size/position before the animation, and snaps to the correct size/position when the animation ends, similar to Problem 1.</p> <hr> <p>To demonstrate the problem, I made a bare-bone custom container controller that does some custom view transitions. It's pretty much a UINavigationController copy that does cross-dissolve instead of push animation between views.</p> <p>The 'Push' method looks like this:</p> <pre><code>- (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]; }]; } </code></pre> <p>Now, <code>DetailViewController</code> (the view controller I'm pushing to) needs to layout its content in <code>viewWillAppear:</code>. It can't do it in <code>viewDidLoad</code> because it wouldn't have the correct frame at that time.</p> <p>For demonstration purpose, <code>DetailViewController</code> sets its label to different locations and colors in <code>viewDidLoad</code>, <code>viewWillAppear</code>, and <code>viewDidAppear</code>:</p> <pre><code>- (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]; } </code></pre> <hr> <p>Now, when pushing the <code>DetailViewController</code>, I'm expecting to see the label at <code>y =200</code> at the begining of the animation (left image), and then jumps to <code>y = 350</code> after the animation is finished (right image).</p> <p><img src="https://i.stack.imgur.com/cBvOo.png" alt="enter image description here"> <img src="https://i.stack.imgur.com/C8Bvd.png" alt="enter image description here"> <strong>Expected view before and after animation.</strong></p> <hr> <p>However, the label was at <code>y=50</code>, as if the layout made in <code>viewWillAppear</code> didn't make it before the animation took place (left image). But notice that the label's background was set to yellow (the color specified by <code>viewWillAppear</code>)!</p> <p><img src="https://i.stack.imgur.com/LN4vq.png" alt="enter image description here"> <img src="https://i.stack.imgur.com/C8Bvd.png" alt="enter image description here"> <strong>Wrong layout at the beginning of the animation. Notice that the bar buttons also start with the wrong position/size.</strong></p> <hr> <p><strong>Console Log</strong><br> TransitionTest[49795:c07] -[DetailViewController viewDidLoad]<br> TransitionTest[49795:c07] Before transitionFromViewController:<br> TransitionTest[49795:c07] -[DetailViewController viewWillAppear:]<br> TransitionTest[49795:c07] -[DetailViewController viewWillLayoutSubviews]<br> TransitionTest[49795:c07] -[DetailViewController viewDidLayoutSubviews]<br> TransitionTest[49795:c07] -[DetailViewController viewDidAppear:]<br> <strong>Notice that <code>viewWillAppear:</code> was called <em>AFTER</em> <code>transitionFromViewController:</code></strong> </p> <hr> <h2><strong>Solution for Problem 1</strong></h2> <p>Alright, here comes the <em>partial</em> solution part. By explicitly calling <code>beginAppearanceTransition:</code> and <code>endAppearanceTransition</code> to <code>toViewController</code>, the view will have the correct layout before the transition animation takes place:</p> <pre><code>- (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]; }]; } </code></pre> <p><strong>Notice that <code>viewWillAppear:</code> is now called BEFORE <code>transitionFromViewController:</code></strong> TransitionTest[18398:c07] -[DetailViewController viewDidLoad]<br> TransitionTest[18398:c07] -[DetailViewController viewWillAppear:]<br> TransitionTest[18398:c07] Before transitionFromViewController:<br> TransitionTest[18398:c07] -[DetailViewController viewWillLayoutSubviews]<br> TransitionTest[18398:c07] -[DetailViewController viewDidLayoutSubviews]<br> TransitionTest[18398:c07] -[DetailViewController viewDidAppear:] </p> <hr> <h2><strong>But that doesn't fix Problem 2!</strong></h2> <p>For whatever reason, the navbar buttons still begin with the wrong position/size at the beginning of the transition animation. I spent so many time trying to find THE right solution but without luck. I'm starting to feel it's a bug in <code>transitionFromViewController:</code> or <code>UIAppearance</code> or whatever. Please, any insight you can offer to this question is greatly appreciated. Thanks!</p> <hr> <p><strong>Other solutions I've tried</strong></p> <ul> <li>Call <code>[self.view addSubview:toViewController.view];</code> before <code>transitionFromViewController:</code> </li> </ul> <p>It actually gives exactly the right result to the user, fixes both Problem 1&amp;2. The problem is, <code>viewWillAppear</code> and <code>viewDidAppear</code> will both be called twice! It's problematic if I want to do some expansive animation or calculation in <code>viewDidAppear</code>.</p> <ul> <li>Call <code>[toViewController viewWillAppear:YES];</code> before <code>transitionFromViewController:</code> </li> </ul> <p>I think it's pretty much the same as calling <code>beginAppearanceTransition:</code>. It fixes Problem 1 but not Problem 2. Plus, the doc says not to call <code>viewWillAppear</code> directly!</p> <ul> <li>Use <code>[UIView animateWithDuration:]</code> instead of <code>transitionFromViewController:</code></li> </ul> <p>Like this: [self addChildViewController:toViewController]; [self.view addSubview:toViewController.view]; toViewController.view.alpha = 0.0;</p> <pre><code>[UIView animateWithDuration:0.5 animations:^{ toViewController.view.alpha = 1.0; } completion:^(BOOL finished) { [toViewController didMoveToParentViewController:self]; }]; </code></pre> <p>It fixes Problem 2, but the view started with the layout in <code>viewDidAppear</code> (label is green, y=350). Also, the cross-dissolve is not as good as using <code>UIViewAnimationOptionTransitionCrossDissolve</code></p>
 

Querying!

 
Guidance

SQuiL has stopped working due to an internal error.

If you are curious you may find further information in the browser console, which is accessible through the devtools (F12).

Reload