iOS 控制器 iOS用户界面 图形图像及动画 iOS 控制器 内容概述 控制器的基类UIViewController 标签控制器UITabBarController 导航控制器UINavigationController 分割控制器UISplitViewController 悬浮控制器UIPopoverController 表格控制器UITableViewController iOS程序设计遵循严格的MVC设计模式,在上一章中我们讲述了大量的View视图组件的用法,这一章我们将详细讲述Controller控制器的用法。控制器关联了Model和View是MVC设计模式中最重要的部分。下面是本章将要讲述到的核心类。 表1.1 本章将要讲述的核心类 类名称 类描述 UIViewController 其它控制器的父类,为其他控制器了提供了公共的属性和行为方法 UITabBarController 容器控制器类,可以管理多个并列关系的其他控制器 UINavigationController 导航控制器类,可以在多个控制器之间实现导航 UISplitViewController 用在iPad开发中,实现具有Master/Detail关系的控制器界面分割 UIPopoverController 悬浮控制器,可以实现具有弹出对话框效果的控制器 UITableViewController 表格控制器,iOS开发中最常用的控制器 1.1 UIViewController UIViewController是其它控制器类的父类,提供了公共的属性和行为方法。一般我们很少直接使用UIViewController,而是继承该类,实现需要的功能。UIViewController的功能总结为以下三点: 管理View视图及视图之间的层次关系。 和Model通信获得要展现的数据。 管理其他控制器及和其他控制器直接的通信和导航。 1.2 UITabBarController UITabBarController 继承UIViewController是一个特殊的控制器类,该类管理多个具有并列关系的控制器类。多个标签位于屏幕的下方,每个标签关联一个自定义的控制器类。我们可以通过两种方法来实现一个UITabBarController,一使用XCode模板,二自定义。首先我们看使用XCode模板如何创建UITabBarController。实现步骤如下: 打开XCode,在模板界面中选择"Tabbed Application",如下图所示。 点击"Next"进入项目设置界面,简单输入项目名称和其他选项,项目创建成功。 这样我们创建了一个具有两个Tab标签的程序。程序运行结果如下图所示。 另外,我们也可以自定义创建UITabBarController程序,其实大部分时候我们是需要自己来定制的,即使是使用模板创建的,我们也需要了解其原理。下面我们来看自定义的实现步骤: 使用XCode模板创建一个空的项目。如下图所示。 序 点击"Next"简单输入项目名称,即可创建一个空的项目。 在代理.h文件中声明UITabBarController属性。 @interface AmakerAppDelegate : UIResponder <UIApplicationDelegate> @property (strong, nonatomic) UIWindow *window; // UITabBarController 属性 @property(strong,nonatomic)UITabBarController *tabBarController; @end 在代理方法didFinishLaunchingWithOptions中实例化UITabBarController。 // 实例化UITabBarController self.tabBarController = [[UITabBarController alloc]init]; 使用XCode模板创建一个类继承UIViewController,并选择创建xib文件。如下图所示。 向项目中添加两张图片。 在刚刚创建的控制器的initWithNibName方法中,为标题设置标题和图片。 - (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil { self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]; if (self) { // 标签标题 self.tabBarItem.title=@"Tab1"; // 标签图片 self.tabBarItem.image = [UIImage imageNamed:@"first.png"]; } return self; } 在代理类中声明该控制器属性,并实例化。 #import <UIKit/UIKit.h> #import "AmakerTab1ViewController.h" @interface AmakerAppDelegate : UIResponder <UIApplicationDelegate> @property (strong, nonatomic) UIWindow *window; // UITabBarController 属性 @property(strong,nonatomic)UITabBarController *tabBarController; @property(strong,nonatomic)AmakerTab1ViewController *tab1Controller; @end - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; // 实例化UITabBarController self.tabBarController = [[UITabBarController alloc]init]; // 实例化AmakerTab1ViewController self.tab1Controller = [[AmakerTab1ViewController alloc]initWithNibName:@"AmakerTab1ViewController" bundle:nil]; // 设置Tab属性 self.tabBarController.viewControllers=@[self.tab1Controller]; self.window.rootViewController = self.tabBarController; self.window.backgroundColor = [UIColor whiteColor]; [self.window makeKeyAndVisible]; return YES; } 按照上述步骤重复创建第二个控制器。 程序运行效果如下所示。 另外,我们还可以为UITabBarController指定代理,来监控标签的选择状态。该代理类为UITabBarControllerDelegate。实现代理步骤如下: 在.h文件中实现代理协议。 @interface AmakerAppDelegate : UIResponder <UIApplicationDelegate,UITabBarControllerDelegate> 设置代理属性。 // 设置代理 self.tabBarController.delegate = self; 覆盖代理方法。 // 代理方法 - (void)tabBarController:(UITabBarController *)tabBarController didSelectViewController:(UIViewController *)viewController{ UIAlertView *alert = [[UIAlertView alloc]initWithTitle:@"Test Tab" message:nil delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil, nil]; [alert show]; } 当我们选中某个Tab时,显示一个对话框。 1.3 UINavigationController UINavigationController,顾名思义是一个导航控制器,可以实现多个控制器之间的导航。UINavigationController使用栈存储结构来管理控制器。栈具有后进先出的特性。iOS程序大量使用UINavigationController来管理控制器之间的导航,例如,系统中的Settings应用程序。如下图所示。 一般导航控制器都有导航条,导航条上有返回上级按钮和其他功能按钮,当我们想导航到某个控制器就执行入栈操作,当我们显示上一级控制器时执行出栈操作。 下面我们通过一个案例来演示UINavigationController的用法,该案例是在第一个控制器上添加一个按钮,点击按钮导航到第二个控制器,第二个控制器上也有一个按钮点击该按钮导航到上一个控制器。程序实现步骤如下: 在代理.h中添加UINavigationController属性。 #import <UIKit/UIKit.h> @class AmakerViewController; @interface AmakerAppDelegate : UIResponder <UIApplicationDelegate> @property (strong, nonatomic) UIWindow *window; @property (strong, nonatomic) AmakerViewController *viewController; @property (strong, nonatomic) UINavigationController *navigationController; @end 在代理方法中实例化UINavigationController,并指定根视图控制器。 - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; self.viewController = [[AmakerViewController alloc] initWithNibName:@"AmakerViewController" bundle:nil]; // 实例化导航控制器,并指定根视图控制器 self.navigationController = [[UINavigationController alloc]initWithRootViewController:self.viewController]; self.window.rootViewController = self.navigationController; [self.window makeKeyAndVisible]; return YES; } 在第一个控制器界面只添加一个按钮,并添加点击事件方法,在该方法中实现导航。 - (IBAction)next:(id)sender { // 实例化第二个视图控制器 self.secondViewController = [[AmakerSecondViewController alloc]initWithNibName:@"AmakerSecondViewController" bundle:nil]; // 入栈实现导航 [self.navigationController pushViewController:self.secondViewController animated:YES]; } 在第二个控制器界面上添加一个按钮,并添加点击事件方法,在该方法中返回第一个控制器。 - (IBAction)back:(id)sender { // 出栈,导航到上一个控制器 [self.navigationController popViewControllerAnimated:YES]; } 程序运行结果如下所示。 另外,我们还可以为当前控制器添加导航按钮和标题。代码如下: - (void)viewDidLoad { [super viewDidLoad]; // 左导航按钮 UIBarButtonItem *leftBtn = [[UIBarButtonItem alloc]initWithTitle:@"Left" style:UIBarButtonItemStylePlain target:self action:nil]; // 设置左导航按钮 self.navigationItem.leftBarButtonItem = leftBtn; // 右导航按钮 UIBarButtonItem *rightBtn = [[UIBarButtonItem alloc]initWithTitle:@"Right" style:UIBarButtonItemStylePlain target:self action:nil]; // 设置右导航按钮 self.navigationItem.rightBarButtonItem = rightBtn; // 设置导航标题 self.navigationItem.title = @"First"; } 程序运行结果如下所示。 1.4 UISplitViewController UISplitViewController 是一个分割视图控制器,用来分割较大屏幕,一般应用在iPad等大屏幕设置的应用程序设计中。在设备横屏状态下将屏幕分为左右两个控制器来显示内容。由于UISplitViewController创建过程相对复杂,XCode提供了创建模板。下面我们来演示如何使用XCode模板创建UISplitViewController应用。创建步骤如下: 使用模板,选择"Master-Detail Application"。 点击"Next",设置项目信息。 程序运行结果如下所示。 为了进一步了解使用XCode创建UISplitViewController的细节,下面我们来手动创建一个UISplitViewController。实现步骤如下: 创建一个空项目,并创建两个控制器,分别是左边要显示的控制器和右边要显示控制器。 在代理头文件中声明UISplitViewController属性,和其他两个控制器的属性以及导航控制器。 @interface AmakerAppDelegate : UIResponder <UIApplicationDelegate> @property (strong, nonatomic) UIWindow *window; // UISplitViewController属性 @property (strong,nonatomic) UISplitViewController *splitViewController; // 左边控制器 @property(strong,nonatomic)MasterViewController *masterViewController; // 右边控制器 @property(strong,nonatomic)DetailViewController *detailViewController; // 左边导航控制器 @property(strong,nonatomic)UINavigationController *masterNavigationController; @end 在代理实现方法中实例化UISplitViewController和左右控制器及导航控制器,重要的是要设置UISplitViewController的viewControllers属性。 - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; // Override point for customization after application launch. // 实例化UISplitViewController self.splitViewController = [[UISplitViewController alloc]init]; // 实例化左边控制器 self.masterViewController = [[MasterViewController alloc]initWithNibName:@"MasterViewController" bundle:nil]; // 实例化导航控制器 self.masterNavigationController = [[UINavigationController alloc]initWithRootViewController:self.masterViewController]; // 实例化右边控制器 self.detailViewController = [[DetailViewController alloc]initWithNibName:@"DetailViewController" bundle:nil]; // 为UISplitViewController设置viewController属性 self.splitViewController.viewControllers = @[self.masterNavigationController,self.detailViewController]; self.window.rootViewController = self.splitViewController; self.window.backgroundColor = [UIColor whiteColor]; [self.window makeKeyAndVisible]; return YES; } 程序运行结果如下所示。 1.5 UIPopoverController UIPopoverController 是一个弹出框控制器,一般用来实现弹出式菜单,该控制器只能使用在iPad设备应用程序中。创建一个UIPopoverController步骤如下: 创建一个项目,在控制器中加入导航控制器,并添加导航按钮。 - (void)viewDidLoad { [super viewDidLoad]; // 导航按钮 self.rightBtn = [[UIBarButtonItem alloc]initWithTitle:@"Show" style:UIBarButtonItemStylePlain target:self action:@selector(show)]; self.navigationItem.rightBarButtonItem = self.rightBtn; } 在导航按钮的点击事件中展示UIPopoverController。 -(void)show{ // 实例化UIPopoverController的内容展示控制器 self.popConentViewController = [[PopoverContentViewController alloc]initWithNibName:@"PopoverContentViewController" bundle:nil]; // 实例化导航控制器 self.popoverController = [[UIPopoverController alloc]initWithContentViewController:self.popConentViewController]; // 显示出来 [self.popoverController presentPopoverFromBarButtonItem:self.rightBtn permittedArrowDirections:UIPopoverArrowDirectionAny animated:YES]; } 这里需要注意的是:1.UIPopoverController的属性名称必须是popoverController,因为他是UIViewController的属性。2.需要设置UIPopoverController的内容展示控制器,这里使用表格控制器(下一章中将介绍)。 程序运行结果如下所示。 1.6 UITableViewController UITableViewController是iOS开发中最常用的控制器类,该类以表格的方式展现数据。UITableViewController类关联了UITableView,使用UITableView视图展现数据。使用时可以直接继承UITableViewController,也可以继承UIViewController且实现UITableViewDataSource和UITableViewDelegate协议。 1.6.1 UITableViewController基本用法 表格视图控制器的基本用法包括,如何绑定数据,设置返回列数据内容,以及代理方法等。下面通过一个案例来演示如何使用UITableViewController展现数据。创建步骤如下所示: 创建一个项目,将继承UIViewController改为UITableViewController。 // 继承UITableViewController @interface AmakerViewController : UITableViewController @end 使用NSArray创建要展示的数据。 // 数据源属性 @property(nonatomic,strong)NSArray *dataSource; // 初始化数据源 self.dataSource = @[@"北京",@"上海",@"天津"]; 覆盖NSTableViewDataSource中的两个方法,分别返回行数和UITableViewCell。 // 表格行数 - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{ return [self.dataSource count]; } // 单元格内容设置 - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{ // 重用标示 static NSString *cid = @"cid"; // 获得重用UITableViewCell UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cid]; // 判断cell是否为空 if (cell==nil) { // 实例化UITableViewCell cell = [[UITableViewCell alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cid]; } // 设置显示的内容 cell.textLabel.text = [self.dataSource objectAtIndex:[indexPath row]]; return cell; } 为UITableView创建连接,如下图所示。 程序运行结果如下所示。 在上述程序中我们使用了UITableViewCellStyleDefault样式,除此之外我们还可以设置其它的样式。下面是iOS SDK 支持的样式。 typedef NS\_ENUM(NSInteger, UITableViewCellStyle) { UITableViewCellStyleDefault, // 默认 UITableViewCellStyleValue1, // 标题+详细 UITableViewCellStyleValue2, // 标题+详细 UITableViewCellStyleSubtitle // 图片+标题+详细 }; 下面我们使用UITableViewCellStyleSubtitle样式来实现一个左边是图片、右边是标题和详细的行样式。代码如下: // 单元格内容设置 - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{ // 重用标示 static NSString *cid = @"cid"; // 获得重用UITableViewCell UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cid]; // 判断cell是否为空 if (cell==nil) { // 实例化UITableViewCell // cell = [[UITableViewCell alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cid]; cell = [[UITableViewCell alloc]initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:cid]; } // 设置显示的标题内容 cell.textLabel.text = [self.dataSource objectAtIndex:[indexPath row]]; // 详细内容 cell.detailTextLabel.text = @"My Detail..."; // 图片 cell.imageView.image = [UIImage imageNamed:@"test.jpg"]; return cell; } 程序运行结果如下: 另外,我们还可以覆盖UITableViewDelegate的heightForRowAtIndexPath方法设置行高。代码如下: // 设置行高 - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath{ return 80; } 还可以响应某行的选择,覆盖UITableViewDelegate的- (void)tableView:(UITableView )tableView didSelectRowAtIndexPath:(NSIndexPath )indexPath方法来响应某行的选择。下面代码实现了当某个被选择时显示一个对话框的功能。 // 行选择 - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{ NSString *msg = [self.dataSource objectAtIndex:[indexPath row]]; UIAlertView *alert = [[UIAlertView alloc]initWithTitle:nil message:msg delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil, nil]; [alert show]; } 程序运行结果如下所示。 1.6.2 分区表 分区表大多数应用在系统配置方面,例如,iOS系统应用程序Settings就应用了大量的分区表来分门别类的进行系统设置。如下图所示。 我们也可以自己定义分区表,实现步骤如下所示。 创建应用程序,使控制器实现UITableViewDataSource协议和UITableViewDelegate协议。 @interface AmakerViewController : UIViewController<UITableViewDataSource,UITableViewDelegate> @end 使用NSArray定义两个数据源。 // 每个分区的数据源 self.dataSource = @[@"海淀",@"东城",@"西城"]; self.dataSource2= @[@"南开",@"河东",@"河西",@"塘沽"]; 在界面上添加UITableView组件,并更改样式为Grouped。 实现协议方法设置分区数和每个分区的行数。 // 每个分区行数 - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{ switch (section) { case 0: return [self.dataSource count]; case 1: return [self.dataSource2 count]; default: break; } return 0; } // 分区数 - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView{ return 2; } 设置分区标题。 // 分区标题 - (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section{ switch (section) { case 0: return @"北京市"; case 1: return @"天津市"; default: break; } return nil; } 设置表单元格。 - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{ static NSString *cid = @"cid"; UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cid]; if (cell==nil) { cell = [[UITableViewCell alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cid]; } switch (indexPath.section) { case 0: cell.textLabel.text = [self.dataSource objectAtIndex:[indexPath row]]; break; case 1: cell.textLabel.text = [self.dataSource2 objectAtIndex:[indexPath row]]; break; default: break; } return cell; } 程序运行结果如下所示。 1.6.3 自定义表单元格 虽然iOS SDK 提供了很多种单元格样式,但是大部分时间我们还是需要来定义的。自定义单元格需要如下步骤: 创建项目,实现UITableViewDataSource和UITableViewDelegate协议。 @interface AmakerViewController : UIViewController<UITableViewDataSource,UITableViewDelegate> @end 在界面上添加UITableView,并设置协议连接。 自定义单元格类,继承UITableViewCell,并定义要显示数据的属性。 @interface CustomCell : UITableViewCell // 图片 @property (strong, nonatomic) IBOutlet UIImageView *img; // 标题 @property (strong, nonatomic) IBOutlet UILabel *title; // 详细 @property (strong, nonatomic) IBOutlet UILabel *detail; @end 自定义一个xib文件,在界面添加UIImageView和两个UILabel,设置关联类为CustomCell。 覆盖数据源的方法,从自定义的xib文件中获得单元格,并设置属性。 // 分区行数 - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{ return [self.dataSource count]; } // 返回单元格 - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{ static NSString *cid = @"cid"; CustomCell *cell = [tableView dequeueReusableCellWithIdentifier:cid]; if (cell==nil) { // 从自定义xib文件加载表格单元格 NSBundle *bundle = [NSBundle mainBundle]; NSArray *views = [bundle loadNibNamed:@"CustomCell" owner:self options:nil]; cell = [views objectAtIndex:0]; } // 设置单元格属性 cell.title.text = [self.dataSource objectAtIndex:[indexPath row]]; cell.detail.text = @"This is detail..."; cell.img.image = [UIImage imageNamed:@"test.jpg"]; return cell; } // 设置单元格行高 - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath{ return 100; } 程序运行结果如下所示。 1.6.4 编辑表数据 有时候我们的数据来源于数据库,需要通过界面UITableView对数据进行维护,例如,添加、删除等操作。下面的案例在导航栏左右各放置一个按钮,左边按钮执行添加操作,右边按钮执行删除操作。实现步骤如下所示。 创建项目,实现数据源和代理协议。 @interface AmakerViewController : UIViewController<UITableViewDataSource,UITableViewDelegate> @property (strong, nonatomic) IBOutlet UITableView *tableView; @end 在界面上添加UITableView视图,并设置连接。 实现数据源方法。 // 每个分区的行数 - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{ return [self.dataSource count]; } // 单元格 - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{ static NSString *cid = @"cid"; UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cid]; if (cell==nil) { cell = [[UITableViewCell alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cid]; } cell.textLabel.text = [self.dataSource objectAtIndex:[indexPath row]]; return cell; } 在viewDidload方法中实例化数据源和导航按钮。 - (void)viewDidLoad { [super viewDidLoad]; // 实例化数据源 self.dataSource = [NSMutableArray arrayWithCapacity:5]; // 添加数据 [self.dataSource addObject:@"北京"]; [self.dataSource addObject:@"天津"]; [self.dataSource addObject:@"上海"]; // 导航左边按钮 self.leftBtn = [[UIBarButtonItem alloc]initWithTitle:@"Add" style:UIBarButtonItemStylePlain target:self action:@selector(add)]; // 导航右边按钮 self.rightBtn = [[UIBarButtonItem alloc]initWithTitle:@"Delete" style:UIBarButtonItemStylePlain target:self action:@selector(del)]; self.navigationItem.leftBarButtonItem = self.leftBtn; self.navigationItem.rightBarButtonItem = self.rightBtn; } 实现添加方法,添加方法是将数据添加到数据源,并重新加载UITableView。 // 添加方法 -(void)add{ [self.dataSource addObject:@"Temp"]; [self.tableView reloadData]; } 实现删除方法,删除方法需要改变UITableView的编辑状态,并且在提交编辑方法中实现删除。 // 删除方法 -(void)del{ if (!self.tableView.editing) { self.rightBtn.title = @"Done"; self.tableView.editing = YES; }else{ self.rightBtn.title = @"Del"; self.tableView.editing = NO; } } // 提交编辑状态,删除数据 - (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath{ [self.dataSource removeObjectAtIndex:[indexPath row]]; [self.tableView reloadData]; } 程序运行结果如下所示。 iOS用户界面 图形图像及动画