【问题标题】:Custom Container Controller: transitionFromViewController: View not properly layed-out before animation自定义容器控制器:transitionFromViewController:动画前视图布局不正确
【发布时间】:2013-01-15 22:12:14
【问题描述】:

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


*此处的示例项目:

https://github.com/JosephLin/TransitionTest


问题一:

使用transitionFromViewController:... 时,由toViewControllerviewWillAppear: 完成的布局不会在过渡动画开始时显示。换句话说,pre-layout view 在动画期间显示,其内容在动画结束后捕捉到 post-layout 位置。

问题 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 中执行此操作,因为当时它没有正确的帧。

出于演示目的,DetailViewControllerviewDidLoadviewWillAppearviewDidAppear 中将其标签设置为不同的位置和颜色:

- (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: 现在被称为 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!

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


我尝试过的其他解决方案

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

它实际上为用户提供了完全正确的结果,解决了问题 1 和 2。问题是,viewWillAppearviewDidAppear 都会被调用两次!如果我想在viewDidAppear 中做一些扩展的动画或计算是有问题的。

  • transitionFromViewController:之前致电[toViewController viewWillAppear:YES];

我认为这与调用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)。还有,cross-dissolve不如UIViewAnimationOptionTransitionCrossDissolve

【问题讨论】:

  • 使用[self.view addSubview:toViewController.view] 实际上似乎是一种合理的方式(至少对于我的用例而言)。只需检查这些方法是否已在最后一秒触发。

标签: ios xcode uiviewcontroller uiappearance


【解决方案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) {
                            }];

}

【讨论】:

  • 您好,escrafford,感谢您的回答。它解决了部分问题,但仍然不是完美的解决方案。条形按钮的行为仍然很奇怪(问题 2),而且 viewWillAppear: 被调用了两次,这在某些情况下可能会出现问题。此外,我收到“开始/结束外观转换的不平衡调用”警告。
  • 我的修改解决了问题吗?
  • 嗯...您可能走在正确的轨道上。 右栏按钮不再变形,但左栏按钮和底部工具栏上的按钮仍然如此。
  • Joseph Lin .. 有什么线索吗?奇怪的是,我也只在 leftBarButtonItems 而不是标题中看到了这个问题。
【解决方案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();
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2013-07-16
    • 2011-05-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多