ViewController容器

分享一下我老师大神的人工智能教程!零基础,通俗易懂!http://blog.csdn.net/jiangjunshow

也欢迎大家转载本篇文章。分享知识,造福人民,实现我们中华民族伟大复兴!

               

原文: http://www.cocoanetics.com/2012/04/containing-viewcontrollers/

 

在我的一个项目中,我需要实现一种容器式的 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 容器,极大地简化了我创建复杂的多分割界面的工作。

本教程代码从这里下载


           

给我老师的人工智能教程打call!http://blog.csdn.net/jiangjunshow
这里写图片描述
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值