自定义容器控制器:transitionFromViewController:在动画之前查看未正确布局

Jos*_*Lin 24 xcode uiviewcontroller ios uiappearance

这既是一个问题,也是一个部分解决方案.


*示例项目:

https://github.com/JosephLin/TransitionTest


问题1:

使用时,过渡动画开始时,不会显示transitionFromViewController:...toViewControllers 完成的布局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];
                            }];
}
Run Code Online (Sandbox Code Playgroud)

现在,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];
}
Run Code Online (Sandbox Code Playgroud)

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

在此输入图像描述 在此输入图像描述 动画前后的预期视图.


但是,标签处于y=50,就像viewWillAppear在制作动画之前没有制作的布局一样(左图).但请注意标签的背景设置为黄色(指定的颜色viewWillAppear)!

在此输入图像描述 在此输入图像描述 动画开头的布局错误.请注意,条形按钮也以错误的位置/大小开头.


Console Log
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的解决方案

好的,这里是部分解决方案部分.通过显式调用beginAppearanceTransition:endAppearanceTransitiontoViewController,该视图将有正确的布局过渡动画发生之前:

- (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];
                            }];
}
Run Code Online (Sandbox Code Playgroud)

请注意,viewWillAppear:现在称为BEFOREtransitionFromViewController: 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任何或.请非常感谢您对此问题提供的任何见解.谢谢!


我试过的其他解决方案

  • [self.view addSubview:toViewController.view];之前打电话transitionFromViewController:

它实际上为用户提供了正确的结果,修复了问题1和2.问题是,viewWillAppear并且viewDidAppear都将被调用两次!如果我想做一些广阔的动画或计算,这是有问题的viewDidAppear.

  • [toViewController viewWillAppear:YES];之前打电话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];
}];
Run Code Online (Sandbox Code Playgroud)

它修复了问题2,但视图以布局开始viewDidAppear(标签为绿色,y = 350).此外,交叉溶解不如使用UIViewAnimationOptionTransitionCrossDissolve

esc*_*ord 7

好吧,添加layoutIfNeeded到toViewController.view似乎可以做到这一点 - 这会在视图显示在屏幕上之前正确显示视图(没有添加/删除),并且没有更奇怪的双viewDidAppear:call.

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

}
Run Code Online (Sandbox Code Playgroud)