zoukankan      html  css  js  c++  java
  • ViewController容器

    在我的一个项目中,我需要实现一种容器式的 view controller。我感觉几乎是寸步难行,因为这种技术用的人是那么的少。因为很显然,开发者更喜欢重用和利用已有的view controller,而不是发明新的容器。

    但是在某些情况下你更需要定制自己的容器。比起UINavigationController 和 UITabBarController,自己的容器更能简化你的代码。想起你什么时候以及什么情况下会使用这两个控制器吗?

    我很容易就想到一个例子。当你想用 view controller 去包含多个占据全窗口的view 时,可以用一个隐藏掉导航栏的 UINavigationController。如果标准的转换动画不能满足你,你还可能要自己定义视图切换和动画。不幸的是,我们今天不想讨论这个,而是要讨论如何实现自己的容器。

    首先,你需要对 view 以及 view controllers 的树形结构有一个了解。在iOS 5 以前,我们经常构建一个 view controller 对象,然后将它的view添加到已有的视图树当中。现在不需要了!

    现在,你再也不会这样做了。相反,你会用 UIViewController 来添加、删除子viewcontroller 。

    另外, 我们已经习惯于把 view controller 看做是整个屏幕,例如tab bar controller 中的子视图控制器。但 UISplitViewController 的出现,则打破了这个铁律。Viewcontrollers仅管理了屏幕当中的一部分区域——当然,对于屏幕空间更加宝贵的 iPhone 来说,则是整个屏幕,除了在屏幕的边沿会有一个 content bar。UISplirViewController有2个子控制器集合,一个针对左边(“主视图”),另一个则针对右边(“详细视图”)。

    UIViewController 有两个方法,用于添加和删除一个子视图控制器。

    它们属于 UIViewController 的新类别“UIContainerViewControllerProtectedMethods”:

    @interface UIViewController (UIContainerViewControllerProtectedMethods)   

    - (void)addChildViewController:(UIViewController *)childController; 

    - (void)removeFromParentViewController;   

    @end

    这两个方法的作用正如其名。你可能猜到如何用它们了。正如你使用addSubview 和 removeFromSuperview 一样。后面我们会演示。注意:我们假设你使用 ARC。

    根据文档,我们可以自由定义使用方式。比如一次只能见到一个 VC(类似导航控制器),或者通过tab 进行导航(tab bar 控制器),或者多个 VC 按一定顺序排在一起(UIPageontrolle)。

    你可以向子控制器集合中干3件事,它们有少许不同:

    • Add 添加到容器中
    • Remove 从容器中删除
    • Transition 切换到另一个控制器 (例如:加入一个新的,删除一个旧的)

    你必须确认这 4 个委托方法能被正确调用,额外的两个方法在 VC 被添加到一个新的父容器之前和之后调用。当parent 为 nil,则表明 VC 从容器中删除。

    为什么要关心委托消息的发生?因为我们经常会使用view(Did|Will)(A|Disa)pear 方法来初始化以及销毁某些东西,因此必须关心这些方法调用后的结果。如果这些方法执行错误,你会在控制台中看到一些讨厌的“unbalanced messages”警告。我们用一个例子进行说明。假设你想达到某种类似于 tab bar controller 的效果。我们有一个view controller 数组,我们要在这些 view controller 之间进行切换。由于作为容器的 VC 是 app 的rootViewController,当 app 启动时,我们要显示第一个子控制器。

    准备

    在实现部分,我们声明一些变量(我们只想在ContainerViewController 中访问它):

    @implementation ContainerViewController {

            NSArray *_subViewControllers;

            UIViewController *_selectedViewController;

            UIView *_containerView; 

    }

    你可以将 subViewControllers 设成一个静态的 ViewController 数组。selectedViewController 会指向当前正在显示的 VC,containerView是一个容器,代表子VC 将放到 containerViewController 的某一个区域。然后在 loadView:方法中:

    - (void)loadView  {

            //  构建 VC 视图

            CGRect frame = [[UIScreen mainScreen] applicationFrame];         UIView  *view =  [[UIView alloc]initWithFrame:frame];         view.autoresizingMask  = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;

            view.backgroundColor = [UIColor blueColor];

            // 在 view 基础上构建 content view (高度缩减100)

            frame = CGRectInset(view.bounds, 0, 100);

            _containerView = [[UIView alloc] initWithFrame:frame];         _containerView.backgroundColor  = [UIColor redColor];         _containerView.autoresizingMask  = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;         [view addSubview:_containerView];

            // 这里container VC 会自动调整方向

            self.view = view; 

    }

    为便于区分,view 的背景色为蓝色,container view 的背景色为红色。子 VC 会在红色区域显示,当我们旋转设备,子 VC 会自动调整大小。


    app delegate 中的代码缺少新意,加入 import 语句然后创建 ContainerViewController 并设置为 RootVC。

    - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {

         self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];

            ContainerViewController  *container =  [[ContainerViewController alloc] init];          self.window.rootViewController  = container;

           [self.window makeKeyAndVisible];

         return YES; 

    }

    接下来需要用几个 view controller 来扮演子 VC 的角色。我创建了一个简单的ViewController子类,上面仅有一个 UILabel 用于显示某些文本。为简便起见,我们显示的是它们的 description。

    - (void)loadView  {

            // set up the base view

            CGRect frame = [[UIScreen mainScreen] applicationFrame];         UILabel  *label =  [[UILabel alloc]initWithFrame:frame];         label.numberOfLines  = 0;

           // multiline

            label.textAlignment = UITextAlignmentCenter;

            // let's just have this view description

            label.text = [self description];

            self.view = label; 

    }

    你以前肯定没见过只有一个 UILabel 构成的 view controller。

    添加

    接下来我们要将这些 view controller 放到数组中并将数组加到容器中。在app delegate 中加入以下内容:

    // make an array of 5 PageVCs

    NSMutableArray *tmpArray = [NSMutableArray array];

       for (int i=0; i<5; i++) {

            PageViewController *page = [[PageViewController alloc] init];

            [tmpArray addObject:page];

     }

       // set these as sub VCs

     [container setSubViewControllers:tmpArray];

    重载 setSubViewControllers 方法,以便选择第一个VC(索引0)作为 selected VC并显示。当然,我们无法在 setter 方法中真的去显示 VC,因为 view 还未加载,同时我们的containerView 变量仍然还是 nil。

    - (void)setSubViewControllers:(NSArray *)subViewControllers {         _subViewControllers  = [subViewControllers copy];

            if (_selectedViewController)   {

                   // TODO: remove previous VC

            }

            _selectedViewController  = [subViewControllers objectAtIndex:0];

            // cannot add here because the view might not have been loaded yet

    }

    @synthesize subViewControllers = _subViewControllers;

    相反,我们应该在 viewWillAppear 中显示 VC,因为loadView 方法已经得到调用。另外,如果我们发现 selected VC 的 parent 已经是 self,我们可以什么都不做,已避免一些不必要的动作。

    - (void)viewWillAppear:(BOOL)animated {

            [super viewWillAppear:animated];

            if (_selectedViewController.parentViewController  == self)    {

                   // nowthing to do

                   return;

            }

            // adjust the frame to fit in the container view         _selectedViewController.view.frame  =_containerView.bounds;

            // make sure that it resizes on rotation automatically         _selectedViewController.view.autoresizingMask  =_containerView.autoresizingMask;

            // add as child VC

            [self addChildViewController:_selectedViewController];       // add it to container view, calls willMoveToParentViewController for us

            [_containerView addSubview:_selectedViewController.view];    // notify it that move is done    [_selectedViewController didMoveToParentViewController:self]; 

    }

    调用顺序为 viewWillAppear,viewDidAppear, willMoveToParentViewController and didMoveToParentViewController。注意,除了最后一个外,其他方法都是被自动调用的。由于未知原因 didMove 方法不会自动调用,因此我们必须手动调用。

    接下来,我们需要从一个 VC 跳到下一个 VC。

    转换

    要在子控制器之间切换,我们需要增加一个手势识别器。朝左扫动,将控制器向前切换一页,朝右扫动则向后切换一页。在 loadView 中加入:

    // add gesture support

    UISwipeGestureRecognizer *swipeLeft  = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:@selector(swipeLeft:)]; 

    swipeLeft.direction = UISwipeGestureRecognizerDirectionLeft; 

    [view addGestureRecognizer:swipeLeft];   

    UISwipeGestureRecognizer *swipeRight  = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:@selector(swipeRight:)]; 

    swipeRight.direction = UISwipeGestureRecognizerDirectionRight; 

    [view addGestureRecognizer:swipeRight];

    swipeLeft 和 swipeRight 方法实现如下。为了简单起见,我们用两个手势识别器。因为要在一个手势识别器中识别两个方向比较麻烦。

    - (void)swipeLeft:(UISwipeGestureRecognizer *)gesture {

            if  (gesture.state == UIGestureRecognizerStateRecognized)     {

                   NSInteger index = [_subViewControllers indexOfObject:_selectedViewController];

                   index  = MIN(index+1, [_subViewControllers count]-1);

                   UIViewController  *newSubViewController = [_subViewControllers objectAtIndex:index];

                   [self transitionFromViewController:_selectedViewController toViewController:newSubViewController];

            }

    }

    - (void)swipeRight:(UISwipeGestureRecognizer *)gesture {

            if  (gesture.state == UIGestureRecognizerStateRecognized)     {

                   NSInteger index = [_subViewControllers indexOfObject:_selectedViewController];

                   index  = MAX(index-1, 0);

                   UIViewController  *newSubViewController = [_subViewControllers objectAtIndex:index];

                   [self transitionFromViewController:_selectedViewController toViewController:newSubViewController];

            }

    }

    从一个 VC 跳转到另一个 VC 用transitionFromViewController:toViewController:方法来实现。这是真正有意思的地方。用一个巧妙的方法处理最麻烦的视图添加和删除工作。当然,一些附带的消息传送是必须的。

    - (void)transitionFromViewController:(UIViewController *)fromViewController toViewController:(UIViewController *)toViewController {

            if  (fromViewController == toViewController)   {

                   // cannot transition to same

                   return;

            }

            // 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]; 
            }];

    }

    有许多 UIViewAnimationOptionTransition变量,但你没必要关心它。如果你想让两个 view 执行动画块,也可以将该选项指定为0。

    之前我想用以前的方式去执行动画。但这会有一些我们意想不到的后果。你需要在转换之前调用“will”委托方法,而在转换之后调用“did”委托方法。如果你自己执行动画,iOS 5 将自动为你发送这些消息,但它会同时发送这些消息。这导致无法在VC显示和消失时执行不同的动作。

    结束语

    为了让所有的消息被调用并保持平衡,花了我不少的时间。

    这个示例程序最终得以正常运行。

    一旦你掌握了本文中的两个技术,在通向自己实现 viewcontroller容器的路上,将迈出你前所未有的一步。

    有一件事情,我至始至终都没有提到,为什么在转换动画中,新控制器的view总是会加在容器view的主视图上。这简化了某些工作,因为知道在动画在哪个阶段来添加或者删除某些视图是没有必要的。

    但是会有这种情况,你不想让动画在整个 container VC 的区域上执行。

    对于这种情况,我所能想到的就是另外用一个子视图遮住这部分。或者可以遍历 viewcontrollers,然后让其中一个遮住 container view 之外的区域。然后裁剪它的 subviews 仅仅留下所需的部分。

    各种 view controller 容器的最大好处是旋屏消息(should|will|did)方法可以传递到你的VC 树的最末梢。除非你关闭了它,也就是覆盖 automaticallyForwardAppearanceAndRotationMethodsToChildViewControllers方法,返回 NO。

    一旦那些我们曾经期待已久的 API 变成过往的时候,谁还会想那么多呢?使用view controller 容器,极大地简化了我创建复杂的多分割界面的工作。

    本教程代码从这里下载

  • 相关阅读:
    制作keil5的pack
    【转】链接脚本(1)
    mongodb数据到MySQL数据库 的迁移步骤
    mongo副本集设置主库权重,永远为主
    mongodb副本集的从库永久性设置setSlaveOk
    Ubuntu系统查看mongo得慢日志,及一些操作
    Ubuntu系统下手动释放内存
    linux下面得小数计算
    Syncthing搭建
    ubuntu搭建ftp服务器
  • 原文地址:https://www.cnblogs.com/YH-Coding/p/5331761.html
Copyright © 2011-2022 走看看