定制集装箱控制器:transitionFromViewController:查看无法正常动画之前叠合

2019-08-16 16:46发布

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


*示例项目在这里:

https://github.com/JosephLin/TransitionTest


问题1:

当使用transitionFromViewController:... ,在完成布局toViewControllerviewWillAppear:不显示过渡动画开始时。 换句话说,布局前视图在动画显示,它的内容捕捉到动画后的布局后位置。

问题2:

如果自定义我的导航栏的背景UIBarButtonItem ,该栏按钮显示了动画之前错误的大小/位置,并捕捉到正确的大小/位置时,动画结束,类似的问题1。


为了说明问题,我做了一个裸骨定制容器控制器,做一些自定义视图过渡。 这几乎是一个UINavigationController副本,做交叉叠,而不是视图之间推动画。

“推”的方法是这样的:

- (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设置它的标签在不同的位置和颜色viewDidLoadviewWillAppear ,并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] 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: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];
                            }];
}

请注意, 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或什么的。 拜托,你可以提供给这个问题的任何见解是极大的赞赏。 谢谢!


其他的解决方案我已经试过

  • 呼叫[self.view addSubview:toViewController.view];transitionFromViewController:

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

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

我认为这是相当多的与调用beginAppearanceTransition: 它修复问题1,但不是问题2加上,医生说不要叫viewWillAppear直接!

  • 使用[UIView animateWithDuration:]代替transitionFromViewController:

像这样的:自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

Answer 1:

好吧,加入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) {
                            }];

}


Answer 2:

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


文章来源: Custom Container Controller: transitionFromViewController: View not properly layed-out before animation