整洁的 Table View 代码

作者 : 开心源码 本文共6910个字,预计阅读时间需要18分钟 发布时间: 2022-05-12 共149人阅读

Table view 是 iOS 应用程序中非常通用的组件。许多代码和 table view 都有直接或者间接的关系,随意举几个例子,比方提供数据、升级 table view,控制它的行为以及响应选择事件。在这篇文章中,我们将会展现保持 table view 相关代码的整洁和良好组织的技术。

UITableViewController vs. UIViewController

Apple 提供了 UITableViewController 作为 table views 专属的 view controller 类。Table view controllers 实现了少量非常有用的特性,来帮你避免一遍又一遍地写那些死板的代码!但是话又说回来,table view controller 只限于管理一个全屏展现的 table view。大多数情况下,这就是你想要的,但假如不是,还有其余方法来处理这个问题,就像下面我们展现的那样。

Table View Controllers 的特性

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

Table view controllers 相对于标准 view controllers 的一个特别的好处是它支持 Apple 实现的“下拉刷新”。目前,文档中唯一的使用 UIRefreshControl 的方式就是通过 table view controller ,尽管通过努力在其余地方也能让它工作(见此处),但很可能在下一次 iOS 升级的时候就不行了。

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

Table View Controllers 的限制

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。

- (void)addPhotoDetailsTableView{    DetailsViewController *details = [[DetailsViewController alloc] init];    details.photo = self.photo;    details.delegate = self;    [self addChildViewController:details];    CGRect frame = self.view.bounds;    frame.origin.y = 110;    details.view.frame = frame;    [self.view addSubview:details.view];    [details didMoveToParentViewController:self];}

假如你使用这个处理方案,你就必需在 child view controller 和 parent view controller 之间建立消息传递的渠道。比方,假如客户选择了一个 table view 中的 cell,parent view controller 需要知道这个事件来推入其余 view controller。根据使用习惯,通常最清晰的方式是为这个 table view controller 定义一个 delegate protocol,而后到 parent view controller 中去实现。

@protocol DetailsViewControllerDelegate- (void)didSelectPhotoAttributeWithKey:(NSString *)key;@end@interface PhotoViewController () @end@implementation PhotoViewController// ...- (void)didSelectPhotoAttributeWithKey:(NSString *)key{    DetailViewController *controller = [[DetailViewController alloc] init];    controller.key = key;    [self.navigationController pushViewController:controller animated:YES];}@end

就像你看到的那样,这种结构为 view controller 之间的消息传递带来了额外的开销,但是作为回报,代码封装和分离非常清晰,有更好的复用性。根据实际情况的不同,这既可能让事情变得更简单,也可能会更复杂,需要读者自行斟酌和决定。

分离关注点(Separating Concerns)

当解决 table views 的时候,有许多各种各样的任务,这些任务穿梭于 models,controllers 和 views 之间。为了避免让 view controllers 做所有的事,我们将尽可能地把这些任务划分到合适的地方,这样有利于阅读、维护和测试。

这里形容的技术是文章更轻量的 View Controllers 中的概念的延伸,请参考这篇文章来了解如何重构 data source 和 model 的逻辑。结合 table views,我们来具体看看如何在 view controllers 和 views 之间分离关注点。

搭建 Model 对象和 Cells 之间的桥梁

有时我们需要将想显示的 model 层中的数据传到 view 层中去显示。因为我们同时也希望让 model 和 view 之间明确分离,所以通常把这个任务转移到 table view 的 data source 中去解决:

- (UITableViewCell *)tableView:(UITableView *)tableView         cellForRowAtIndexPath:(NSIndexPath *)indexPath{    PhotoCell *cell = [tableView dequeueReusableCellWithIdentifier:@"PhotoCell"];    Photo *photo = [self itemAtIndexPath:indexPath];    cell.photoTitleLabel.text = photo.name;    NSString* date = [self.dateFormatter stringFromDate:photo.creationDate];    cell.photoDateLabel.text = date;}

但是这样的代码会让 data source 变得混乱,由于它向 data source 暴露了 cell 的设计。最好分解出来,放到 cell 类的一个 category 中。

@implementation PhotoCell (ConfigureForPhoto)- (void)configureForPhoto:(Photo *)photo{    self.photoTitleLabel.text = photo.name;    NSString* date = [self.dateFormatter stringFromDate:photo.creationDate];    self.photoDateLabel.text = date;}@end

有了上述代码后,我们的 data source 方法就变得简单了。

- (UITableViewCell *)tableView:(UITableView *)tableView         cellForRowAtIndexPath:(NSIndexPath *)indexPath{    PhotoCell *cell = [tableView dequeueReusableCellWithIdentifier:PhotoCellIdentifier];    [cell configureForPhoto:[self itemAtIndexPath:indexPath]];    return cell;}

在我们的示例代码中,table view 的 data source 已经分解到单独的类中了,它用一个设置 cell 的 block 来初始化。这时,这个 block 就变得这样简单了:

TableViewCellConfigureBlock block = ^(PhotoCell *cell, Photo *photo) {    [cell configureForPhoto:photo];};

让 Cells 可复用

有时多种 model 对象需要用同一类型的 cell 来表示,这种情况下,我们可以进一步让 cell 可以复用。首先,我们给 cell 定义一个 protocol,需要用这个 cell 显示的对象必需遵循这个 protocol。而后简单修改 category 中的设置方法,让它可以接受遵循这个 protocol 的任何对象。这些简单的步骤让 cell 和任何特殊的 model 对象之间得以解耦,让它可适应不同的数据类型。

在 Cell 内部控制 Cell 的状态
假如你想自己设置 table views 默认的高亮或者选择行为,你可以实现两个 delegate 方法,把点击的 cell 修改成我们想要的样子。例如:

- (void)tableView:(UITableView *)tableView        didHighlightRowAtIndexPath:(NSIndexPath *)indexPath{    PhotoCell *cell = [tableView cellForRowAtIndexPath:indexPath];    cell.photoTitleLabel.shadowColor = [UIColor darkGrayColor];    cell.photoTitleLabel.shadowOffset = CGSizeMake(3, 3);}- (void)tableView:(UITableView *)tableView        didUnhighlightRowAtIndexPath:(NSIndexPath *)indexPath{    PhotoCell *cell = [tableView cellForRowAtIndexPath:indexPath];    cell.photoTitleLabel.shadowColor = nil;}

然而,这两个 delegate 方法的实现又基于了 view controller 知晓 cell 实现的具体细节。假如我们想替换或者重新设计 cell,我们必需改写 delegate 代码。View 的实现细节和 delegate 的实现交织在一起了。我们应该把这些细节移到 cell 自身中去。

@implementation PhotoCell// ...- (void)setHighlighted:(BOOL)highlighted animated:(BOOL)animated{    [super setHighlighted:highlighted animated:animated];    if (highlighted) {        self.photoTitleLabel.shadowColor = [UIColor darkGrayColor];        self.photoTitleLabel.shadowOffset = CGSizeMake(3, 3);    } else {        self.photoTitleLabel.shadowColor = nil;    }}@end

总的来说,我们在努力把 view 层和 controller 层的实现细节分离开。delegate 一定得清楚一个 view 该显示什么状态,但是它不应该理解如何修改 view 结构或者者给某些 subviews 设置某些属性以取得正确的状态。所有这些逻辑都应该封装到 view 内部,而后给外部提供一个简单的 API。

控制多个 Cell 类型

假如一个 table view 里面有多种类型的 cell,data source 方法很快就难以控制了。在我们示例程序中,photo details table 有两种不同类型的 cell:一种用于显示几个星,另一种用来显示一个键值对。为了划分解决不同 cell 类型的代码,data source 方法简单地通过判断 cell 的类型,把任务派发给其余指定的方法。

- (UITableViewCell *)tableView:(UITableView *)tableView         cellForRowAtIndexPath:(NSIndexPath *)indexPath{    NSString *key = self.keys[(NSUInteger) indexPath.row];    id value = [self.photo valueForKey:key];    UITableViewCell *cell;    if ([key isEqual:PhotoRatingKey]) {        cell = [self cellForRating:value indexPath:indexPath];    } else {        cell = [self detailCellForKey:key value:value];    }    return cell;}- (RatingCell *)cellForRating:(NSNumber *)rating                    indexPath:(NSIndexPath *)indexPath{    // ...}- (UITableViewCell *)detailCellForKey:(NSString *)key                                value:(id)value{    // ...}

编辑 Table View

Table view 提供了易于使用的编辑特性,允许你对 cell 进行删除或者重新排序。这些事件都可以让 table view 的 data source 通过 delegate 方法得到通知。因而,通常我们能在这些 delegate 方法中看到对数据的进行修改的操作。

修改数据很显著是属于 model 层的任务。Model 应该为诸如删除或者重新排序等操作暴露一个 API,而后我们可以在 data source 方法中调用它。这样,controller 即可以扮演 view 和 model 之间的协调者,而不需要知道 model 层的实现细节。并且还有额外的好处,model 的逻辑也变得更容易测试,由于它不再和 view controllers 的任务混杂在一起了。

总结

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

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

扩展阅读

  • Blog: Skinnier Controllers using View Categories
  • Table View Programming Guide
  • Cocoa Core Competencies: Controller Object

小编这里推荐一个群:691040931 里面有大量的书籍和面试资料,很多的iOS开发者都在里面交流技术

面试资料截图.jpg
原文Clean table view code

说明
1. 本站所有资源来源于用户上传和网络,如有侵权请邮件联系站长!
2. 分享目的仅供大家学习和交流,您必须在下载后24小时内删除!
3. 不得使用于非法商业用途,不得违反国家法律。否则后果自负!
4. 本站提供的源码、模板、插件等等其他资源,都不包含技术服务请大家谅解!
5. 如有链接无法下载、失效或广告,请联系管理员处理!
6. 本站资源售价只是摆设,本站源码仅提供给会员学习使用!
7. 如遇到加密压缩包,请使用360解压,如遇到无法解压的请联系管理员
开心源码网 » 整洁的 Table View 代码

发表回复