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
Custom Container Controller: transitionFromViewController: View not properly layed-out before animation
提问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 viewDidLoad
because it wouldn't have the correct frame at that time.
现在,DetailViewController
(我正在推送的视图控制器)需要将其内容布局在viewWillAppear:
. 它不能这样做,viewDidLoad
因为它当时没有正确的框架。
For demonstration purpose, DetailViewController
sets its label to different locations and colors in viewDidLoad
, viewWillAppear
, and viewDidAppear
:
出于演示的目的,DetailViewController
将其标签在不同的位置和颜色viewDidLoad
,viewWillAppear
以及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 =200
at the begining of the animation (left image), and then jumps to y = 350
after the animation is finished (right image).
现在,当按下 时DetailViewController
,我希望y =200
在动画开始时看到标签(左图),然后y = 350
在动画完成后跳转到(右图)。
Expected view before and after animation.
动画前后的预期视图。
However, the label was at y=50
, as if the layout made in viewWillAppear
didn'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
)!
Wrong 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 endAppearanceTransition
to toViewController
, the view will have the correct layout before the transition animation takes place:
好了,部分解决方案到了。通过显式调用beginAppearanceTransition:
和endAppearanceTransition
to 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 UIAppearance
or 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];
beforetransitionFromViewController:
[self.view addSubview:toViewController.view];
之前打电话transitionFromViewController:
It actually gives exactly the right result to the user, fixes both Problem 1&2. The problem is, viewWillAppear
and viewDidAppear
will 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];
beforetransitionFromViewController:
[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 viewWillAppear
directly!
我认为这与调用beginAppearanceTransition:
. 它解决了问题 1 但不解决问题 2。另外,文档说不要viewWillAppear
直接打电话!
- Use
[UIView animateWithDuration:]
instead oftransitionFromViewController:
- 使用
[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();
}