24

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


*这里的示例项目:

https://github.com/JosephLin/TransitionTest


问题1:

使用 时,由'stransitionFromViewController:...完成的布局不会在过渡动画开始时显示。换句话说,预布局视图在动画期间显示,并且它的内容在动画之后捕捉到布局后位置。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:and 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:现在称为 BEFOREtransitionFromViewController: 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:]


但这并不能解决问题 2!

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


我尝试过的其他解决方案

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

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

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

我认为这与 call 几乎相同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

4

2 回答 2

7

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

}
于 2013-02-05T17:11:16.033 回答
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();
}
于 2015-02-03T21:13:15.930 回答