本文由 伯乐在线 - christian 翻译自 Florian Kugler。欢迎加入技术翻译小组。转载请参见文章末尾处的要求。

表视图是一个非常万能的iOS应用程序构建模块。因此,有很多与表视图直接或间接相关的代码,包括提供数据、更新表视图、控制其行为和选择做出的反应,这仅仅是几个例子。在这篇文章里,我们将会介绍一些整洁而结构良好的代码。

UITableViewController VS UIViewControler

苹果提供了UITableViewController作为表视图的专用视图控制器。Table view controllers实现了一些非常有用的特性来帮助你避免写重复的代码。另一方面,Table view controllers仅限于管理一个全屏显示的表视图。然而,在大多数情况下,这就可以满足你的需要了。如果不满足的话,我们将会在下面讲解解决它的方法。

Table View Controllers的特性

Table View Controllers将会在它第一次显示的时候帮你加载表视图的数据。更具体地说,它可以帮助切换表视图的编辑模式,对键盘的通知做出反应,比如滚动刷新和清除选项这类的小功能。重要的是,你可以自定义子类来重写这些可以被称为万能的视图时间方法来实现这些特性。

Table View Controllers有一个独特的卖点超越了标准视图控制器的独特卖点,它支持苹果的z“下拉更新”的功能目前,这是唯一通过使用一个表控制器来控制刷新记录的方法。还有一些其他的方法来使它生效,但是在下一次iOS中的更新却不是那么的容易

所有的这些原理提供了大量的表视图控制器的接口像苹果已经定义的那样,如果你APP符合这些标准,坚持使用表视图控制器是一个避免重写模版代码的好办法。

Table View控制器的限制

表视图控制器的视图属性总是被设定在一个表视图上。如果你以后决定想在屏幕上显示除了视图表以外的东西(比如地图),假如你不想依赖于笨拙的补丁那你就有的惨了。

如果你已经定义在代码里定义了接口或者在使用.xib文件,那么转换到一个标准的视图控制器就会相当容易了。如果你使用脚本的话这个转换过程会涉及到更多一些的步骤。用脚本,你需要通过重新创建来把一个表视图控制器转变为一个标准的表视图控制器。这就意味这你必须把所有的内容复制到这个新的试图控制器中然后再重新建立。最后,你需要重新把转变过程中丢失的表视图控制器的功能添加一遍。大部分都是viewWillAppear或viewDidAppear里简单的单行语句。切换编辑状态需要一个点击表视图的编辑属性方法来执行。大部分的工作在于重新创建键盘支持。

在你继续走这个路线之前,这里有一个关注点分离附加好处的简单替代方法。

子视图控制器

并非为了完全摆脱表视图管理器,你也可以将其作为一个子视图控制器添加到另一个视图控制器(见本文关于视图管理器的控制)。然后表视图管理器只需要继续管理这个表视图而父视图管理器可以处理任何额外你需要的界面元素。

1
2
3
4
5
6
7
8
9
10
11
12
- (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];
}

如果你选择这个解决方法的话,你需要建立一个子视图和父视图之间的通信通道。例如,为了能够push另一个视图进来,父视图需要知道table view的cell被选中了。鉴于这个使用场景,最干净的方法就是为table view控制器定义一个代理协议,可以在父视图中实现这个协议。

如果你要使用这个解决方法,你必须创建一个从子类到父类的通信通道。例如,如果用户选择了一个表视图的单元格,父视图控制器需要接收到消息来推动另一个视图控制器。根据实例,通常最简洁的方法是为这个表视图控制器定义一个委托协议,然后你在父视图管理器中实现。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@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

正如你看的那样,这种结构会在视图控制器之间的通信中伴随一些其他的额外开销来换取干净的关注点分离和更好的重用性.根据具体的用例,最终使事情比必需要更简单或者更复杂,这是你需要考虑和决定的。

分离关注点

当处理表视图时有各种不同的任务关于模型,控制器和视图的跨越边界问题。为了防止视图控制器成为存放这些任务的地方,我们会视图讲这些任务单独的放在更合适的地方。这有助于代码的可读性,维护性和测试性。

在轻视图控制器的文章里讲述了详细的概念和扩展技术。如何把我们的数据源和模型逻辑引入。在表视图的环境下,我们将专门看看如何分离视图控制器和视图的关注点的问题。

桥接模型对象和单元之间的差距

在某种程度上,我们必须交出想要在视图层显示的数据。由于我们想要维持一个模型和视图之间清晰的分离点,经常把这个任务放到表视图的数据来源里。

1
2
3
4
5
6
7
8
9
- (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的设计逻辑绑定在了一起。我们最好将这个在cell的类别类里重构一下。

1
2
3
4
5
6
7
8
9
10
@implementation PhotoCell (ConfigureForPhoto)
 
- (void)configureForPhoto:(Photo *)photo
{
    self.photoTitleLabel.text = photo.name;
    NSString* date = [self.dateFormatter stringFromDate:photo.creationDate];
    self.photoDateLabel.text = date;
}
 
@end

这种情况下,我们的数据源代码就变得非常简单了。

1
2
3
4
5
6
7
- (UITableViewCell *)tableView:(UITableView *)tableView
         cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    PhotoCell *cell = [tableView dequeueReusableCellWithIdentifier:PhotoCellIdentifier];
    [cell configureForPhoto:[self itemAtIndexPath:indexPath]];
    return cell;
}

在示例代码中,初始化cell的时候时候block的方式,table视图的数据源已经分离到了一个单独的控制对象中,在这个例子中,这个block就像下面这样

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

使cell可重用

在这种有多个数据模型使用同一个cell类型展示的情况下,我们甚至可以进用一步就可以达到cell重用的效果。首先,我们定义一个所有需要使用这个cell类型展示数据的对象都需要实现的协议。然后,我们修改一些cell类别中的配置方法,使它可以接受任何遵循上述协议的对象。这两个简单的步骤将cell和数据模型分离并且使cell可以接受不同的数据类型。

在cell中处理cell的状态

如果我们想做一些与默认情况下不同的table view的高亮和选中状态,我们需要实现两个代理方法来实现将cell修改成我们想要的状态。例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
- (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是如何布局的,如果我们想要换一个cel或者重新设计cell,我们同样需要修改这段代理代码。view的设计细节就和代理交织在一起了,我们应该将这段逻辑放到cell中。

1
2
3
4
5
6
7
8
9
10
11
12
13
@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层的实现细节和控制器层的实现细节分离开来。代理可以知道view的状态变化,但是不应该知道如何修改view的树状结构以及它的子视图应该设成什么状态,所有这些状态都应该封装在view中,然后提供给外部一个访问的接口。

处理多种cell类型

如果在一个table view中有多种cell类型,数据源就要变得失控了。在我们的示例app中,我们的照片详情表格有两种不同类型的cell:一个显示评分,另一个就是一般的显示键-值的cell。为了将显示不同cell类型的代码分离,数据源方法里就是简单调用不同类型cell的方法。

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
- (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 controllers (和其他控制器对象)大部分情况下都应该起着模型和视图对象之间的协调中介作用,他们不应该关心模型或者视图的具体实现细节。如果你记住这点,那么代表和数据源方法就变得更简单和更容易维护的样板代码了。

这样不仅会降低了Table view controllers 的代码规模和复杂性,而且使模型逻辑代码和视图代码放在了更合适的地方。控制器上下之间的实现细节都被隐藏在简单的API中,最终使得代码更容易理解和协同工作。

 

原文链接: Florian Kugler   翻译: 伯乐在线christian
译文链接: http://blog.jobbole.com/53123/
转载必须在正文中标注并保留原文链接、译文链接和译者等信息。]

iOS系列译文:整洁的表视图代码的更多相关文章

  1. IOS开发中UITableView(表视图)的滚动优化及自定义Cell

    IOS开发中UITableView(表视图)的滚动优化及自定义Cell IOS 开发中UITableView是非常常用的一个控件,我们平时在手机上看到的联系人列表,微信好友列表等都是通过UITable ...

  2. iOS系列 基础篇 04 探究视图生命周期

    iOS系列 基础篇 04 探究视图生命周期 视图是应用的一个重要的组成部份,功能的实现与其息息相关,而视图控制器控制着视图,其重要性在整个应用中不言而喻. 以视图的四种状态为基础,我们来系统了解一下视 ...

  3. iOS企业级开发初级课程-表视图(13集)

    首先了解了表视图的组成.表视图类的构成.表视图的分类以及表视图的两个重要协议(委托协议和数据源协议),对表视图有了一个整体上的认识.接下来我们掌握了如何实现简单表视图和分节表视图,以及表视图中索引.搜 ...

  4. iOS系列译文:自定义Collection View布局

    原文出处: Ole Begemann   译文出处: 黄爱武(@answer-huang).欢迎加入技术翻译小组. UICollectionView在iOS6中第一次被介绍,也是UIKit视图类中的一 ...

  5. [转]iOS系列译文:深入理解 CocoaPods

    Cocoapods是 OS X 和 iOS 下的一个第三方库管理工具.你能使用CocoaPods添加被称作“Pods”的依赖库,并轻松管理它们的版本,而不用考虑当前的时间和开发环境. Cocoapod ...

  6. iOS系列教程 目录 (持续更新...)

      前言: 听说搞iOS的都是高富帅,身边妹子无数.咱也来玩玩.哈哈. 本篇所有内容使用的是XCode工具.Swift语言进行开发. 我现在也是学习阶段,每一篇内容都是经过自己实际编写完一遍之后,发现 ...

  7. iOS系列 基础篇 05 视图鼻祖 - UIView

    iOS系列 基础篇 05 视图鼻祖 - UIView 目录: UIView“家族” 应用界面的构建层次 视图分类 最后 在Cocoa和Cocoa Touch框架中,“根”类时NSObject类.同样, ...

  8. iOS开发之多表视图滑动切换示例(仿"头条"客户端)---优化篇

    前几天发布了一篇iOS开发之多表视图滑动切换示例(仿"头条"客户端)的博客,之所以写这篇博客,是因为一位iOS初学者提了一个问题,简单的写了个demo做了个示范,让其在基础上做扩展 ...

  9. iOS开发之表视图爱上CoreData

    在接触到CoreData时,感觉就是苹果封装的一个ORM.CoreData负责在Model的实体和sqllite建立关联,数据模型的实体类就相当于Java中的JavaBean, 而CoreData的功 ...

随机推荐

  1. JSP内置对象阶段案例

    1.login2: <%@ page language="java" import="java.util.*" pageEncoding="UT ...

  2. Ubuntu 16.04通过网络配置工具NetworkManager设置IP、网关、DNS和查看IP、网关、DNS

    说明: 1.NetworkManager工具是Ubuntu桌面版的GUI设置工具. 2.这个工具推荐直接在GUI上操作,不建议用命令行进行管理,比如Wifi这些配置等. 3.当然,这个工具能带有命令行 ...

  3. Windows 8.1中WinRT的变化(二)——新增功能

    首先我们来看看现有控件中新增的功能: FlipView编程方式切换时支持平滑滚动: 在Windows8中,FlipView在用手触控翻页的时候是有动画效果的,但当我们使用键盘或代码编程翻页时,却没有这 ...

  4. linux命令详解:basename命令

    转:http://www.cnblogs.com/lwgdream/archive/2013/11/05/3407768.html 前言 bashname命令用于获取路径中的文件名或路径名(获取的时候 ...

  5. 利用【深度网络】高效提取feature

    extracting features from a learned model, and add some new features yourself.

  6. django 修改默认的user表和默认的认证系统

    django的功能非常强大,但是自带的user表很多情况下并不满足我们的需求,因此我们需要修改其默认的user表,并且把用username登录改成用email登录 第一步,创建自己的user表,在创建 ...

  7. python良好的编程习惯

    良好的编程习惯 2.1 在程序中是用丰富的注释,注释有助于其他程序员理解程序,有助于程序调试(发现和排除程序中的错误),并列出有用的信息.以后修改或更新代码时,注释还有助于理解当初自己编写的程序 2. ...

  8. 前端JS利用canvas的drawImage()对图片进行压缩

    对于大尺寸图片的上传,在前端进行压缩除了省流量外,最大的意义是极大的提高了用户体验. 这种体验包括两方面: 1.由于上传图片尺寸比较小,因此上传速度会比较快,交互会更加流畅,同时大大降低了网络异常导致 ...

  9. ElasticSearch获取指定Field数据的Java方法

    ElasticSearch(ES)检索后需要结果时,可能通过source接口读出.但是这样的话,返回的结果会很多.在调用search方法时,我们可以添加addfield或addfields方法,仅仅读 ...

  10. 文件压缩和解压 FileStream GZipStream

    using (FileStream reader=new FileStream (@"c:\1.txt",FileMode.Open,FileAccess.Read)) { usi ...