iOS View Controller概览

### 引言

在斯坦福CS193p课程中,白胡子老教授首先介绍了iOS开发中的MVC软件设计模式,这是因为iOS应用即采用此种设计模式。如下图所示,MVC模式将软件分为三个部分:

  • Model:数据存储层
  • View:视图展示层
  • Controller:逻辑控制层

MVC模式隔离了视图层和数据层,两者必须以控制层作为中介实现交互和通信。实际的iOS应用一般都是MVC模式的嵌套组合实现,如下图所示。其中,控制层是由UIViewController类及其子类(如:UITableViewController、UINavigationController、UITabBarController)的实例实现的。UIViewController自带一个根视图层(即view属性),以便添加和操作用户自定义的视图层。当然,我们也可以在UIViewController中定义数据层。这是因为UIViewController需要这样的接口来连通并控制M和V。但是,好的设计会尽量将数据层和视图层从UIViewController中解耦分离出来。

### 视图控制器

分类

前面说到,实际的app采用MVC嵌套组合的设计模式,以此为据,app中至少包含一个视图控制器。这些视图控制器管理着app中所有的事务,包括:视图管理、事件处理、视图控制器之间的调度等等。

既然视图控制器具有可以嵌套和组合,那么肯定有部分视图控制器可以充当视图控制器的容器。事实上,视图控制器的确可以分为以下两种:

  • 内容视图控制器(Content View Controller):用于管理app内容的视图控制器,也是最常用的视图控制器。
  • 容器视图控制器(Container View Controller):作为其他视图控制器的容器,并以某种方式呈现这些视图控制器及其内容。
内容视图控制器

内容视图控制器是最常用的元素,常见的有三种(后两者是前者的子类):

  • UIViewController
  • UITableViewController
  • UICollectionViewController

常用的在大多数情况下,app上的一个页面就是由一个视图控制器所展现的。内容视图控制器的主要任务包含:

  • 更新视图内容,通常是响应底层数据的变化
  • 响应用户与视图的交互
  • 缩放视图、管理布局
容器视图控制器

容器视图控制器使用属性viewcontrollers来存储对视图控制器的引用,并且不对容器中视图控制器的数量作限制。最常见容器视图控制器的有两种(两者均是UIViewController的子类):

  • UINavigationController
  • UITabBarController

关于viewcontrollers属性,UINavigationController采用的是stack结构,UITabBarController采用的是array结构。不同的数据结构,对视图控制器的调度形式也不同。

下图所示为UINavigationController的应用示例,UINavigationController采用stack的方式调度页面(即视图控制器)。

三个视图控制器视图的顶部是UINavigationController的导航栏,当切换到某个内容视图控制器时,导航栏的样式可以由该视图控制器进行配置。

下图所示为UITabBarController的应用示例,其对页面的调度和操作array一样灵活,视图的底部有四个标签,对应着4个独立的视图控制器,可任意切换。

视图管理

视图管理器,顾名思义,其有一个重要的作用就是:管理视图的层次结构。每个视图控制器都有一个根视图,其中包含了视图控制器的所有内容,可通过view属性获取。通过该属性,我们可以添加更多视图。
内容视图管理器自身管理其所有的视图;容器视图管理器则采用分治的方式,通过子视图控制器来进行视图管理。 下图所示,代表性地展示了内容视图控制器及其视图之间的关系。

数据管理

在MVC设计模式中,视图控制器作为承担着数据和视图通信的中介角色。通过UIViewController的内置及自定义的属性和方法,可以完成控制器需要承担的任务。如下图所示,为视图控制器与数据、视图的关系示意图。

在实际开发中,我们始终应该清晰地分离视图控制器和数据对象。视图控制器应该扮演逻辑控制的角色,而应该减少数据管理的角色。

为了处理数据管理,很多设计使用了MVVM的设计模式,这种设计添加了一个View Model层来对来自Model层的原始数据进行处理,从而提供给Controller,使其可以直接使用。

生命周期

如下图所示为视图控制器的生命周期,其中与视图view相关的步骤较多。

  • init:
    • init阶段,通常只初始化比较关键的数据,而不会在该阶段对view进行初始化。
  • loadView:
    • loadView阶段,通常只初始化view
  • viewDidLoad
    • 此时view不在为nil,适合创建一些附加的view及控件。
  • viewWillAppear
    • 该阶段阶段view即将被添加到app的window对象之上。一般在view被添加到superview之前,切换动画之前调用。
  • viewDidAppear
    • 一般用户显示后,可以进行动画操作。

如果在视图控制器中添加自定义的视图,则生命周期中还会多出两个步骤:

  • viewWillLayoutSubviews
  • viewDidLayoutSubviews

如下图所示,我分别在loadView和viewDidLoad阶段添加了一个视图,而viewWillLayoutSubviews和viewDidLayoutSubviews均出现在viewWillAppear之后。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
- (void)loadView {
[super loadView];
NSLog(@"%@",NSStringFromSelector(_cmd));

UIButton *button = [[UIButton alloc] initWithFrame:CGRectMake(100, 400, 100, 50)];
button.backgroundColor = [UIColor blueColor];
[button addTarget:self action:@selector(buttonPressed) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:button];
}

- (void)viewDidLoad {
[super viewDidLoad];
NSLog(@"%@",NSStringFromSelector(_cmd));

self.testView = [[TestView alloc] initWithFrame:CGRectMake(100, 100, 200, 200)];
self.testView.backgroundColor = [UIColor grayColor];
[self.view addSubview:self.testView];
}