UITableViewController子控制器的使用方法

在上一篇文章中 我们讲了通过整合tableview的代理,来达到代码复用,减少控制器代码量的效果,今天我们接着来讲另外的一些方法来减轻控制器代码量,整合封装,提高代码的可读性。

UITableViewController的使用

TableView在iOS应用程序中是非常通用的组件,Apple为我们提供了tableview专属的view controller类UITableViewController。Table view controller实现了一些非常有用的特性,来避免一遍又一遍的写那些死板的代码!

但是,我们经常会遇到这样一个问题,table view controller 只限于管理一个全屏展示的table view。大多数情况下,我们想达到的效果就是这样,但是如果不是呢,我们就只能使用通常的控制器,其实还有其他的方法来达到使用table view controller 的目的。

UITableViewController的特性

Table view controllers 会在第一次显示 table view 的时候帮你加载其数据。另外,它还会帮你切换 table view 的编辑模式、响应键盘通知、以及一些小任务,比如闪现侧边的滑动提示条和清除选中时的背景色。为了让这些特性生效,当你在子类中覆写类似 viewWillAppear: 或者 viewDidAppear: 等事件方法时,需要调用 super 版本。

Table view controllers 相对于标准 view controllers 的一个特别的好处是它支持 Apple 实现的“下拉刷新”。目前,文档中唯一的使用 UIRefreshControl 的方式就是通过 table view controller ,虽然通过努力在其他地方也能让它工作(例如直接将UIRefreshControl直接addSubView到tableview),但很可能在下一次 iOS 更新的时候就不行了。

这些要素加一起,为我们提供了大部分 Apple 所定义的标准 table view 交互行为,如果你的应用恰好符合这些标准,那么直接使用 table view controllers 来避免写那些死板的代码是个很好的方法。

UITableViewController的限制

Table view controllers 的 view 属性永远都是一个 table view。如果你稍后决定在 table view 旁边显示一些东西(比如一个地图,一个小的按钮),如果不依赖于那些奇怪的 hacks,估计就没什么办法了。

如果你是用代码或 .xib 文件来定义的界面,那么迁移到一个标准 view controller 将会非常简单。但是如果你使用了 storyboards,那么这个过程要多包含几个步骤。除非重新创建,否则你并不能在 storyboards 中将 table view controller 改成一个标准的 view controller。这意味着你必须将所有内容拷贝到新的 view controller,然后再重新连接一遍。

最后,你需要把迁移后丢失的 table view controller 的特性给补回来。大多数都是 viewWillAppear: 或 viewDidAppear: 中简单的一条语句。切换编辑模式需要实现一个 action 方法,用来切换 table view 的 editing 属性。大多数工作来自重新创建对键盘的支持。

在选择这条路之前,其实还有一个更轻松的选择,它可以通过分离我们需要关心的功能(关注点分离),让你获得额外的好处:

使用 Child View Controllers

和完全抛弃 table view controller 不同,你还可以将它作为 child view controller 添加到其他 view controller 中。这样,parent view controller 在管理其他的你需要的新加的界面元素的同时,table view controller 还可以继续管理它的 table view。

我们来看代码:

我有一个UITableViewController

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38

@interface MyTableViewController ()

@end

@implementation MyTableViewController

- (void)viewDidLoad {
[super viewDidLoad];
[self.tableView registerClass:[UITableViewCell class] forCellReuseIdentifier:@"cell"];
self.refreshControl = [[UIRefreshControl alloc] initWithFrame:CGRectMake(0, 0, 20, 20)];
}

- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];

}

#pragma mark - Table view data source

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {

return 1;
}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {

return 10;
}


- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"cell" forIndexPath:indexPath];

cell.textLabel.text = [NSString stringWithFormat:@"%ld",(long)indexPath.row];

return cell;
}

我想要使用这个UITableViewController来管理我的tableview

然后 我还有一个view controller,在这个view controller中 我既要包含这个table view 还要有其他的view 用上面的加自controller来实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30

@implementation ViewController

- (void)viewDidLoad {
[super viewDidLoad];
[self addMyTableViewController];
[self addOtherView];
}

//添加zicontroller
- (void)addMyTableViewController{
MyTableViewController *vc = [[MyTableViewController alloc] init];
[self addChildViewController:vc];
CGRect frame = self.view.bounds;
frame.size.height = 300;
vc.view.frame = frame;
[self.view addSubview:vc.view];
[vc didMoveToParentViewController:self];
}

- (void)addOtherView{
UIButton *btn = [[UIButton alloc] initWithFrame:CGRectMake(20, 400, 100, 30)];
btn.backgroundColor = [UIColor redColor];
[self.view addSubview:btn];
}


- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
}

如果我们使用了这种方法,那么我们就需要在子controller 和 父 controller之间建立起通信渠道,因为,如果点击了cell,在父类中我需要知道,虽然这样看起来是额外的开销,但是我们的代码变得非常清晰,复用性也更高

我们怎样建立起通信呢,当然最简单的就是使用delegate,这个就比较简单的了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#import <UIKit/UIKit.h>

@protocol MyTableViewControllerDelegate <NSObject>

- (void)didSelectCell;

@end


@interface MyTableViewController : UITableViewController

@property (nonatomic, weak) id<MyTableViewControllerDelegate> delegate;

@end

传递点击事件

1
2
3
4
5
6
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{
[tableView deselectRowAtIndexPath:indexPath animated:YES];
if ([self.delegate respondsToSelector:@selector(didSelectCell)]) {
[self.delegate didSelectCell];
}
}

在父controller中捕获点击事件

1
2
MyTableViewController *vc = [[MyTableViewController alloc] init];
vc.delegate = self;
1
2
3
-(void)didSelectCell{
NSLog(@"点击");
}

在cell内部控制cell的状态

如果我们想自定义cell内部的状态,在点击cell的时候 自定义高亮样式 我们知道有这样的delegate方法,让我们在view controller中操作,但是我们要讲的就是减少view controller的代码

首先我们来看看在view controller 中 怎样来改变

1
2
3
4
5
6
7
8
9
10
11
- (void)tableView:(UITableView *)tableView
didHighlightRowAtIndexPath:(NSIndexPath *)indexPath
{
//高亮时
}

- (void)tableView:(UITableView *)tableView
didUnhighlightRowAtIndexPath:(NSIndexPath *)indexPath
{
//非高亮
}

View 的实现细节和 delegate 的实现交织在一起了。我们应该把这些细节移到 cell 自身中去。

1
2
3
4
5
6
7
8
-(void)setHighlighted:(BOOL)highlighted animated:(BOOL)animated{
[super setHighlighted:highlighted animated:animated];
if (highlighted) {
//高亮时改变cell的状态
}else{
//非高亮时改变
}
}

总结

Table view controllers(以及其他的 controller 对象!)应该在 model 和 view 对象之间扮演协调者和调解者的角色。它不应该关心明显属于 view 层或 model 层的任务。你应该始终记住这点,这样 delegate 和 data source 方法会变得更小巧,最多包含一些简单的样板代码。

这不仅减少了 table view controllers 那样的大小和复杂性,而且还把业务逻辑和 view 的逻辑放到了更合适的地方。Controller 层的里里外外的实现细节都被封装成了简单的 API,最终,它变得更加容易理解,也更利于团队协作。

参考:

https://www.objccn.io/issue-1-2/