如何使用容器过渡的自动布局?

Mik*_*ler 31 uiviewcontroller ios autolayout

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

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

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

该方法可以将视图添加到视图层次结构并为您运行动画.

问题是您不能在同一位置(在方法调用之前)添加初始约束,因为视图尚未添加到视图层次结构中.

任何想法我如何使用此方法以及自动布局?

以下是使用Springs/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];
                            }];
}
Run Code Online (Sandbox Code Playgroud)

Mik*_*ler 11

开始认为实用程序方法transitionFromViewController:toViewController:duration:options:animations:无法使用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];
                     }];
}
Run Code Online (Sandbox Code Playgroud)


Ste*_*cor 8

真正的解决方案似乎是在动画块中设置约束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];
                        }];
Run Code Online (Sandbox Code Playgroud)


Sen*_*ful 8

根据您是否只需要通过自动布局(简单)定位视图与需要动画自动布局约束更改(更难),有两种解决方案.

TL; DR版本

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

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

解决方案1:通过自动布局定位视图

让我们先解决第一个简单案例.在这种情况下,视图应通过自动布局定位,以便更改状态栏高度(例如,通过选择切换呼叫状态栏)等,不会将事情从屏幕上移开.

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

- (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];
            }];
}
Run Code Online (Sandbox Code Playgroud)

我们必须添加约束,而不是像上面的例子那样使用框架.问题是在哪里添加它们.我们无法在上面的标记(2)处添加它们,因为newC.view它未安装在视图层次结构中.它只在我们打电话的那一刻安装transitionFromViewController...(3).这意味着我们可以在调用transitionFromViewController之后立即安装约束,或者我们可以将其作为动画块中的第一行.两者都应该有效.如果你想在最早的时候做,那么把它放在动画块中就是要走的路.下面将讨论关于如何调用这些块的顺序的更多内容.

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

- (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
}
Run Code Online (Sandbox Code Playgroud)

解决方案2:动画约束更改

动画约束更改并不那么简单,因为在视图附加到层次结构和通过该transitionFromViewController...方法调用动画块之间没有给出回调.

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

- (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
}
Run Code Online (Sandbox Code Playgroud)

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

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

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

执行此操作后,似乎该方法的实现方式类似于以下方式:

- (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);
    });
}
Run Code Online (Sandbox Code Playgroud)

现在很清楚,为什么不能使用transitionFromViewController来为约束更改设置动画.第一次初始化约束是在添加视图之后(第A行).约束应该在animations()块中进行动画处理(B行),但是无法在这两行之间运行代码.

因此,我们必须使用手动动画块,以及动画约束变化标准方法:

- (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];
                     }];
}
Run Code Online (Sandbox Code Playgroud)

警告

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

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

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