在IOS应用里tableview是非常灵活的。因此,许多代码都直接的或间接的与tableview有关,包括提供数据,更新tableview,控制它的行为和选择相应的反应。在本文中,我们将展示一种技术使它代码简洁并且结构良好。
UIViewController
苹果提供UITableviewController作为tableview专用的view controller。UITableviewController实现了一些非常实用的功能,它可以帮助你避免一再的写相同的模板代码。在另一方面,UITableViewController被限制只能精确的管理一个完全充满屏幕的Tableview,在许多时候,这正是你所需要的,如果它不是,有许多方法可以解决它,正如我们接下来所说的。
UITableviewController的特性
UITableviewController第一时间就帮你加载要展示在tableview上的数据。更具体的说,它帮你切换tableview的编辑模式,对键盘的的消息做出反应。为了使这些功能工作,当你调用任何view的事件方法同时调用基类的方法是很重要的,你可以在你的子类里重写这些方法。
UITableviewController和标准的视图控制器相比有一个唯一的特点,它支持苹果实现的下拉刷新。现在,文档里说唯一的使用UIRefreshControl的方法是UITableviewController。当然也有其他的方法使它工作,但是下次苹果更新之后,它很可能就不能正常工作了。
Apple定义了所有的关于tableview标准的接口。如果你的应用遵从这些标准,这是一个很好的主意防止重复写模板代码。
UITableviewController的限制
UITableviewController中view的特性总是被设置到tableview。如果您以后决定在屏幕上除tableview以外放一些其它的东西,那就要靠你的运气了,如果你不想依靠黑客。
如果您已经用代码或xib中定义好你的界面,它可以轻易的转换成标准的视图控制器。如果你使用的是storyboard,那么这个过程包含几个步骤。用storyboard,你不能把UITableviewController转换成标准的视图控件除非你重新做一遍。这就意味着你必须把所有的内容拷贝到新的view controller,把每个都重新写一遍。
最终,你需要重新添加在这个过度中失去的功能。大多数是在viewWillAppear或viewDidApper中的单行语句。
之前你虽然走过这条路,这里有一个不错的选择。
Child View Controllers
- (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];
}
如果你使用这种方法你必须创建一个子视图控制器和父视图控制器之间的联系。例如,用户选择了tableview中的一个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
分离关注点
模型和cell之间的桥接模式
- (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的类别中:
@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;
}
在我们的示例代码中,tableview的数据源被分解到我们自己的类中,他获取一个cell的配置block,因此,这个block就变得如此的简单:
TableViewCellConfigureBlock block = ^(PhotoCell *cell, Photo *photo) {
[cell configureForPhoto:photo];
};
使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总分离出来,我们必须有适当的委托代码。这个view具体的执行基于代理。另一方面,我们可以把它的逻辑移动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
一般来说,我们努力的把具体的实现从试图控制器中分离出来。一个代理必须知道视图的不同状态,但它不需要知道如何修改视图树或子视图的设置,这一切逻辑应该在视图中封装,这样可以对外提供一个简单的API;
处理不同的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