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

时间:2013-01-31 17:52:32

标签: ios xcode uiviewcontroller uiappearance

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


*示例项目:

https://github.com/JosephLin/TransitionTest


问题1:

使用transitionFromViewController:...时,toViewController的{​​{1}}完成的布局在转换动画开始时不会显示。换句话说,在动画期间显示预布局视图,并且它的内容会在动画后捕捉到布局后的位置。

问题2:

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


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

“推送”方法如下所示:

UIBarButtonItem

现在,- (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将其标签设置为DetailViewControllerviewDidLoadviewWillAppear中的不同位置和颜色:

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动画完成(右图)。

enter image description here enter image description here 动画前后的预期视图。


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

enter image description here enter image description here 动画开头的布局错误。请注意,条形按钮也以错误的位置/大小开始。


控制台日志
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 viewWillAppear:


问题1的解决方案

好的,这里是部分解决方案部分。通过明确调用transitionFromViewController:beginAppearanceTransition:endAppearanceTransition,视图将在转换动画发生之前具有正确的布局:

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]; }]; } 现在被称为viewWillAppear: 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:transitionFromViewController:或其他什么的错误。请非常感谢您对此问题提供的任何见解。谢谢!


我尝试过的其他解决方案

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

它实际上为用户提供了正确的结果,修复了问题1和2。问题是,transitionFromViewController:viewWillAppear都会被调用两次!如果我想在viewDidAppear中进行一些广泛的动画或计算,那就有问题了。

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

我认为这与调用transitionFromViewController:几乎相同。它修复了问题1,但没有修复问题2.此外,文档说不要直接调用beginAppearanceTransition:

  • 使用viewWillAppear代替[UIView animateWithDuration:]

像这样:     [self addChildViewController:toViewController];     [self.view addSubview:toViewController.view];     toViewController.view.alpha = 0.0;

transitionFromViewController:

它解决了问题2,但视图以[UIView animateWithDuration:0.5 animations:^{ toViewController.view.alpha = 1.0; } completion:^(BOOL finished) { [toViewController didMoveToParentViewController:self]; }]; 中的布局开始(标签为绿色,y = 350)。此外,交叉溶解不如使用viewDidAppear

那么好

2 个答案:

答案 0 :(得分:7)

好的,在toViewController.view中添加layoutIfNeeded似乎可以解决问题 - 这会在视图显示在屏幕上之前正确显示视图(没有添加/删除),并且不再有奇怪的双重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) {
                            }];

}

答案 1 :(得分: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();
}
相关问题