第三节、整理表视图的代码

CSDN博客: forlong401
邮件地址:forlong401@163.com
微博:@forlong401

第一章 轻量级视图控制器,2013年6月,作者:Florian Kugler

  表视图是一个非常通用的iOS应用程序构建块。因此,大量的代码是直接或间接与表视图任务相关,包括提供数据、更新表视图、控制其行为和反应选择等,但这些也仅仅是其中一部分任务。在本文中,我们将介绍保持这类代码清晰且结构简单的相关技术。

1.UITableViewController和UIViewController

  苹果提供UITableViewController专门作为表视图的视图控制器类。一方面,表视图控制器实现一些非常有用的特性,可以帮助你避免编写相同的样板代码。另一方面,表视图控制器被限制只能管理一个全屏显示的表视图。然而,在许多情况下,这些就是你所需要的全部功能了,如果不是,也有办法解决这个问题,我们将在下面显示。

2.表视图控制器的特性

  当表视图第一次显示时,表视图控制器帮你加载表视图的数据。更具体地说,它帮助你在收到键盘的通知时,切换表视图的编辑模式,还有一些其它的事情,如闪烁显示滚动指示器和清除选择指标。为了让这些特性工作,重要的是,你可能需要在您的自定义子类里复写并调用任意的视图超类中的事件方法(如viewWillAppear:viewDidAppear:)。
  表视图控制器相对于标准视图控制器,有一个独特的卖点就是支持苹果的已经实现的“下拉刷新”。目前,文档中有描述的唯一使用UIRefreshControl的方法就是在表视图控制器中。也有很多办法使UIRefreshControl在其他场合下工作,但这些都可能很容易地在下一个iOS更新后就不能使用。
所有这些标准表视图界面行为需要提供的元素,它们大部分苹果都已经定义了。如果您的应用程序符合这些标准,那么使用表视图控制器就可以避免编写重复的模块代码。

3.表视图控制器的局限性

  表视图控制器的视图属性总是被设置为一个表视图。以后如果你决定显示除了表视图外屏幕上的其它东西(比如:地图),而你又不想依靠让人不爽的黑客,那么你就要倒霉了。
  如果你已经用代码或.xib文件定义好了界面,那么很容易过渡到一个标准的视图控制器。如果你使用故事板,那么这个过程包括几个步骤。对于故事板,你不能改变一个表视图控制器为标准视图控制器,除非你重新创建它。这意味着您必须将所有内容复制到新视图控制器并重新连接所有的一切。
  最后,您想要添加表视图控制器的功能,那么你就惨了。他们中的大多数将是在viewWillAppear或viewDidAppear中单行声明。切换编辑状态需要执行一个动作方法翻转表视图的编辑属性。当然大部分工作集中在重新创建对键盘支持。
  你要继续走下去之前,下面是一个简单的选择,分离关注的额外好处:

4.子视图控制器

  要完全摆脱表视图控制器,您也可以将其添加为子视图控制器到另一个视图控制器(见本期关于视图控制器容器的文章)。然后表视图控制器继续管理表视图,父视图控制器可以管理您可能需要的任何额外的界面元素。

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

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

@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

  正如您可以看到的,这种结构也有一些开销来应对视图控制器之间进行通信,以换取清晰的解耦和更好的可重用性。根据具体的用例,这最终可以使事情不可避免的更简单或更复杂。这取决于你的考虑和决定。

5.解耦

  当处理表视图时,有各种不同的任务,它们涉及跨越模型、控制器和视图之间的边界。为了防止所有这些任务放置在视图控制器,我们将试图尽可能隔离这些任务到更合适的地方。这有助于可读性、可维护性和可测试性。
  在本章轻量级视图控制器文章中已经证明相关技术方法包括继承和合成。请参阅本文如何分解我们的数据源和模型逻辑。在表视图的背景下,我们专门介绍如何解耦视图控制器和视图。

6.连接模型对象和单元格

  在某种程度上,我们必须移交我们想要显示到数据视图层的数据。因为我们仍然想保持一个清晰的模型和视图解耦,我们经常倾泻这个任务到表视图的数据源:

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

  这种代码混乱了具有关于单元设计的特定知识的数据源。我们最好考虑分解单元格到一个类别单元格类:

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

7.生成可重用的单元格

  我们有多个模型对象使用相同的单元格类型的这种情况下,我们甚至可以更进一步提高单元格的可重用性。首先,我们定义一个关于单元格的协议,让对象必须遵循以显示为这个单元格类型。然后我们在单元格类别中简单地改变配置方法来接受符合此协议的任何对象。这些简单的分离了任何特定的模型对象单元格的耦合,使其适用于不同的数据类型。

8.处理单元格内的状态

  如果我们想做一些超出了表视图的标准的强调或选择的行为,我们可以实现两个委托方法,来以我们想要的方式修改了轻击中的单元格。例如:

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

  然而,实现的两个委托方法又依赖于单元格是如何实现的特定知识。如果我们想换出单元格或以不同的方式去重新设计,我们也必须适配委托代码。视图的实现细节与委托的实现交织。相反,我们应该把这种逻辑移到单元格本身。

@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

  一般来说,我们努力分离视图层的实现细节和控制器层的实现细节。委托必须知道在一个视图不同的状态,但是它不应该知道如何修改视图树或哪些属性设置到子视图以便得到它正确的状态。所有这些逻辑应该封装在视图,然后提供了一个简单的API给外部使用。

9.处理多种单元格类型

  如果你有多个不同的单元格类型在一个表视图,数据源方法可能迅速失控。在我们的示例应用程序有两种不同的单元格类型的图片详情表:显示一星级的单元格、一个通用的显示一个键-值对的单元格。为了单独的代码处理这些不同的单元格类型,数据源方法只是将请求分发到每个单元格类型的指定方法。

- (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
{
// ...
}

10.表视图的编辑

  表视图提供易于使用的编辑功能,允许单元格的重新排序和删除。在这些事件的情况下,表视图的数据源通过委托方法得到通知。因此,我们经常看到在这些委托方法中的领域逻辑执行实际修改数据的操作。

  修改数据是一个显然属于模型层的任务。模型应该公开一个API,包括:删除和重新排序,然后我们可以从数据源方法调用它。这种方式,控制器扮演着协调视图和模型的角色,但不必知道模型层的实现细节。另一个好处是,模型逻辑也变得更容易测试,因为它不是交织于其他视图控制器的任务了。

11.结论

  表视图控制器(和其他控制器对象!)主要应该是模型和视图对象之间的协调人及中间人角色。他们不应该关心显然属于视图或模型层的任务。如果你记住这一点,委托和数据源方法将变得更小,主要包含简单的样板代码。

  这不仅降低了表视图控制器的规模和复杂性,同时也把领域逻辑和视图逻辑分离到各自更合适的地方。控制器层之上和之下的实现细节封装在一个简单的API,这最终会使它变成更容易理解的代码,也更容易协同工作。

12.延伸阅读

展开阅读全文

没有更多推荐了,返回首页