这既是一个问题,也是一个部分解决方案。
*示例项目在此处:
https://github.com/JosephLin/TransitionTest
问题1:
使用transitionFromViewController:...
时,由toViewController
的viewWillAppear:
完成的布局不会在过渡动画开始时显示。换句话说,在动画期间显示预先布局的视图,并且其内容在动画之后捕捉到后布局的位置。
问题2:
如果我自定义了我的导航栏的UIBarButtonItem
的背景,则在动画之前,工具栏按钮以错误的大小/位置显示,并在动画结束时捕捉到正确的大小/位置,类似于问题1。
为了演示问题,我制作了一个基本的自定义容器控制器,用于执行一些自定义视图转换。它与UINavigationController非常相似,只是在视图之间执行交叉溶解而不是推送动画。
'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
在 viewDidLoad
、viewWillAppear
和 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];
}
现在,当推送DetailViewController
时,我希望看到标签在动画开始时位于y = 200
(左图),然后在动画完成后跳到y = 350
(右图)。
预期的视图在动画前后。
然而,标签在y = 50
处,好像在viewWillAppear
中设置的布局没有在动画发生之前完成(左边的图片)。但请注意,标签的背景色设置为黄色(由viewWillAppear
指定的颜色)!
动画开始时的错误布局。请注意,栏按钮也以错误的位置/大小开始。
控制台日志
TransitionTest[49795:c07] -[DetailViewController viewDidLoad]
TransitionTest[49795:c07] Before transitionFromViewController:
TransitionTest[49795:c07] -[DetailViewController viewWillAppear:]
TransitionTest[49795:c07] -[DetailViewController viewWillLayoutSubviews]
TransitionTest[49795:c07] -[DetailViewController viewDidLayoutSubviews]
TransitionTest[49795:c07] -[DetailViewController viewDidAppear:]
请注意,viewWillAppear:
在transitionFromViewController:
之后被调用
问题1的解决方案
好了,这里是部分解决方案。通过显式调用beginAppearanceTransition:
和endAppearanceTransition
到toViewController
,视图将在过渡动画发生前具有正确的布局:
- (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:
现在在 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!
出于某种原因,导航栏按钮在过渡动画开始时仍然具有错误的位置/大小。我花了很多时间尝试找到正确的解决方法,但没有运气。我开始觉得这是一个transitionFromViewController:
或UIAppearance
等中的错误。请提供任何您可以提供的对此问题的见解,非常感谢。谢谢!
我尝试过的其他解决方案
- 在
transitionFromViewController:
之前调用[self.view addSubview:toViewController.view];
它实际上会给用户带来完全正确的结果,解决问题1和2。问题是,viewWillAppear
和viewDidAppear
都将被调用两次!如果我想在viewDidAppear
中进行一些昂贵的动画或计算,这是有问题的。
- 在
transitionFromViewController:
之前调用[toViewController viewWillAppear:YES];
我认为这基本上与调用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
效果比交叉溶解要好。
[self.view addSubview:toViewController.view]
实际上似乎是一个合理的方法(至少对于我的用例来说)。只需在这些方法中检查它们是否已经在最后一秒钟内触发即可。 - sobri