前言

ios5以前控制器容器是苹果专用(呵呵,苹果一直都狠XD)。使用控制器容器的视图层级如下:
图片描述

带来的问题是子控制器接收不到window传递下来的各类状态,比如:viewWillAppear:消息。
iOS5以前所有做法是在父亲视图控制器内强引用一个子视图控制器,所有子控制器的事件方法调用都是根据起父视图控制器的状态并且需要手动传导到子视图控制器,这样操作比较难实现。

栗子

铲子挖到地球对端....(有魄力),其实就是弄两个关联的控制器,当一方视图控制器视图接收并响应事件的时候另一个关联视图控制器也响应。
继续这个挖地球的栗子,每次完整事件无非是拖动->定位->刷UI。请确保子控制器视图跟预期一样是正确的。

示例代码,在RootViewController中添加子视图控制器:

 1. (void)viewDidLoad
{
    [super viewDidLoad];

    //Setup controllers
    _startMapViewController = [RGMapViewController new];
    [_startMapViewController setAnnotationImagePath:@"man"];
    [self addChildViewController:_startMapViewController];          //  1
    [topContainer addSubview:_startMapViewController.view];         //  2
    [_startMapViewController didMoveToParentViewController:self];   //  3
    [_startMapViewController addObserver:self
                              forKeyPath:@"currentLocation" 
                                 options:NSKeyValueObservingOptionNew 
                                 context:NULL];

    _startGeoViewController = [RGGeoInfoViewController new];        //  4
}

1.添加子视图控制器
2.添加子视图控制器的视图
3.子视图控制器被告知有一个父视图控制器
4.响应后生成子视图初始化,但是并没有被做任何设置。

布局

根视图控制器定义了两个内置视图来决定子视图控制器的视图大小,因为子视图控制器无法感知被加入了那个视图容器,所以size需要是灵活可变的。

- (void) loadView
{
    mapView = [MKMapView new];
    mapView.autoresizingMask = UIViewAutoresizingFlexibleWidth |UIViewAutoresizingFlexibleHeight;
    [mapView setDelegate:self];
    [mapView setMapType:MKMapTypeHybrid];

    self.view = mapView;
}

根据上述代码可以做到根据父视图的边界自适应大小,同时可以增加了子视图控制器的复用(压入新的导航栏控制器)。

转场

大苹果提供了转场动画接口transitionFromViewController:toViewController:(...)

- (void) flipFromViewController:(UIViewController*) fromController 
               toViewController:(UIViewController*) toController  
                  withDirection:(UIViewAnimationOptions) direction
{
    toController.view.frame = fromController.view.bounds;                           //  1
    [self addChildViewController:toController];                                     //  
    [fromController willMoveToParentViewController:nil];                            //  

    [self transitionFromViewController:fromController
                      toViewController:toController
                              duration:0.2
                               options:direction | UIViewAnimationOptionCurveEaseIn
                            animations:nil
                            completion:^(BOOL finished) {

                                [toController didMoveToParentViewController:self];  //  2
                                [fromController removeFromParentViewController];    //  3
                            }];
}

1.添加toController的时候我们先通知fromController它将要被移除。如果fromController的视图是其容器视图层级视图,那么viewWillDisapear:会被调用。
2.toController被其新父控制器通知,此时恰当得视图事件将会被调用。
3.fromController被移除。

这里要记住。。两个子控制器视图添加到容器视图内是为了保证翻转动画不引起整个根视图的翻转。

通信

视图控制器需要满足可以服用并且是自包含的实体,子视图控制器也不例外。在这种情形下,父视图控制器只需要关心两件事:一是排版好子视图控制器的根视图,一是与子视图控制通过开放的API进行通信。绝对不允许直接去修改子视图的图层结构树以及内部状态。

子视图需要包含足够的代码逻辑去管理自身的视图树(不要不把它们当回事)。这样做的好处是更清晰更解耦的逻辑以及更好的复用性。

在上路的栗子中,父视图控制器通过观察map试图控制器上一个叫做currentLocation的属性:

[_startMapViewController addObserver:self 
                          forKeyPath:@"currentLocation"
                             options:NSKeyValueObservingOptionNew
                             context:NULL];

当该属性变化的时候,父视图控制器与另一个地图视图控制器进行通信,让其刷新此时的对端极坐标。

[oppositeController updateAnnotationLocation:[newLocation antipode]];

同样的,当你点击雷达按钮的时候,父视图对翻转的新的视图控制器设置了此时的坐标。

[_startGeoViewController setLocation:_startMapViewController.currentLocation];
[_targetGeoViewController setLocation:_targetMapViewController.currentLocation];

不管你使用什么方式与子视图控制器进行通信,目的都是一样的:
那就是子视图控制器要保证解耦并且可以复用!


Cruise_Chan
729 声望71 粉丝

技能树点歪了...咋办