自定义容器控制器:transitionFromViewController:视图在animation之前未正确放置

这既是一个问题,也是一个部分的解决scheme。


*示例项目在这里:

https://github.com/JosephLin/TransitionTest


问题1:

当使用transitionFromViewController:...toViewControllerviewWillAppear:完成的布局viewWillAppear:当过渡animation开始时不显示。 换句话说,布局前的视图在animation中显示,并且其内容在animation之后捕捉到布局后的位置。

问题2:

如果我自定义导航栏的UIBarButtonItem的背景,那么在animation结束之前,button会出现错误的大小/位置,并在animation结束时捕捉到正确的大小/位置,类似于问题1。


为了演示这个问题,我做了一个自定义的容器控制器,它执行一些自定义的视图转换。 这几乎是一个UINavigationController副本,交叉溶解,而不是视图之间的推animation。

'Push'方法如下所示:

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

现在, DetailViewController (我推动的视图控制器)需要在viewWillAppear:布局其内容。 它不能在viewDidLoad因为那时候没有正确的框架。

为了演示目的, DetailViewController将其标签设置为viewDidLoadviewWillAppearviewDidAppear不同位置和颜色:

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

现在,在推动DetailViewController ,我期待在animation开始时看到y =200的标签(左图),然后在animation结束后跳转到y = 350 (右图)。

在这里输入图像说明在这里输入图像说明 animation前后的预期视图。


但是,这个标签在y=50 ,就好像在viewWillAppear做的布局在animation发生之前没有做出来一样(左图)。 但请注意,标签的背景被设置为黄色(由viewWillAppear指定的颜色)!

在这里输入图像说明在这里输入图像说明 animation开始时的错误布局。 请注意,条形button也以错误的位置/大小开始。


控制台日志
TransitionTest [49795:c07] – [DetailViewController viewDidLoad]
TransitionTest [49795:c07]在transitionFromViewController之前:
TransitionTest [49795:c07] – [DetailViewController viewWillAppear:]
TransitionTest [49795:c07] – [DetailViewController viewWillLayoutSubviews]
TransitionTest [49795:c07] – [DetailViewController viewDidLayoutSubviews]
TransitionTest [49795:c07] – [DetailViewController viewDidAppear:]
注意viewWillAppear:被称为AFTER transitionFromViewController:


问题1的解决scheme

好的,这里是部分解决scheme的一部分。 通过显式调用beginAppearanceTransition:endAppearanceTransition toViewController ,视图将在转换animation发生之前具有正确的布局:

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

注意, viewWillAppear:现在被称为BEFORE transitionFromViewController: TransitionTest [18398:c07] – [DetailViewController viewDidLoad]
TransitionTest [18398:c07] – [DetailViewController viewWillAppear:]
TransitionTest [18398:c07]在transitionFromViewController之前:
TransitionTest [18398:c07] – [DetailViewController viewWillLayoutSubviews]
TransitionTest [18398:c07] – [DetailViewController viewDidLayoutSubviews]
TransitionTest [18398:c07] – [DetailViewController viewDidAppear:]


但是这并不能解决问题2!

无论出于何种原因,在过渡animation开始时,导航栏button仍然以错误的位置/大小开始。 我花了很多时间试图find正确的解决scheme,但没有运气。 我开始觉得这是一个在transitionFromViewController:UIAppearance或任何错误。 请,任何洞察力,你可以提供这个问题,非常感谢。 谢谢!


我试过的其他解决scheme

  • 调用[self.view addSubview:toViewController.view]; transitionFromViewController:之前transitionFromViewController:

它实际上给用户正确的结果,修复了问题1和2。 问题是, viewWillAppearviewDidAppear都会被调用两次! 如果我想在viewDidAppear做一些膨胀的animation或计算,这是有问题的。

  • 调用[toViewController viewWillAppear:YES]; transitionFromViewController:之前transitionFromViewController:

我觉得跟调用beginAppearanceTransition: 。 它修复问题1,而不是问题2.另外,文件说,不要直接调用viewWillAppear

  • 使用[UIView animateWithDuration:]而不是transitionFromViewController:

像这样:[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]; }]; 

它修复了问题2,但视图以viewDidAppear的布局开始(标签为绿色,y = 350)。 而且,交叉UIViewAnimationOptionTransitionCrossDissolve不如使用UIViewAnimationOptionTransitionCrossDissolve

好的,将layoutIfNeeded添加到toViewController.view似乎是做了窍门 – 这使得视图在屏幕上显示之前(没有添加/删除)正确布局,没有更奇怪的双重viewDidAppear:调用。

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

有同样的问题,所有你需要的是转发外观交易和UIView.Animate。 这种方法解决了所有问题,不会创build新的问题。 这里是一些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); } ); 

当然你应该禁用外观方法的自动转发并手动完成:

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