如何使用容器转换的自动布局?

如何使用UIViewController容器转换方法使用自动布局:

-(void)transitionFromViewController:(UIViewController *)fromViewController toViewController:(UIViewController *)toViewController duration:(NSTimeInterval)duration options:(UIViewAnimationOptions)options animations:(void (^)(void))animations completion:(void (^)(BOOL finished))completion; 

传统上,使用Springs / Struts,您可以设置初始帧(在调用此方法之前),并在传递给方法的animation块中设置最终帧。

该方法将视图添加到视图层次结构并为您运行animation。

问题在于,我们不能在同一个地方(方法调用之前)添加初始约束,因为视图还没有添加到视图层次结构中。

任何想法如何与自动布局一起使用这种方法?

下面是一个使用Spring / Struts(框架)的例子(谢谢cocoanetics) http://www.cocoanetics.com/2012/04/containing-viewcontrollers

 - (void)transitionFromViewController:(UIViewController *)fromViewController toViewController:(UIViewController *)toViewController { // XXX We can't add constraints here because the view is not yet in the view hierarchy // animation setup toViewController.view.frame = _containerView.bounds; toViewController.view.autoresizingMask = _containerView.autoresizingMask; // notify [fromViewController willMoveToParentViewController:nil]; [self addChildViewController:toViewController]; // transition [self transitionFromViewController:fromViewController toViewController:toViewController duration:1.0 options:UIViewAnimationOptionTransitionCurlDown animations:^{ } completion:^(BOOL finished) { [toViewController didMoveToParentViewController:self]; [fromViewController removeFromParentViewController]; }]; } 

开始认为实用方法transitionFromViewController:toViewController:duration:options:animations:completion不能使用Auto Layout干净地工作。

现在我已经用这个方法直接调用了每一个“低级别”的包含方法。 这是更多的代码,但似乎给予更大的控制。

它看起来像这样:

 - (void) performTransitionFromViewController:(UIViewController*)fromVc toViewController:(UIViewController*)toVc { [fromVc willMoveToParentViewController:nil]; [self addChildViewController:toVc]; UIView *toView = toVc.view; UIView *fromView = fromVc.view; [self.containerView addSubview:toView]; // TODO: set initial layout constraints here [self.containerView layoutIfNeeded]; [UIView animateWithDuration:.25 delay:0 options:0 animations:^{ // TODO: set final layout constraints here [self.containerView layoutIfNeeded]; } completion:^(BOOL finished) { [toVc didMoveToParentViewController:self]; [fromView removeFromSuperview]; [fromVc removeFromParentViewController]; }]; } 

真正的解决scheme似乎是在transitionFromViewController:toViewController:duration:options:animations:的animation块中设置约束transitionFromViewController:toViewController:duration:options:animations:

 [self transitionFromViewController:fromViewController toViewController:toViewController duration:1.0 options:UIViewAnimationOptionTransitionCurlDown animations:^{ // SET UP CONSTRAINTS HERE } completion:^(BOOL finished) { [toViewController didMoveToParentViewController:self]; [fromViewController removeFromParentViewController]; }]; 

有两种解决scheme,取决于您是否需要通过自动布局(简单)来定位视图,还是需要animation自动布局约束更改(更难)。

TL; DR版本

如果您只需要通过自动布局定位视图,则可以使用-[UIViewController transitionFromViewController:toViewController:duration:options:animations:completion:]方法,并将约束安装在animation块中。

如果您需要animation自动布局约束更改,则必须使用通用+[UIView animateWithDuration:delay:options:animations:completion:]调用并定期添加子控制器。

解决scheme1:通过自动布局定位视图

我们首先解决第一个简单的问题。 在这种情况下,视图应该通过自动布局进行定位,以便状态栏高度的变化(例如通过select“ 切换呼叫状态栏”等 )不会将任何事情推离屏幕。

作为参考,这里是苹果关于从一个视图控制器到另一个视图控制器的官方代码 :

 - (void) cycleFromViewController: (UIViewController*) oldC toViewController: (UIViewController*) newC { [oldC willMoveToParentViewController:nil]; // 1 [self addChildViewController:newC]; newC.view.frame = [self newViewStartFrame]; // 2 CGRect endFrame = [self oldViewEndFrame]; [self transitionFromViewController: oldC toViewController: newC // 3 duration: 0.25 options:0 animations:^{ newC.view.frame = oldC.view.frame; // 4 oldC.view.frame = endFrame; } completion:^(BOOL finished) { [oldC removeFromParentViewController]; // 5 [newC didMoveToParentViewController:self]; }]; } 

如上例所示,我们不必使用框架,而必须添加约束条件。 问题是在哪里添加它们。 我们不能在上面的标记(2)处添加它们,因为newC.view没有安装在视图层次结构中。 只有在我们调用transitionFromViewController... (3)的时候才安装。 这意味着我们可以在调用transitionFromViewController之后立即安装约束,或者我们可以将它作为animation块的第一行。 两者都应该工作。 如果你想在最早的时候做到这一点,那么把它放在animation块中是一条可行的路。 下面将讨论如何调用这些块的顺序。

总之,对于通过自动布局进行定位,请使用如下模板:

 - (void)cycleFromViewController:(UIViewController *)oldViewController toViewController:(UIViewController *)newViewController { [oldViewController willMoveToParentViewController:nil]; [self addChildViewController:newViewController]; newViewController.view.alpha = 0; [self transitionFromViewController:oldViewController toViewController:newViewController duration:0.25 options:0 animations:^{ newViewController.view.translatesAutoresizingMaskIntoConstraints = NO; // create constraints for newViewController.view here newViewController.view.alpha = 1; } completion:^(BOOL finished) { [oldViewController removeFromParentViewController]; [newViewController didMoveToParentViewController:self]; }]; // or create constraints right here } 

解决scheme2:animation约束更改

限制animation变化并不是那么简单,因为我们没有在视图附加到层次结构之间和通过transitionFromViewController...方法调用animation块之间的callback。

作为参考, 这里是添加/删除子视图控制器的标准方式:

 - (void) displayContentController: (UIViewController*) content; { [self addChildViewController:content]; // 1 content.view.frame = [self frameForContentController]; // 2 [self.view addSubview:self.currentClientView]; [content didMoveToParentViewController:self]; // 3 } - (void) hideContentController: (UIViewController*) content { [content willMoveToParentViewController:nil]; // 1 [content.view removeFromSuperview]; // 2 [content removeFromParentViewController]; // 3 } 

通过比较这两个方法和上面发布的cycleFromViewController:我们看到transitionFromViewController为我们处理了两件事情:

  • [self.view addSubview:self.currentClientView];
  • [content.view removeFromSuperview];

通过添加一些日志logging(从这篇文章中省略),我们可以很好地了解何时调用这些方法。

这样做之后,看起来该方法以类似于以下的方式实现:

 - (void)transitionFromViewController:(UIViewController *)fromViewController toViewController:(UIViewController *)toViewController duration:(NSTimeInterval)duration options:(UIViewAnimationOptions)options animations:(void (^)(void))animations completion:(void (^)(BOOL finished))completion { [self.view addSubview:toViewController.view]; // A animations(); // B dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(duration * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ [fromViewController.view removeFromSuperview]; completion(YES); }); } 

现在很清楚为什么使用transitionFromViewController来animation约束更改是不可能的。 第一次你可以初始化约束是在视图添加后(A行)。 约束应该在animations()块(B行)中animations() ,但是在这两行之间无法运行代码。

因此,我们必须使用手动animation块,以及animation约束更改的标准方法 :

 - (void)cycleFromViewController:(UIViewController *)oldViewController toViewController:(UIViewController *)newViewController { [oldViewController willMoveToParentViewController:nil]; [self addChildViewController:newViewController]; [self.view addSubview:newViewController.view]; newViewController.view.translatesAutoresizingMaskIntoConstraints = NO; // TODO: create initial constraints for newViewController.view here [newViewController.view layoutIfNeeded]; // TODO: update constraint constants here [UIView animateWithDuration:0.25 animations:^{ [newViewController.view layoutIfNeeded]; } completion:^(BOOL finished) { [oldViewController.view removeFromSuperview]; [oldViewController removeFromParentViewController]; [newViewController didMoveToParentViewController:self]; }]; } 

警告

这不等同于故事板如何embedded容器视图控制器。 例如,如果通过故事板与上面的方法比较embedded式视图的translatesAutoresizingMaskIntoConstraints值,它将向故事板报告YES ,而对于我上面推荐的方法, NO (显然,因为我们明确地将其设置为NO)。

这可能会导致您的应用程序不一致,因为系统的某些部分似乎依赖于UIViewController遏制与translatesAutoresizingMaskIntoConstraints设置为NO 。 例如, 在iPad Air(8.4)上,从纵向旋转到横向时可能会出现奇怪的行为 。

简单的解决scheme似乎是保持translatesAutoresizingMaskIntoConstraints设置为NO ,然后设置newViewController.view.frame = newViewController.view.superview.bounds 。 但是,除非你非常小心地调用这个方法,否则很可能会给你一个不正确的视觉布局。 (注意:故事板确保视图大小的方式是通过将embedded视图的autoresize属性设置为W+H在添加子视图后立即打印框架也将揭示故事板与程序化方法之间的差异,这表明苹果公司正在把框架直接放在包含的视图上。)

我希望你的问题得到一些牵引,因为我认为这是一个很好的问题。 我没有给你明确的答案,但是我可以用你自己的情况描述自己的经历。

下面是我从我的经验中得出的结论:不能直接在视图控制器的根视图上使用自动布局。 只要我在根视图上设置translatesAutoresizingMaskIntoConstraintsNO ,我开始得到错误,或者更糟。

所以我使用混合解决scheme。 我设置框架,并使用自动resize来定位和调整根据自动布局configuration的布局的根视图。 例如,以下是我如何在使用自动布局的应用程序的viewDidLoad中将页面视图控制器加载为子视图控制器:

 self.pageViewController = ... ... [self addChildViewController:self.pageViewController]; [self.view addSubview:self.pageViewController.view]; // could not get constraints to work here (using autoresizing mask) self.pageViewController.view.frame = self.view.bounds; [self.pageViewController didMoveToParentViewController:self]; 

这是苹果在Xcode“基于页面的应用程序”模板中加载子视图控制器的方式 – 这是在启用了自动布局的项目中执行的。

所以,如果我是你,我会尝试设置框架animation视图控制器转换,看看会发生什么。 让我知道它是如何工作的。