1 乐库的设置界面
1.1 问题
tableView分为静态(static)和动态(dynamic),之前使用的都是动态的tableView,表视图的有多少分区、有多少行以及每一行显示的内容都不是固定的,都由数据模式来决定。而静态的tableView有多少分区、有多少行以及每一行显示的内容都是固定不变的。
静态tableView应用广泛,常用于各种应用软件的设置界面,本案例使用静态tableView完成乐库的设置界面,如图-1所示:
图-1
1.2 方案
由图-1可以看到该界面的显示内容是固定不变的,不需要根据数据模型而变化,所以该页面是一个静态的tableView。
首先还是创建一个带有导航的TRMusicConfigureTableViewController的表视图控制器,作为根视图控制器,并在TRMusicConfigureTableViewController.m文件中通过tableView的协议方法固定表视图的分区数和行数。
其次在xib文件中将tableView的style设置为Grouped。从对象库中拖拽一个UIView对象到xib中,作为tableView的tableheaderView。在该UIView视图上拖放一个UIImageView对象和四个UIButton对象,并在右边栏的检查器中设置相关的属性。
然后在xib文件中拖放四个UITableViewCell对象,根据界面需求从对象库中拖放控件到cell上面,完成每个cell的界面布局。
最后关联xib中的每个UITableViewCell对象和UIView对象为TRMusicConfigureTableViewController的私有属性,在viewDidLoad方法里面设置tableHeaderView,在tableView的协议方法cellForRowAtIndexPath里面指定每个cell对象。
1.3 步骤
实现此案例需要按照如下步骤进行。
步骤一:创建TRMusicConfigureTableViewController类
创建TRMusicConfigureTableViewController类继承至UITableViewController,然后在TRAppDelegate.m文件的程序入口方法中创建一个带有导航的表视图控制器作为根视图控制器,代码如下所示:
- -(BOOL)application:(UIApplication *)application
- didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
- self.window = [[UIWindow alloc]initWithFrame:[[UIScreen mainScreen] bounds]];
- TRMusicConfigureTableViewController *musicTVC = [[TRMusicConfigureTableViewController alloc]initWithNibName:@"TRMusicConfigureTableViewController" bundle:nil];
- UINavigationController *navi = [[UINavigationController alloc]initWithRootViewController:musicTVC];
- self.window.rootViewController = navi;
- [self.window makeKeyAndVisible];
- return YES;
- }
然后在TRMusicConfigureTableViewController.m文件中通过协议方法确定表视图的分区和行数,由于是静态的tableView所以分区和行数都是固定的,代码如下所示:
- //返回4个分区
- - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
- {
- return 4;
- }
- //确定每个分区的行数
- -(NSInteger)tableView:(UITableView *)tableView
- numberOfRowsInSection:(NSInteger)section
- {
- switch (section) {
- case 0:return 1;
- case 1:return 4;
- case 2:return 1;
- case 3:return 1;
- }
- return 0;
- }
步骤二:创建tableHeaderView视图
选中xib文件中的tableView,在右边栏的第四个检查中将tableView的style设置为Grouped,以分区的形式展示表视图,如图-2所示:
图-2
xib文件中除了可以创建一个视图控制器的视图,还可以创建任何UIView对象,比如一个UITableViewCell对象,xib中的每一个对象都会在viewDidLoad方法完成前被创建。一般情况,xib中只有一个对象是和控制器类的view绑定在一起的,其他对象一般会做为控制器的属性存在。
在这里先创建tableViewHeader视图对象,从对象库中拖拽一个UIView对象到xib文件中,默认的UIView对象是和屏幕一样的大小,并且带有状态栏,由于该UIView对象是tableView的tableHeaderView视图,并不需要和屏幕一样的大小,也不需要有状态栏。所以选中右边栏的第四个检查器,将Size设置为Freeform,StatusBar设置为None,如图-3所示:
图-3
最后从对象库中拖放一个UIImageView对象和四个UIButton对象到UIView对象中,并在右边栏的第四个检查器中设置相关属性,完成tableHeaderView的界面,如图-4所示:
图-4
步骤三:创建UITableViewCell对象
在xib文件中拖放七个UITableViewCell对象,依次按照界面的需求,从对象库中拖放相应的控件到每个cell的contentView中,并在在右边栏的检查器中对各控件的属性进行设置,完成每个cell的界面,如图-5所示:
图-5
这里“定时开关”单元格还有一个UISwitch控件的辅助视图,添加辅助视图首先从对象库中拖拽一个UISwitch对象到xib中,然后选中“定时开关”单元格,在右边栏的第六个检查器中选中accessoryView后面的空心小圆圈往UISwitch控件上拖拽,释放鼠标,:accessoryView后面的空心小圆圈变成实心即表示已将“定时开关”单元格的辅助视图和UISwitch对象关联上,如图-6所示:
图-6
选中UISwitch对象,点击鼠标右键也可以查看,如图-7所示:
图-7
步骤四:关联属性,设置tableHeaderView和每个单元格
选中xib文件的中的headerView视图,将其关联成TRMusicConfigureTableViewController的私有属性headerView,将xib中headerView上的UIImageView视图关联成TRMusicConfigureTableViewController的私有属性userImageView,然后在viewDidLoad方法里面对表视图的tableHeaderView进行设置,代码如下所示:
- @property (weak, nonatomic) IBOutlet UIView *headerView;
- @property (weak, nonatomic) IBOutlet UIImageView *userImageView;
- - (void)viewDidLoad
- {
- [super viewDidLoad];
- self.title = @"更多";
- //设置表视图的表头视图
- self.tableView.tableHeaderView = self.headerView;
- self.userImageView.image = [UIImage imageNamed:@"Rainbow.ico"];
- }
然后将xib中的七个cell视图关联成TRMusicConfigureTableViewController的私有属性,代码如下所示:
- @property (strong, nonatomic) IBOutlet UITableViewCell *myGreenDiamondCell;
- @property (strong, nonatomic) IBOutlet UITableViewCell *settingCell;
- @property (strong, nonatomic) IBOutlet UITableViewCell *packageCell;
- @property (strong, nonatomic) IBOutlet UITableViewCell *qplayCell;
- @property (strong, nonatomic) IBOutlet UITableViewCell *closeCell;
- @property (strong, nonatomic) IBOutlet UITableViewCell *aboutCell;
- @property (strong, nonatomic) IBOutlet UITableViewCell *exitCell;
这里需要注意在关联属性时,属性的设置应该选择strong而不是weak,如果是弱引用在viewDidLoad方法执行完毕xib创建的cell对象没有被任何对象持有,那么出了viewDidLoad方法cell对象就会被释放,后面在协议方法cellForRowAtIndexPath方法里面设置每行cell单元格时得不到cell对象程序就会报错。
最后在协议方法cellForRowAtIndexPath方法里面设定每一行的cell对象,代码如下所示:
- -(UITableViewCell *)tableView:(UITableView *)tableView
- cellForRowAtIndexPath:(NSIndexPath *)indexPath
- {
- if(indexPath.section==0&&indexPath.row==0) {
- return self.myGreenDiamondCell;
- }else if(indexPath.section==1&&indexPath.row==0){
- return self.settingCell;
- }else if(indexPath.section==1&&indexPath.row==1){
- return self.packageCell;
- }else if(indexPath.section==1&&indexPath.row==2){
- return self.qplayCell;
- }else if(indexPath.section==1&&indexPath.row==3){
- return self.closeCell;
- }else if(indexPath.section==2){
- return self.aboutCell;
- }else{
- return self.exitCell;
- }
- }
1.4 完整代码
本案例中,TRAppDelegate.m文件中的完整代码如下所示:
- #import "TRAppDelegate.h"
- #import "TRMusicConfigureTableViewController.h"
- @implementation TRAppDelegate
- -(BOOL)application:(UIApplication *)application
- didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
- {
- self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
- self.window.backgroundColor = [UIColor whiteColor];
- TRMusicConfigureTableViewController *musicTVC = [[TRMusicConfigureTableViewController alloc]initWithNibName:@"TRMusicConfigureTableViewController" bundle:nil];
- UINavigationController *navi = [[UINavigationController alloc]initWithRootViewController:musicTVC];
- self.window.rootViewController = navi;
- [self.window makeKeyAndVisible];
- return YES;
- }
- @end
本案例中,TRMusicConfigureTableViewController.m文件中的完整代码如下所示:
- #import "TRMusicConfigureTableViewController.h"
- @interface TRMusicConfigureTableViewController ()
- @property (strong, nonatomic) IBOutlet UITableViewCell *myGreenDiamondCell;
- @property (strong, nonatomic) IBOutlet UITableViewCell *settingCell;
- @property (strong, nonatomic) IBOutlet UITableViewCell *packageCell;
- @property (strong, nonatomic) IBOutlet UITableViewCell *qplayCell;
- @property (strong, nonatomic) IBOutlet UITableViewCell *closeCell;
- @property (strong, nonatomic) IBOutlet UITableViewCell *aboutCell;
- @property (strong, nonatomic) IBOutlet UITableViewCell *exitCell;
- @property (weak, nonatomic) IBOutlet UIView *headerView;
- @property (weak, nonatomic) IBOutlet UIImageView *userImageView;
- @end
- @implementation TRMusicConfigureTableViewController
- - (void)viewDidLoad
- {
- [super viewDidLoad];
- self.title = @"更多";
- self.tableView.tableHeaderView = self.headerView;
- self.userImageView.image = [UIImage imageNamed:@"Rainbow.ico"];
- }
- - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
- {
- return 4;
- }
- -(NSInteger)tableView:(UITableView *)tableView
- numberOfRowsInSection:(NSInteger)section
- {
- switch (section) {
- case 0:return 1;
- case 1:return 4;
- case 2:return 1;
- case 3:return 1;
- }
- return 0;
- }
- -(UITableViewCell *)tableView:(UITableView *)tableView
- cellForRowAtIndexPath:(NSIndexPath *)indexPath
- {
- if(indexPath.section==0&&indexPath.row==0) return self.myGreenDiamondCell;
- else if(indexPath.section==1&&indexPath.row==0) return self.settingCell;
- else if(indexPath.section==1&&indexPath.row==1) return self.packageCell;
- else if(indexPath.section==1&&indexPath.row==2) return self.qplayCell;
- else if(indexPath.section==1&&indexPath.row==3) return self.closeCell;
- else if(indexPath.section==2) return self.aboutCell;
- else return self.exitCell;
- }
- @end
2 复杂的表视图展示
2.1 问题
之前的案例都是使用的系统提供的UITableViewController类,实现一个表视图控制器管理一个表视图,给一个表视图加载数据。在实际开发中也经常会遇到一个视图控制器管理多个表视图,给多个表视图加载数据的情况,本案例将学习如何使用一个视图控制器管理两个表视图,同时给两个表视图加载数据,如图-8所示:
图-8
2.2 方案
首先创建一个SingleViewApplication项目,然后创建一个带有导航的TRViewController视图控制器作为根视图控制器。在xib文件中的view视图上拖放一个UITableView对象,并关联成TRViewController的属性tableView1,将tableView1的dataSouce和delegate以拉线的形式设置为TRViewController。
其次在xib的界面上拖放一个UISlider对象并关联成TRViewController的方法,主要实现功能是拖动滑块能控制tableView1在界面中的显示第几行单元格。
然后定义一个UITableView私有属性tableView2,并在viewDidLoad方法里面创建该对象,将tableView2的dataSouce和delegate以代码的形式设置为TRViewController,最后将tableView2添加到父视图中。
最后在TRViewController.m文件中通过实现协议方法给tableView1和tableView2加载数据。
2.3 步骤
实现此案例需要按照如下步骤进行。
步骤一:在xib中创建tableView1
首先创建一个SingleViewApplication项目,然后在TRAppDelegate中创建一个带有导航的TRViewController视图控制器作为根视图控制器,代码如下所示:
- -(BOOL)application:(UIApplication *)application
- didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
- {
- self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
- self.window.backgroundColor = [UIColor whiteColor];
- TRViewController *vc = [[TRViewController alloc]initWithNibName:@"TRViewController" bundle:nil];
- UINavigationController *navi = [[UINavigationController alloc]initWithRootViewController:vc];
- self.window.rootViewController = navi;
- [self.window makeKeyAndVisible];
- return YES;
- }
然后在xib文件中的view视图上拖放一个UITableView对象,并关联成TRViewController的私有属性tableView1,代码如下所示:
- @property (weak, nonatomic) IBOutlet UITableView *tableView1;
在xib文件中选中tableView对象,点击右键选择dataSouce和delegate后面的空心小圆圈拖拽到File’s Owner,即将tableView的dataSource和delegate设置为了TRViewController,如图-9所示:
图-9
步骤二:在xib中创UISlider对象
在xib界面上拖拽一个UISlider控件,在右边栏的第四个检查器中将miniMum、maxiMum和current分别设置为0、1和0,然后将slider关联成TRViewController的方法sliderValueChange:,该方法主要实现功能是拖动滑块能控制tableView1在界面中的显示第几行单元格,在方法里面根据slider的value值计算出tableView的contentOffset即可,代码如下所示:
- - (IBAction)sliderValueChange:(UISlider *)sender
- {
- CGFloat height = self.tableView1.contentSize.height;
- CGFloat scrollVal = sender.value * height;
- CGPoint offset = CGPointMake(self.tableView1.contentOffset.x, scrollVal);
- self.tableView1.contentOffset = offset;
- }
步骤三:使用代码创建tableView2对象
首先在TRViewController中定义一个UITableView类型的私有属性,然后在viewDidLoad方法里面创建tableView2对象,根据屏幕和tableView1的位置大小设置tableView2的frame属性,代码如下所示:
- //定义属性tableView2
- @property (strong, nonatomic) UITableView *tableView2;
- //viewDidLoad方法里面创建tableView2对象
- - (void)viewDidLoad
- {
- [super viewDidLoad];
- self.title = @"TableView";
- CGSize screenSize = [[UIScreen mainScreen] bounds].size;
- CGRect frame = CGRectMake(0, self.tableView1.frame.size.height, 320, screenSize.height-self.tableView1.frame.size.height);
- self.tableView2 = [[UITableView alloc]initWithFrame:frame style:UITableViewStylePlain];
- }
然后使用代码将tableView2的dataSource和delegate赋值为TRViewController,代码如下所示:
- - (void)viewDidLoad
- {
- [super viewDidLoad];
- self.title = @"TableView";
- CGSize screenSize = [[UIScreen mainScreen] bounds].size;
- CGRect frame = CGRectMake(0, self.tableView1.frame.size.height, 320, screenSize.height-self.tableView1.frame.size.height);
- self.tableView2 = [[UITableView alloc]initWithFrame:frame style:UITableViewStylePlain];
- //设置tableView2的dataSource和delegate
- self.tableView2.delegate = self;
- self.tableView2.dataSource = self;
- [self.view addSubview:self.tableView2];
- }
步骤四:给tableView1和tableView2加载数据
首先实现协议方法tableView:numberOfRowsInSection:告诉两个tableView分别显示多少行,由于两个tableView在加载数据时都会调用此方法,因此需要在方法里面根据tableView参数做一个判断,判断当前调用此方法的是tableView1还是tableView2,本案例让tableView1显示50行,tableView2显示10行,代码如下所示:
- -(NSInteger)tableView:(UITableView *)tableView
- numberOfRowsInSection:(NSInteger)section
- {
- if (tableView == self.tableView1) {
- return 50;
- }else{
- return 10;
- }
- }
然后在协议方法tableView:cellForRowAtindexPath:里面创建cell对象,同样需要根据tableView参数区分tableView1和tableView2,代码如下所示:
- -(UITableViewCell *)tableView:(UITableView *)tableView
- cellForRowAtIndexPath:(NSIndexPath *)indexPath
- {
- UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"MyCell"];
- if (cell==nil) {
- cell = [[UITableViewCell alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"MyCell"];
- }
- if(tableView == self.tableView1){
- cell.textLabel.text = [NSString stringWithFormat:@"Hello World. %d", indexPath.row];
- }else{
- [cell.contentView setBackgroundColor:[UIColor yellowColor]];
- cell.textLabel.text = @"这是tableView2的数据";
- }
- return cell;
- }
最后实现协议方法tableView:didSelectRowAtIndexPath:,该方法在点击单元格时会被调用,同样的需要根据tableView参数区分tableView1和tableView2,代码如下所示:
- -(void)tableView:(UITableView *)tableView
- didSelectRowAtIndexPath:(NSIndexPath *)indexPath
- {
- if (tableView == self.tableView1) {
- NSLog(@"用户点击了%d行", indexPath.row);
- }else{
- NSLog(@"这是tableView2");
- }
- }
2.4 完整代码
本案例中,TRAppDelegate.m文件中的完整代码如下所示:
- #import "TRAppDelegate.h"
- #import "TRViewController.h"
- @implementation TRAppDelegate
- -(BOOL)application:(UIApplication *)application
- didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
- {
- self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
- self.window.backgroundColor = [UIColor whiteColor];
- TRViewController *vc = [[TRViewController alloc]initWithNibName:@"TRViewController" bundle:nil];
- UINavigationController *navi = [[UINavigationController alloc]initWithRootViewController:vc];
- self.window.rootViewController = navi;
- [self.window makeKeyAndVisible];
- return YES;
- }
- @end
本案例中,TRViewController.m文件中的完整代码如下所示:
- #import "TRViewController.h"
- @interface TRViewController () <UITableViewDataSource, UITableViewDelegate>
- @property (weak, nonatomic) IBOutlet UITableView *tableView1;
- @property (strong, nonatomic) UITableView *tableView2;
- @end
- @implementation TRViewController
- - (void)viewDidLoad
- {
- [super viewDidLoad];
- self.title = @"TableView";
- CGSize screenSize = [[UIScreen mainScreen] bounds].size;
- CGRect frame = CGRectMake(0, self.tableView1.frame.size.height, 320, screenSize.height-self.tableView1.frame.size.height);
- self.tableView2 = [[UITableView alloc]initWithFrame:frame style:UITableViewStylePlain];
- self.tableView2.delegate = self;
- self.tableView2.dataSource = self;
- [self.view addSubview:self.tableView2];
- }
- -(NSInteger)tableView:(UITableView *)tableView
- numberOfRowsInSection:(NSInteger)section
- {
- if (tableView == self.tableView1) {
- return 50;
- }else{
- return 10;
- }
- }
- -(UITableViewCell *)tableView:(UITableView *)tableView
- cellForRowAtIndexPath:(NSIndexPath *)indexPath
- {
- UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"MyCell"];
- if (cell==nil) {
- cell = [[UITableViewCell alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"MyCell"];
- }
- if(tableView == self.tableView1){
- cell.textLabel.text = [NSString stringWithFormat:@"Hello World. %d", indexPath.row];
- }else{
- [cell.contentView setBackgroundColor:[UIColor yellowColor]];
- cell.textLabel.text = @"这是tableView2的数据";
- }
- return cell;
- }
- -(void)tableView:(UITableView *)tableView
- didSelectRowAtIndexPath:(NSIndexPath *)indexPath
- {
- if (tableView == self.tableView1) {
- NSLog(@"用户点击了%d行", indexPath.row);
- }else{
- NSLog(@"这是tableView2");
- }
- }
- - (IBAction)sliderValueChange:(UISlider *)sender
- {
- CGFloat height = self.tableView1.contentSize.height;
- CGFloat scrollVal = sender.value * height;
- CGPoint offset = CGPointMake(self.tableView1.contentOffset.x, scrollVal);
- self.tableView1.contentOffset = offset;
- }
- @end
3 下拉后重新获取模型中数据
3.1 问题
下拉刷新是手机应用经常使用的一种功能,苹果在ios6中推出了下拉刷新控件,本案例通过使用下拉刷新控件实现每次下拉刷新获取当前时间,如图-10所示:
图-10
3.2 方案
UITableViewController有一个refreshControl控件,该控件是一个UIRefreshControl类型,主要为了表视图实现下拉刷新而提供的类,该类型只能用于表视图界面,当设置refreshControl属性之后,表视图控制器会自动将其放置表视图中。
首先同样创建一个SingleViewApplication项目,然后创建一个带有导航的TRTableViewController视图控制器作为根视图控制器。定义一个NSMutableArray类型的私有属性logs用于管理数据源,在viewDidLoad方法中初始化数组logs,并获取当前时间对象添加到数组中。
其次在viewDidLoad方法里面创建一个UIRefreshControl对象refreshControl,将其设置为TRTableViewController的refreshControl属性,通过addTarget:action:forControlEvents:方法给刷新控件添加事件,当下拉刷新时获取新的数据。
然后实现获取新数据的方法,本案例中就是该方法内重新获取当前时间,并将其添加到数据源数组中。
最后在刷新完成之后,表视图更新数据和界面。
3.3 步骤
实现此案例需要按照如下步骤进行。
步骤一:创建TRTableViewController视图控制器
首先创建一个带有导航的TRTableViewController视图控制器作为根视图控制器。在TRTableViewController类中定义一个NSMutableArray类型的私有属性loges用于管理表视图需要显示的数据,本案例中就是管理的每次刷新获取的时间数据,代码如下所示:
- //定义logs属性
- @interface TRTableViewController ()
- @property (nonatomic,strong)NSMutableArray *logs;
- @end
- //在viewDidLoad方法里面初始化数组
- - (void)viewDidLoad {
- [super viewDidLoad];
- //初始化数组和时间
- self.logs = [@[]mutableCopy];
- NSDate *date = [NSDate date];
- [self.logs addObject:date];
- }
然后在TRTableViewController类中实现协议方法,给表视图加载数据,代码如下所示:
- -(NSInteger)tableView:(UITableView *)tableView
- numberOfRowsInSection:(NSInteger)section {
- return self.logs.count;
- }
- -(UITableViewCell *)tableView:(UITableView *)tableView
- cellForRowAtIndexPath:(NSIndexPath *)indexPath {
- static NSString *cellIdentifer = @"Cell";
- UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifer];
- if (!cell) {
- cell = [[UITableViewCell alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIdentifer];
- }
- NSDate *date = self.logs[indexPath.row];
- NSDateFormatter *df = [[NSDateFormatter alloc]init];
- [df setDateFormat:@"yyyy-MM-dd HH:mm:ss"];
- cell.textLabel.text = [df stringFromDate:date];
- return cell;
- }
步骤二:创建UIRefreshControl对象
在viewDidLoad方法里面创建一个UIRefreshControl对象refreshControl,并设置refreshControl的下拉显示标题attributedTitle,这里attributedTitle是一个NSAtrributedString类型的字符串,代码如下所示:
- //创建一个refreshControl对象
- UIRefreshControl *refreshControl = [[UIRefreshControl alloc]init];
- //设置refreshControl的标题
- refreshControl.attributedTitle = [[NSAttributedString alloc]initWithString:@"下拉刷新"];
然后使用addTarget:action:forControlEvents:方法给refreshControl添加事件处理方法,这里的action:参数传递的方法主要功能就是获取新数据,event参数选择UIControlEventValueChanged,代码如下所示:
- [refreshControl addTarget:self action:@selector(refreshTableView) forControlEvents:UIControlEventValueChanged];
最后将创建好的下拉刷新控件对象设置为TRTableViewController的refreshControl属性,代码如下所示:
- //让self.refreshControl指向refreshControl
- self.refreshControl = refreshControl;
步骤三:实现获取新数据方法refreshTableView
在refreshTableView方法的功能主要是获取新数据,在实际的应用开发中这里通常都是发送网络请求或者数据库查询来获取新数据,本案例使用获取当前时间来模拟实现,代码如下所示:
- -(void)refreshTableView{
- //isRefreshing属性主要用于判断是否处于下拉刷新状态
- if (self.refreshControl.isRefreshing) {
- self.refreshControl.attributedTitle = [[NSAttributedString alloc]initWithString:@"加载中......"];
- //模拟获取新数据,重新获取当前时间
- NSDate *date = [NSDate date];
- [self.logs addObject:date];
- //模拟新数据获取完成之后,延迟2秒调用endRefreshData方法
- [self performSelector:@selector(endRefreshData) withObject:nil afterDelay:2];
- }
- }
以上代码中使用performSelector:withObject:afterDelay:方法来延迟2秒调用endRefreshData方法,用于模拟实现数据获取完成之后的操作。
然后实现endRefreshData方法,该方法内部首先让刷新控件结束刷新,然后让tableView更新数据和界面,代码如下所示:
- -(void)endRefreshData{
- //刷新控件结束刷新
- [self.refreshControl endRefreshing];
- //表视图更新数据
- [self.tableView reloadData];
- }
3.4 完整代码
本案例中,TRTableViewController.m文件中的完整代码如下所示:
- #import "TRTableViewController.h"
- @interface TRTableViewController ()
- @property (nonatomic,strong)NSMutableArray *logs;
- @end
- @implementation TRTableViewController
- - (void)viewDidLoad {
- [super viewDidLoad];
- //初始化数组和时间
- self.logs = [@[]mutableCopy];
- NSDate *date = [NSDate date];
- [self.logs addObject:date];
- //创建一个refreshControl对象
- UIRefreshControl *refreshControl = [[UIRefreshControl alloc]init];
- refreshControl.attributedTitle = [[NSAttributedString alloc]initWithString:@"下拉刷新"];
- [refreshControl addTarget:self action:@selector(refreshTableView) forControlEvents:UIControlEventValueChanged];
- //让self.refreshControl指向refreshControl
- self.refreshControl = refreshControl;
- }
- -(void)refreshTableView{
- //isRefreshing属性主要用于判断是否处于下拉刷新状态
- if (self.refreshControl.isRefreshing) {
- self.refreshControl.attributedTitle = [[NSAttributedString alloc]initWithString:@"加载中......"];
- //模拟获取新数据,重新获取当前时间
- NSDate *date = [NSDate date];
- [self.logs addObject:date];
- //模拟新数据获取完成之后,延迟2秒调用endRefreshData方法
- [self performSelector:@selector(endRefreshData) withObject:nil afterDelay:2];
- }
- }
- -(void)endRefreshData{
- //刷新控件结束刷新
- [self.refreshControl endRefreshing];
- //表视图更新数据
- [self.tableView reloadData];
- }
- -(NSInteger)tableView:(UITableView *)tableView
- numberOfRowsInSection:(NSInteger)section {
- return self.logs.count;
- }
- -(UITableViewCell *)tableView:(UITableView *)tableView
- cellForRowAtIndexPath:(NSIndexPath *)indexPath {
- static NSString *cellIdentifer = @"Cell";
- UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifer];
- if (!cell) {
- cell = [[UITableViewCell alloc]initWithStyle:UITableViewCellStyleDefault
- reuseIdentifier:cellIdentifer];
- }
- NSDate *date = self.logs[indexPath.row];
- NSDateFormatter *df = [[NSDateFormatter alloc]init];
- [df setDateFormat:@"yyyy-MM-dd HH:mm:ss"];
- cell.textLabel.text = [df stringFromDate:date];
- return cell;
- }
- @end
4 给友录添加索引条
4.1 问题
索引也是手机应用中经常会用到的功能,当表视图中有大量数据集合时,可以通过索引来辅助查询。本案例模拟系统通讯录,给联系人界面添加索引条,完成效果如图-11所示:
图-11
4.2 方案
首先同样创建一个SingleViewApplication项目,然后创建一个带有导航的TRContactTableViewController视图控制器作为根视图控制器。定义一个NSMutableArray类型的私有属性contacts用于管理数据源,通过setter方法初始化数组,并创建一组用于给表视图展示的模拟数据。
由于索引视图是分区显示数据的,因此需要将contacts里面存放的数据按首字母进行分组,并将每一组数据放入一个新的数组filterContacts,该数组是TRContactTableViewController类的NSMutableArray类型的私有属性。
然后在TRContactTableViewController中实现协议方法告诉表视图需要显示的分区数、行数和显示内容,让表视图以分区形式显示数据。
最后实现添加索引条的协议方法,根据表视图的数据添加索引条,并实现索引条和表视图的对应关系。
4.3 步骤
实现此案例需要按照如下步骤进行。
步骤一:创建表视图项目,定义contacts属性管理数据源
首先使用Xcode创建一个表视图项目,在TRContactTableViewController类中定义一个NSArray类型的私有属性contacts,通过setter方法初始化数组,并创建一组用于给表视图展示的模拟数据,代码如下所示:
- //定义contacts属性
- @interface TRContactTableViewController ()
- @property (nonatomic,strong)NSArray *contacts;
- @end
- //重写setter方法,初始化数组,创建一组模拟数据
- -(NSArray *)contacts {
- if (!_contacts) {
- _contacts= @[@"tarena",@"apple",@"zhangsan",@"lisi",@"wangwu",
- @"zhaoliu",@"chenqi",@"samsung",@"xiaomi",@"iPhone",
- @"iTouch,@"iPad",@"android",@"huawei",@"object-c",@"swift"];
- }
- return _contacts;
- }
由于索引视图是分区显示数据的,因此需要将contacts里面存放的数据按首字母进行分组,所以需要在TRContactTableViewController类中定义一个NSMutableArray类型的私有属性filterContacts用于保存分组后的数据,代码如下所示:
- //定义filterContacts属性
- @interface TRContactTableViewController ()
- @property (nonatomic,strong)NSArray *contacts;
- @property (nonatomic,strong)NSMutableArray *filterContacts;
- @end
- //重写setter方法进行初始化
- -(NSMutableArray *)filterContacts {
- if (!_filterContacts) {
- _filterContacts = [@[]mutableCopy];
- }
- return _filterContacts;
- }
然后在viewDidLoad方法中对self.contacts里面的数据按首字母进行分组并保存,代码如下所示:
- - (void)viewDidLoad {
- [super viewDidLoad];
- self.title = @"联系人";
- //将contacts数组数据源按首字母分组
- for(int i=97;i<123;i++)
- {
- //定义一个NSMutableArray类型的数组subArray,用来保存每一组的数据
- NSMutableArray *subArray = [@[]mutableCopy];
- NSString *string = [NSString stringWithFormat:@"%c",i];
- for(NSString *name in self.contacts)
- {
- if ([name hasPrefix:string]) {
- [subArray addObject:name];
- }
- }
- //如果subArray数组里面有元素存在将其添加到self.filterContacts中
- if (subArray.count) {
- [subArray sortUsingSelector:@selector(compare:)];
- [self.filterContacts addObject:subArray];
- }
- }
- }
步骤二:给表视图加载数据
首先实现协议方法numberOfSectionsInTableView:告诉tableView有多少分区,本案例的表视图的分区数就是filterContacts数组的count,代码如下所示:
- -(NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
- return self.filterContacts.count;
- }
然后实现协议方法numberOfRowsInSection:告诉tableView有每个分区有多少行,代码如下所示:
- -(NSInteger)tableView:(UITableView *)tableView
- numberOfRowsInSection:(NSInteger)section {
- NSMutableArray *subArray = self.filterContacts[section];
- return subArray.count;
- }
最后实现协议方法cellForRowAtIndexPath:创建cell,并设置每个cell需要显示的内容,代码如下所示:
- -(UITableViewCell *)tableView:(UITableView *)tableView
- cellForRowAtIndexPath:(NSIndexPath *)indexPath {
- static NSString *cellIdentifier =@"Cell";
- UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier];
- if (!cell) {
- cell = [[UITableViewCell alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIdentifier];
- }
- NSMutableArray *subArray = self.filterContacts[indexPath.section];
- cell.textLabel.text = subArray[indexPath.row];
- return cell;
- }
运行程序,界面效果如图-12所示:
图-12
步骤三:给表视图添加索引条
首先给每一个分区加上标题,分区的标题应该具有一定的代表性,代表着一组数据,并且会和索引条对应,本案例使用每组数据首字母的大写形式作为分区标题,通过协议方法titleForHeaderInSection:给分区加上标题,代码如下所示:
- -(NSString*)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section
- {
- //获取到每个分区第一个单元格显示的字符串
- NSMutableArray *subArray = self.filterContacts[section];
- NSString *name = subArray[0];
- //截取字符串的第一个字符,即是该分区的title
- NSRange range = {0,1};
- NSString *title = [name substringWithRange:range];
- return [NSString stringWithFormat:@"%@",title];
- }
此时已经完成了对表视图的分区,完成界面如图-13所示:
图-13
接下来通过实现协议方法sectionIndexTitlesForTableView:给tableView添加索引条,该方法需要返回一个NSArray类型的数组,该数组就是索引条上显示的标题,本案例索引条上显示的标题就是分区标题,代码如下所示:
- -(NSArray*)sectionIndexTitlesForTableView:(UITableView *)tableView
- {
- NSMutableArray *titles=[@[]mutableCopy];
- //根据数据源的内容设置索引条显示的字符
- for(NSMutableArray *subArray in self.filterContacts)
- {
- NSString *name = subArray[0];
- NSRange range = {0,1};
- NSString *title = [name substringWithRange:range];
- [titles addObject: [title capitalizedString]];
- }
- return titles;
- }
最后通过协议方法sectionForSectionIndexTitle:atIndex:设置索引条和表视图的对应关系,index参数就是选中索引条上的序号,本案例索引的index和表视图的section是一一对应的关系,所以直接返回index即可,代码如下所示:
- -(NSInteger)tableView:(UITableView *)tableView sectionForSectionIndexTitle:(NSString *)title atIndex:(NSInteger)index
- {
- //点击索引条的第几个字符,表视图就显示第几个分区
- return index;
- }
4.4 完整代码
- #import "TRContactTableViewController.h"
- @interface TRContactTableViewController ()
- @property (nonatomic,strong)NSArray *contacts;
- @property (nonatomic,strong)NSMutableArray *filterContacts;
- @end
- @implementation TRContactTableViewController
- - (void)viewDidLoad {
- [super viewDidLoad];
- self.title = @"联系人";
- //将contacts数组数据源按首字母分组
- for(int i=97;i<123;i++)
- {
- //定义一个NSMutableArray类型的数组subArray,用来保存每一组的数据
- NSMutableArray *subArray = [@[]mutableCopy];
- NSString *string = [NSString stringWithFormat:@"%c",i];
- for(NSString *name in self.contacts)
- {
- if ([name hasPrefix:string]) {
- [subArray addObject:name];
- }
- }
- //如果subArray数组里面有元素存在将其添加到self.filterContacts中
- if (subArray.count) {
- [subArray sortUsingSelector:@selector(compare:)];
- [self.filterContacts addObject:subArray];
- }
- }
- }
- -(NSArray *)contacts {
- if (!_contacts) {
- _contacts = @[@"tarena",@"apple",@"zhangsan",@"lisi",@"wangwu",@"zhaoliu",@"chenqi",@"samsung",@"xiaomi",@"iPhone",@"iTouch",@"iPad",@"android",@"huawei",@"object-c",@"swift"];
- }
- return _contacts;
- }
- -(NSMutableArray *)filterContacts {
- if (!_filterContacts) {
- _filterContacts = [@[]mutableCopy];
- }
- return _filterContacts;
- }
- -(NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
- return self.filterContacts.count;
- }
- -(NSInteger)tableView:(UITableView *)tableView
- numberOfRowsInSection:(NSInteger)section {
- NSMutableArray *subArray = self.filterContacts[section];
- return subArray.count;
- }
- -(UITableViewCell *)tableView:(UITableView *)tableView
- cellForRowAtIndexPath:(NSIndexPath *)indexPath {
- static NSString *cellIdentifier =@"Cell";
- UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier];
- if (!cell) {
- cell = [[UITableViewCell alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIdentifier];
- }
- NSMutableArray *subArray = self.filterContacts[indexPath.section];
- cell.textLabel.text = subArray[indexPath.row];
- return cell;
- }
- //设置section的title
- -(NSString*)tableView:(UITableView *)tableView
- titleForHeaderInSection:(NSInteger)section
- {
- //获取到每个分区第一个单元格显示的字符串
- NSMutableArray *subArray = self.filterContacts[section];
- NSString *name = subArray[0];
- //截取字符串的第一个字符,即是该分区的title
- NSRange range = {0,1};
- NSString *title = [name substringWithRange:range];
- return [NSString stringWithFormat:@"%@",title];
- }
- //设置索引条
- -(NSArray*)sectionIndexTitlesForTableView:(UITableView *)tableView
- {
- NSMutableArray *titles=[@[]mutableCopy];
- //根据数据源的内容设置索引条显示的字符
- for(NSMutableArray *subArray in self.filterContacts)
- {
- NSString *name = subArray[0];
- NSRange range = {0,1};
- NSString *title = [name substringWithRange:range];
- [titles addObject: [title capitalizedString]];
- }
- return titles;
- }
- //设置索引条和tableView的对应关系
- -(NSInteger)tableView:(UITableView *)tableView
- sectionForSectionIndexTitle:(NSString *)title
- atIndex:(NSInteger)index
- {
- //点击索引条的第几个字符,表视图就显示第几个分区
- return index;
- }
- @end
5 行政区域选择
5.1 问题
本案例将学习使用tableView展示多层数据模型,实现行政区域选择功能,如图-14所示:
图-14
5.2 方案
首先同样创建一个SingleViewApplication项目,然后创建一个带有导航的TRAreaTableViewController视图控制器作为根视图控制器。
其次定义一个数据模型类TRArea,该类有两个属性一个是NSString类型的name属性用来存储地区的名字,另一个是NSMutableArray类型的subAreas用来存储子区域的信息。并且在TRArea类中定义一个静态方法demoData,用来创建一组行政区域的模拟数据,该组数据即是表视图需要展示的内容。
然后在TRAreaTableViewController类中定义一个TRArea类型的公开属性,用于保存表视图的数据源,并在TRAppDelegate里面使用TRArea的静态方法demoData进行初始化,并且通过实现协议方法给表视图加载数据,显示区域名称;
最后实现点击单元格推出新页面功能,推出的新页面同样是一个TRAreaTableViewController类型的对象,展示的数据是刚被点击单元格上显示区域的子区域信息(subAreas),如果子区域还存在子区域信息,还可以继续点击推出新页面。
5.3 步骤
实现此案例需要按照如下步骤进行。
步骤一:创建表视图控制器和TRArea类
首先创建一个SingleViewApplication项目,在TRAppDelegate中创建一个带有导航的TRAreaTableViewController视图控制器作为根视图控制器,代码如下所示:
- -(BOOL)application:(UIApplication *)application
- didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
- {
- self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
- self.window.backgroundColor = [UIColor whiteColor];
- TRAreaTableViewController *areaTVC = [[TRAreaTableViewController alloc]initWithNibName:@"TRAreaTableViewController" bundle:nil];
- UINavigationController *navi = [[UINavigationController alloc]initWithRootViewController:areaTVC];
- self.window.rootViewController = navi;
- [self.window makeKeyAndVisible];
- return YES;
- }
然后创建一个TRArea类,并且该定义两个公开属性,一个是NSString类型的name属性用来存储地区的名字,另一个是NSMutableArray类型的subAreas用来存储子区域的信息,代码如下所示:
- @interface TRArea : NSObject
- @property (nonatomic, strong) NSString *name;
- @property (nonatomic, strong) NSArray *subAreas;
- @end
在TRArea类中定义一个静态方法demoData,用来创建一组行政区域的模拟数据,该组数据即是表视图需要展示的内容,代码如下所示:
- + (TRArea *)demoData
- {
- TRArea *jingsong = [[TRArea alloc]init];
- jingsong.name = @"劲松";
- jingsong.subAreas = nil;
- TRArea *panjiayuan = [[TRArea alloc]init];
- panjiayuan.name = @"潘家园";
- panjiayuan.subAreas = nil;
- TRArea *chaoyang = [[TRArea alloc]init];
- chaoyang.name = @"朝阳";
- chaoyang.subAreas = [NSArray arrayWithObjects:jingsong,panjiayuan,nil];
- TRArea *dongcheng = [[TRArea alloc]init];
- dongcheng.name = @"东城";
- dongcheng.subAreas = nil;
- TRArea *haidian = [[TRArea alloc]init];
- haidian.name = @"海淀";
- haidian.subAreas = nil;
- TRArea *beijing = [[TRArea alloc]init];
- beijing.name = @"北京";
- beijing.subAreas = [NSArray arrayWithObjects:chaoyang,dongcheng,haidian, nil];
- TRArea *lujiazui = [[TRArea alloc]init];
- lujiazui.name = @"陆家嘴";
- lujiazui.subAreas = nil;
- TRArea *pudong = [[TRArea alloc]init];
- pudong.name = @"浦东";
- pudong.subAreas = @[lujiazui];
- TRArea *zhabei = [[TRArea alloc]init];
- zhabei.name = @"闸北";
- zhabei.subAreas = nil;
- TRArea *xuhui = [[TRArea alloc]init];
- xuhui.name = @"徐汇";
- xuhui.subAreas = nil;
- TRArea *shanghai = [[TRArea alloc]init];
- shanghai.name = @"上海";
- shanghai.subAreas = [NSArray arrayWithObjects:pudong,zhabei,xuhui,nil];
- TRArea *china = [[TRArea alloc]init];
- china.name = @"中国";
- china.subAreas = [NSArray arrayWithObjects:beijing,shanghai,nil];
- return china;
- }
步骤二:定义TRArea类型的属性并进行初始化
在TRAreaTableViewController类中定义一个TRArea类型的公开属性area,用于保存表视图的数据源,代码如下所示:
- @interface TRAreaTableViewController : UITableViewController
- @property (nonatomic, strong) TRArea *area;
- @end
然后在TRAppDelegate里面使用TRArea的静态方法demoData进行对TRAreaTableViewController的area属性进行初始化,代码如下所示:
- -(BOOL)application:(UIApplication *)application
- didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
- {
- self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
- self.window.backgroundColor = [UIColor whiteColor];
- TRAreaTableViewController *areaTVC = [[TRAreaTableViewController alloc]initWithNibName:@"TRAreaTableViewController" bundle:nil];
- //初始化area
- areaTVC.area = [TRArea demoData];
- UINavigationController *navi = [[UINavigationController alloc]initWithRootViewController:areaTVC];
- self.window.rootViewController = navi;
- [self.window makeKeyAndVisible];
- return YES;
- }
最后在TRAreaTableViewController类中实现协议方法,给表视图加载数据显示self.area.subAreas信息列表,代码如下所示:
- //设置表视图的行数
- -(NSInteger)tableView:(UITableView *)tableView
- numberOfRowsInSection:(NSInteger)section
- {
- return self.area.subAreas.count;
- }
- //设置表视图单元格的显示内容
- -(UITableViewCell *)tableView:(UITableView *)tableView
- cellForRowAtIndexPath:(NSIndexPath *)indexPath
- {
- static NSString *CellIdentifier = @"Cell";
- UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
- if (cell == nil) {
- cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
- }
- TRArea *subarea = self.area.subAreas[indexPath.row];
- cell.textLabel.text = subarea.name;
- if(subarea.subAreas.count){
- cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
- }
- return cell;
- }
步骤三:实现协议方法didSelectRowAtIndexPath:
实现协议方法didSelectRowAtIndexPath:完成点击单元格推出一个新页面功能,这里所推出的新页面同样是一个TRAreaTableViewController类型的对象,展示的数据是被点击单元格所显示区域的subAreas属性所存储的数据,代码如下所示:
- - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
- {
- //获取被点击的区域
- TRArea *subArea = self.area.subAreas[indexPath.row];
- //如果被点击区域并没有子区域则对应单元格不能被点击
- if (!subArea.subAreas.count) {
- UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
- [cell setSelected:NO animated:YES];
- return;
- }
- //创建新的视图控制器对象
- TRAreaTableViewController *areaTVC = [[TRAreaTableViewController alloc]initWithNibName:@"TRAreaTableViewController" bundle:nil];
- //将被点击区域信息传递给新页面
- areaTVC.area = subArea;
- //推出新的页面
- [self.navigationController pushViewController:areaTVC animated:YES];
- }
5.4 完整代码
本案例中,TRAppDelegate.m文件中的完整代码如下所示:
- #import "TRAppDelegate.h"
- #import "TRAreaTableViewController.h"
- @implementation TRAppDelegate
- -(BOOL)application:(UIApplication *)application
- didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
- {
- self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
- // Override point for customization after application launch.
- self.window.backgroundColor = [UIColor whiteColor];
- TRAreaTableViewController *areaTVC = [[TRAreaTableViewController alloc]initWithNibName:@"TRAreaTableViewController" bundle:nil];
- //初始化area属性
- areaTVC.area = [TRArea demoData];
- UINavigationController *navi = [[UINavigationController alloc]initWithRootViewController:areaTVC];
- self.window.rootViewController = navi;
- [self.window makeKeyAndVisible];
- return YES;
- }
- @end
本案例中,TRAreaTableViewController.h文件中的完整代码如下所示
- #import<UIKit/UIKit.h>
- #import "TRArea.h"
- @interface TRAreaTableViewController : UITableViewController
- @property (nonatomic, strong) TRArea *area;
- @end
本案例中,TRAreaTableViewController.m文件中的完整代码如下所示:
- #import "TRAreaTableViewController.h"
- @implementation TRAreaTableViewController
- - (void)viewDidLoad
- {
- [super viewDidLoad];
- self.title = self.area.name;
- }
- -(NSInteger)tableView:(UITableView *)tableView
- numberOfRowsInSection:(NSInteger)section
- {
- return self.area.subAreas.count;
- }
- -(UITableViewCell *)tableView:(UITableView *)tableView
- cellForRowAtIndexPath:(NSIndexPath *)indexPath
- {
- static NSString *CellIdentifier = @"Cell";
- UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
- if (cell == nil) {
- cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
- }
- TRArea *area = self.area.subAreas[indexPath.row];
- cell.textLabel.text = area.name;
- return cell;
- }
- -(void)tableView:(UITableView *)tableView
- didSelectRowAtIndexPath:(NSIndexPath *)indexPath
- {
- //获取被点击的区域
- TRArea *subArea = self.area.subAreas[indexPath.row];
- //如果被点击区域并没有子区域则对应单元格不能被点击
- if (!subArea.subAreas.count) {
- UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
- [cell setSelected:NO animated:YES];
- return;
- }
- //创建新的视图控制器对象
- TRAreaTableViewController *areaTVC = [[TRAreaTableViewController alloc]initWithNibName:@"TRAreaTableViewController" bundle:nil];
- //将被点击区域信息传递给新页面
- areaTVC.area = subArea;
- //推出新的页面
- [self.navigationController pushViewController:areaTVC animated:YES];
- }
- @end
本案例中,TRArea.h文件中的完整代码如下所示
- #import<Foundation/Foundation.h>
- @interface TRArea : NSObject
- @property (nonatomic, strong) NSString *name;
- @property (nonatomic, strong) NSArray *subAreas;
- + (TRArea *)demoData;
- @end
本案例中,TRAreaTableViewController.m文件中的完整代码如下所示:
- #import "TRArea.h"
- @implementation TRArea
- + (TRArea *)demoData
- {
- TRArea *jingsong = [[TRArea alloc]init];
- jingsong.name = @"劲松";
- jingsong.subAreas = nil;
- TRArea *panjiayuan = [[TRArea alloc]init];
- panjiayuan.name = @"潘家园";
- panjiayuan.subAreas = nil;
- TRArea *chaoyang = [[TRArea alloc]init];
- chaoyang.name = @"朝阳";
- chaoyang.subAreas = [NSArray arrayWithObjects:jingsong,panjiayuan,nil];
- TRArea *dongcheng = [[TRArea alloc]init];
- dongcheng.name = @"东城";
- dongcheng.subAreas = nil;
- TRArea *haidian = [[TRArea alloc]init];
- haidian.name = @"海淀";
- haidian.subAreas = nil;
- TRArea *beijing = [[TRArea alloc]init];
- beijing.name = @"北京";
- beijing.subAreas = [NSArray arrayWithObjects:chaoyang,dongcheng,haidian, nil];
- TRArea *lujiazui = [[TRArea alloc]init];
- lujiazui.name = @"陆家嘴";
- lujiazui.subAreas = nil;
- TRArea *pudong = [[TRArea alloc]init];
- pudong.name = @"浦东";
- pudong.subAreas = @[lujiazui];
- TRArea *zhabei = [[TRArea alloc]init];
- zhabei.name = @"闸北";
- zhabei.subAreas = nil;
- TRArea *xuhui = [[TRArea alloc]init];
- xuhui.name = @"徐汇";
- xuhui.subAreas = nil;
- TRArea *shanghai = [[TRArea alloc]init];
- shanghai.name = @"上海";
- shanghai.subAreas = [NSArray arrayWithObjects:pudong,zhabei,xuhui,nil];
- TRArea *china = [[TRArea alloc]init];
- china.name = @"中国";
- china.subAreas = [NSArray arrayWithObjects:beijing,shanghai,nil];
- return china;
- }
- @end
6 友录中的搜索功能
6.1 问题
当表视图中的数据量比较大的时候,找到指定数据就会比较麻烦,因此ios提供一个搜索框控件UISearchBar,一般情况下搜索框都置于表头。本案例给通讯录的联系人界面添加搜索框,实现效果如图-15所示:
图-15
6.2 方案
搜索框是一个比较复杂的控件,它有一个UISearchBarDelegate的委托协议。搜索框通常都和UISearchDisplayController配套使用,UISearchDisplayController是用来管理搜索框并显示搜索结果的视图控制器,事件处理由UISearchDisplayDelegate协议的委托对象delegate来管理。
UISearchDisplayController除了delegate,另外还有两个委托对象searchResultsDataSource和searchResultsDelegate,分别要求遵守UITableViewDataSource协议和UITableViewDelegate协议,这两个委托对象是用来给搜索结果视图加载数据的。
首先同样的创建一个表视图控制器的项目,表视图控制器类命名为TRContactTableViewController,该类有一个NSArray类型的属性contacts用于管理联系人数据源,重写setter方法对该属性进行初始化,并创建一组联系人的模拟数据。
其次在TRContactTableViewController中定义一个NSMutableArray类型的私有属性filterContacts用于存储搜索之后的联系人信息,并在viewDidLoad方法里面进行赋值,当页面刚加载成功时并没有进行任何搜索,filterContacts存储的数据和contacts的数据一样。
然后在xib文件中拖拽一个带有SearchDisplayController的SearchBar控件,将searchBar关联成TRContactTableViewController的属性searchBar。
最后在TRContactTableViewController类中将tableHeaderView设置为searchBar,然后实现协议方法给表视图加载数据。
6.3 步骤
实现此案例需要按照如下步骤进行。
步骤一:创建表视图控制器,并初始化数据源
同以往的案例一样创建一个表视图控制类TRContactTableViewController,并且定义一个NSArray类型的属性contacts用于管理联系人数据,代码如下所示:
- @property (strong,nonatomic) NSArray *contacts;
其次重写setter方法初始化contacts属性,并创建一组联系人模拟数据,代码如下所示:
- -(NSArray *)contacts {
- if (!_contacts) {
- NSArray *tempArray = @[@"tarena",@"apple",@"zhangsan",@"lisi",@"wangwu",@"zhaoliu",@"chenqi",@"samsung",@"xiaomi",@"iPhone",@"iTouch",@"iPad",@"android",@"huawei",@"object-c",@"swift"];
- _contacts = [tempArray sortedArrayUsingSelector:@selector(compare:)];
- }
- return _contacts;
- }
然后在TRContactTableViewController中定义一个NSMutableArray类型的私有属性filterContacts用于存储搜索之后的联系人信息,并在viewDidLoad方法里面进行赋值,当页面刚加载成功时并没有进行任何搜索,filterContacts存储的数据和contacts的数据一样,代码如下所示:
- //定义属性
- @property (strong,nonatomic)NSMutableArray *filterContacts;
- //在viewDidLoad方法里面进行赋值
- - (void)viewDidLoad {
- [super viewDidLoad];
- self.title = @"联系人";
- self.filterContacts = [self.contacts mutableCopy];
- }
步骤二:创建搜索框
在xib文件中拖拽一个带有SearchDisplayController的SearchBar控件,注意这里不是UISearchBar控件,而是SearchBar and SearchDisplayController,如图-16所示:
图-16
这样做的好处就是可以把UISearchDisplayController也添加到搜索框,并且将委托和数据源连线完毕,如图-17所示:
图-17
然后searchBar关联成TRContactTableViewController的属性searchBar,代码如下所示:
- @property (weak, nonatomic) IBOutlet UISearchBar *searchBar;
最后在TRContactTableViewController.m文件中的viewDidLoad方法里面将self.searchBar设置为tableViewHeaderView,代码如下所示:
- - (void)viewDidLoad {
- [super viewDidLoad];
- self.title = @"联系人";
- self.filterContacts = [self.contacts mutableCopy];
- self.tableView.tableHeaderView = self.searchBar;
- }
步骤三:实现协议方法
首先实现协议方法,给tableView加载数据,代码如下所示:
- -(NSInteger)tableView:(UITableView *)tableView
- numberOfRowsInSection:(NSInteger)section {
- return self.filterContacts.count;
- }
- -(UITableViewCell *)tableView:(UITableView *)tableView
- cellForRowAtIndexPath:(NSIndexPath *)indexPath {
- static NSString *cellIdentifier = @"Cell";
- UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier];
- if (!cell) {
- cell = [[UITableViewCell alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIdentifier];
- }
- cell.textLabel.text = self.filterContacts[indexPath.row];
- [cell setAccessoryType:UITableViewCellAccessoryDisclosureIndicator];
- return cell;
- }
然后实现UISearchDisplayDelegate协议里面的方法searchDisplayController:shouldReloadTableForSearchString:,该方法会根据新的数据源重新给表视图加载数据,代码如下所示:
- -(BOOL)searchDisplayController:(UISearchDisplayController *)controller
- shouldReloadTableForSearchString:(NSString *)searchString{
- //根据输入的字符串筛选数据
- NSMutableArray *tempArray = [@[]mutableCopy];
- for (NSString *name in self.contacts) {
- if ([name hasPrefix:searchString]) {
- [tempArray addObject:name];
- }
- }
- //更新self.filterContacts存储的联系人信息
- self.filterContacts = tempArray;
- return YES;
- }
最后实现UISearchBarDelegate协议的方法searchBarCancelButtonClicked:,该方法会在点击搜索框的取消按钮时被调用,本案例中点击取消即展示所有联系人信息,代码如下所示:
- - (void)searchBarCancelButtonClicked:(UISearchBar *)searchBar {
- self.filterContacts = [self.contacts mutableCopy];
- [self.tableView reloadData];
- }
运行程序最后实现效果如图-18所示:
图-18
6.4 完整代码
本案例中,TRAppDelegate.m文件中的完整代码如下所示:
- #import "TRAppDelegate.h"
- #import "TRSearchTableViewController.h"
- @implementation TRAppDelegate
- -(BOOL)application:(UIApplication *)application
- didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
- self.window = [[UIWindow alloc]initWithFrame:[[UIScreen mainScreen]bounds]];
- UINavigationController *navi = [[UINavigationController alloc]initWithRootViewController:[[TRSearchTableViewController alloc]initWithNibName:@"TRSearchTableViewController" bundle:nil]];
- self.window.rootViewController = navi;
- [self.window makeKeyAndVisible];
- return YES;
- }
- @end
本案例中,TRNewsTableViewController.h文件中的完整代码如下所示:
- #import "TRSearchTableViewController.h"
- @interface TRSearchTableViewController ()<UISearchBarDelegate,UISearchDisplayDelegate>
- @property (weak, nonatomic) IBOutlet UISearchBar *searchBar;
- @property (strong,nonatomic) NSArray *contacts;
- @property (strong,nonatomic)NSMutableArray *filterContacts;
- @end
- @implementation TRSearchTableViewController
- - (void)viewDidLoad {
- [super viewDidLoad];
- self.title = @"联系人";
- self.tableView.tableHeaderView = self.searchBar;
- self.filterContacts = [self.contacts mutableCopy];
- }
- -(NSArray *)contacts {
- if (!_contacts) {
- NSArray *tempArray = @[@"tarena",@"apple",@"zhangsan",@"lisi",@"wangwu",@"zhaoliu",@"chenqi",@"samsung",@"xiaomi",@"iPhone",@"iTouch",@"iPad",@"android",@"huawei",@"object-c",@"swift"];
- _contacts = [tempArray sortedArrayUsingSelector:@selector(compare:)];
- }
- return _contacts;
- }
- - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
- return self.filterContacts.count;
- }
- - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
- static NSString *cellIdentifier = @"Cell";
- UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier];
- if (!cell) {
- cell = [[UITableViewCell alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIdentifier];
- }
- cell.textLabel.text = self.filterContacts[indexPath.row];
- [cell setAccessoryType:UITableViewCellAccessoryDisclosureIndicator];
- return cell;
- }
- //根据输入的字符串刷新tableView
- - (BOOL)searchDisplayController:(UISearchDisplayController *)controller shouldReloadTableForSearchString:(NSString *)searchString{
- //根据输入的字符串筛选数据
- NSMutableArray *tempArray = [@[]mutableCopy];
- for (NSString *name in self.contacts) {
- if ([name hasPrefix:searchString]) {
- [tempArray addObject:name];
- }
- }
- //更新self.filterContacts存储的联系人信息
- self.filterContacts = tempArray;
- return YES;
- }
- //当点击searchBar的cancel按钮时退出搜索,展示所有联系人信息
- - (void)searchBarCancelButtonClicked:(UISearchBar *)searchBar {
- self.filterContacts = [self.contacts mutableCopy];
- [self.tableView reloadData];
- }
- @end