简洁的表格视图代码 无脑意译

前言

表格视图在iOS应用开发中是极具多变的模块,会有很多相关得此类代码,包含了提供数据,更新视图,响应行为事件,响应用户选择等等功能。本文主要介绍如何编写一个简洁优良架构的表格类代码。

UITableViewController对比UIViewController

表格视图控制器是专门用来服务于表格视图。其实现了大把有用的特性从而避免不断去敲一些既定需求的代码。相反方面,表格视图控制器被严格限制于只管理满屏的表格视图。但是,在许多场景已经够用。若不够用,也有一些方法可以解决,下面会介绍这些方法。

表格视图控制器特性

表格视图控制器当第一次出现得时候为你加载表格视图数据。更专业一点的说,它为你连接表格视图的编辑模式,响应键盘事件,刷新滚动标志图,反选。为了能成功的实现这些效果,对你来说非常重要的是你在自定义的子类中重写视图事件方法时候需要调用父类的视图事件方法(比如viewWillAppear:viewDidAppear:)。

对比标准的视图控制器,表格视图控制器有一个只有其才拥有的卖点那就是支持苹果拉动式刷新的实现。现在唯一有文档化声明并使用UIRefreshControl。有其他一些方式去实现这种功能,但是如果大苹果更新了iOS了可能就失效了。。。

表格视图控制器的局限性

表格视图通常情况自然是表格视图控制器的成员变量。如果你决定除了表格视图外还在你的屏幕上显示其他的东东,那你面对糟糕的约束时候你就不够走运了。

如果你定义你的接口无论是使用代码亦或是使用XIB则非常弄容易的转换成标准视图控制器。但是一旦你使用了SB,那就需要涉及到多一些的步骤。使用SB的时候你不能够在不重新创建的前提下改变表格视图控制器成标准视图控制器。这意味着你不得不拷贝所有得内容到新的视图控制器并重新将所有东西连线好。

最后,你还需要加回所有表格试图控制器所具有因为转换遗漏的特性。大多数是很简单的一句代码比如在viewWillAppear或者viewDidAppear。切换编辑状态需要实现一个动作方法,该方法改变表格视图的编辑状态editing属性。工作量最大的在于编写键盘事件响应的支持。

在你这么做之前,这里有个一个很好的转换方法??

子视图控制器

与彻底取代表格视图控制器不同,你也可以以子视图控制器的形式添加它。这时候就可以让表格试图控制器关注它该关注的东西了,而父试图控制器则关注剩余的接口。

- (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];
}

如果你用上述实现模式,你需要为父子视图控制器创建一个通信通道。举个栗子,如果用户选中一个cell那么父试图控制器需要知道该事件并且推入另外一个试图控制器。根据使用情形,通常最简洁的方式是为表格视图控制器定义一个代理协议,代理方法具体实现由父视图控制器搞定。

@protocol DetailsViewControllerDelegate
- (void)didSelectPhotoAttributeWithKey:(NSString *)key;
@end

@interface PhotoViewController () <DetailsViewControllerDelegate>
@end

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

正如你所看到的,这种构建确实可以得到一个解耦和更好复用的效果但是同时也造成额外的通信代价。根据特殊的使用情形,这样可以根据具体需要来简化或者复杂化。这是你所要思考跟决定的。

解耦思想

处理表格视图的时候会因为涉及到因为数据,控制器,视图之间的交互的深度不同会有不同的变种。为了避免代码理到处都是这些业务逻辑,我们需要尽量在正确的地方编写出这部分逻辑代码。这样可以增强可读性,可维护性以及可测试性。

连接数据对象与Cells

我们需要根据数据源来展示我们的cells,一般做法是让表格视图的数据源来完成这个任务:

- (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;
}

更好的办法是写一个这种cell的类的category分离cell根据业务初始化的代码:

@implementation PhotoCell (ConfigureForPhoto)

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

@end

这样子的好处是使我们的数据源方法十分精简:

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

我们还可以通过块设置来进一步精简我们的代码:

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

Cells复用

在许多场景我们有多种得数据对象需要使用一些相同类型得cell展示,我们可以进一步提升cell的复用效果。要做到这些,我们首先要定一个cell需要遵守的协议来展示我们的cell,从而我们可以简单改变cell的category中的配置方法中遵守该协议的任何对象。这些简单地步骤可以让cell从特定的数据对象中解耦出来并且可以使其适用于不同的数据类型。

在Cell中处理Cell的状态

如果我们想多处理一些超出标准高亮、选择的表格视图操作。我们需要实现下面两个代理方法来让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;
}

然后,这两个代理的实现依赖于它们知道特定的cell实现。如果我们需要交换cell或者重现用另外的方式实现,我们不得不去适配代理方法。视图的具体实现完全以代理实现为根本。换言之,我们需要把逻辑搬到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

通常来说我们极力推荐将视图层的实现与控制器层的实现做到分割。代理需要知道视图可能处于的不同状态,但是不能去越权修改如何修改视图树结构或者子视图的属性来获取想要的状态。所有得逻辑需要在视图内封装好,对外暴露的接口需要保证简洁。

处理多Cell类型

若在你的表格视图有多种不同的cell类型,那么数据源方法可以帮你很容易的搞定这类问题。在我们的栗子APP中的图片详情表格我们拥有两种不同的Cell类型:一种用来展示星级,另外一种用来展示键值对Cell。为了将处理不同cell类型的代码分离出去,数据源方法根据不同的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
{
    // ...
}

表格视图编辑

表格视图提供了一种便捷的编辑方式,允许用户改变cell顺序以及删除cell。在这些事件中,表格视图的数据源通过代理方法得到修改。因此,我们通常主要在这些代理方法中看到数据修改的代码实现。

修改数据其实是数据层应该干的活。数据层需要暴露出API去删除或者排序,从而我们可以调用这些数据源方法。这样子,控制器可以扮演一个视图与数据协调人的角色而不用去管数据层具体实现细节。至于更多的优点是在于数据层可以更容易被测试,因为它不用跟视图控制器的其他业务逻辑混杂在一起。

总结

表格视图控制器需要尽可能的承当数据与视图中间人的角色。它们不应该狗拿耗子去管理本应属于视图层或者数据层所要关注的任务。如果你始终记住这点,那么代理以及数据源方法将可以简化到只包含简单的模板。

这样做不仅仅淡出的降低了表格视图控制的代码量与复杂度,更把一些业务逻辑以及视图逻辑放在了恰当得地方。实现的具体细节被封装到了API里面,最终达到代码可读性优良以及更好的调用协作!

    原文作者:Cruise_Chan
    原文地址: https://segmentfault.com/a/1190000000758534
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞