xcode 自定义容器控制器:transitionFromViewController:动画前视图未正确布局

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/14632064/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-15 02:41:07  来源:igfitidea点击:

Custom Container Controller: transitionFromViewController: View not properly layed-out before animation

iosxcodeuiviewcontrolleruiappearance

提问by Joseph Lin

This is both a question and a partialsolution.

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



*Sample project here:

*示例项目在这里:

https://github.com/JosephLin/TransitionTest

https://github.com/JosephLin/TransitionTest



Problem 1:

问题1:

When using transitionFromViewController:..., layouts done by the toViewController's viewWillAppear: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.

使用 时transitionFromViewController:...,由toViewController's完成的布局viewWillAppear:不会在过渡动画开始时显示。换句话说,预布局视图在动画期间显示,并且它的内容在动画之后捕捉到布局后位置。

Problem 2:

问题2:

If I customize the background of my navbar's UIBarButtonItem, 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.

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



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.

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

The 'Push' method looks like this:

“推送”方法如下所示:

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

Now, DetailViewController(the view controller I'm pushing to) needs to layout its content in viewWillAppear:. It can't do it in viewDidLoadbecause it wouldn't have the correct frame at that time.

现在,DetailViewController(我正在推送的视图控制器)需要将其内容布局在viewWillAppear:. 它不能这样做,viewDidLoad因为它当时没有正确的框架。

For demonstration purpose, DetailViewControllersets its label to different locations and colors in viewDidLoad, viewWillAppear, and viewDidAppear:

出于演示的目的,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];
}


Now, when pushing the DetailViewController, I'm expecting to see the label at y =200at the begining of the animation (left image), and then jumps to y = 350after the animation is finished (right image).

现在,当按下 时DetailViewController,我希望y =200在动画开始时看到标签(左图),然后y = 350在动画完成后跳转到(右图)。

enter image description hereenter image description hereExpected view before and after animation.

在此处输入图片说明在此处输入图片说明动画前后的预期视图。



However, the label was at y=50, as if the layout made in viewWillAppeardidn'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 viewWillAppear)!

但是,标签在y=50,好像viewWillAppear在动画发生之前制作的布局没有制作(左图)。但是请注意标签的背景设置为黄色(由 指定的颜色viewWillAppear)!

enter image description hereenter image description hereWrong layout at the beginning of the animation. Notice that the bar buttons also start with the wrong position/size.

在此处输入图片说明在此处输入图片说明动画开始时的布局错误。请注意,条形按钮也以错误的位置/大小开始。



Console Log
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:]
Notice that viewWillAppear:was called AFTERtransitionFromViewController:

控制台登录
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:在调用AFTERtransitionFromViewController:



Solution for Problem 1

问题 1 的解决方案

Alright, here comes the partialsolution part. By explicitly calling beginAppearanceTransition:and endAppearanceTransitionto toViewController, the view will have the correct layout before the transition animation takes place:

好了,部分解决方案到了。通过显式调用beginAppearanceTransition:endAppearanceTransitionto 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];
                            }];
}

Notice that viewWillAppear:is now called BEFORE transitionFromViewController:TransitionTest[18398:c07] -[DetailViewController viewDidLoad]
TransitionTest[18398:c07] -[DetailViewController viewWillAppear:]
TransitionTest[18398:c07] Before transitionFromViewController:
TransitionTest[18398:c07] -[DetailViewController viewWillLayoutSubviews]
TransitionTest[18398:c07] -[DetailViewController viewDidLayoutSubviews]
TransitionTest[18398:c07] -[DetailViewController viewDidAppear:]

请注意,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:]



But that doesn't fix Problem 2!

但这并不能解决问题 2!

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 transitionFromViewController:or UIAppearanceor whatever. Please, any insight you can offer to this question is greatly appreciated. Thanks!

无论出于何种原因,导航栏按钮在过渡动画开始时仍以错误的位置/大小开始。我花了很多时间试图找到正确的解决方案,但没有运气。我开始觉得这是一个错误transitionFromViewController:UIAppearance或什么的。请您对此问题提供任何见解,我们将不胜感激。谢谢!



Other solutions I've tried

我尝试过的其他解决方案

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

It actually gives exactly the right result to the user, fixes both Problem 1&2. The problem is, viewWillAppearand viewDidAppearwill both be called twice! It's problematic if I want to do some expansive animation or calculation in viewDidAppear.

它实际上为用户提供了完全正确的结果,解决了问题 1 和 2。问题是,viewWillAppear并且viewDidAppear都会被调用两次!如果我想在viewDidAppear.

  • Call [toViewController viewWillAppear:YES];before transitionFromViewController:
  • [toViewController viewWillAppear:YES];之前打电话transitionFromViewController:

I think it's pretty much the same as calling beginAppearanceTransition:. It fixes Problem 1 but not Problem 2. Plus, the doc says not to call viewWillAppeardirectly!

我认为这与调用beginAppearanceTransition:. 它解决了问题 1 但不解决问题 2。另外,文档说不要viewWillAppear直接打电话!

  • Use [UIView animateWithDuration:]instead of transitionFromViewController:
  • 使用[UIView animateWithDuration:]代替transitionFromViewController:

Like this: [self addChildViewController:toViewController]; [self.view addSubview:toViewController.view]; toViewController.view.alpha = 0.0;

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

It fixes Problem 2, but the view started with the layout in viewDidAppear(label is green, y=350). Also, the cross-dissolve is not as good as using UIViewAnimationOptionTransitionCrossDissolve

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

采纳答案by escrafford

Ok, adding layoutIfNeeded to the toViewController.view seems to do the trick - this gets the view laid out properly before it shows up on screen (without the add/remove), and no more weird double viewDidAppear: call.

好的,将 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) {
                            }];

}

回答by Alexander Danilov

Had the same problem, all you need is forwarding appearance transactions and UIView.Animate. This approach fixes all problems, doesn't create new ones. Here is some C# code (xamarin):

有同样的问题,你只需要转发外观事务和 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);
    }
);

and of course you should disable automatic forwarding of appearance methods and do it manually:

当然,您应该禁用外观方法的自动转发并手动执行:

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