美文网首页
提高 TableView 的整洁度

提高 TableView 的整洁度

作者: 爱敲代码的果果 | 来源:发表于2017-11-16 16:34 被阅读58次

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

UITableViewController vs. UIViewController

Apple 提供了UITableViewController作为 tableViews 专属的 viewController 类。TableViewControllers 实现了一些非常有用的特性,来帮你避免一遍又一遍地写那些死板的代码!但是话又说回来,tableViewController 只限于管理一个全屏展示的 tableView。大多数情况下,这就是你想要的,但如果不是,还有其他方法来解决这个问题,就像下面我们展示的那样。

TableViewControllers 的特性

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

TableViewControllers 相对于标准 viewControllers 的一个特别的好处是它支持 Apple 实现的“下拉刷新”。目前,文档中唯一的使用 UIRefreshControl 的方式就是通过 tableViewController ,虽然通过努力在其他地方也能让它工作,但很可能在下一次 iOS 更新的时候就不行了。

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

TableViewControllers 的限制

Tableviewcontrollers 的 view 属性永远都是一个 tableView。如果你稍后决定在 tableView 旁边显示一些东西(比如一个地图),如果不依赖于那些奇怪的 hacks,估计就没什么办法了。

如果你是用代码或 .xib 文件来定义的界面,那么迁移到一个标准 viewController 将会非常简单。但是如果你使用了 storyboards,那么这个过程要多包含几个步骤。除非重新创建,否则你并不能在 storyboards 中将 tableViewController 改成一个标准的 viewController。这意味着你必须将所有内容拷贝到新的 viewController,然后再重新连接一遍。

最后,你需要把迁移后丢失的 tableViewController 的特性给补回来。大多数都是 viewWillAppear: 或 viewDidAppear:中简单的一条语句。切换编辑模式需要实现一个 action 方法,用来切换 tableView 的 editing 属性。大多数工作来自重新创建对键盘的支持。

在选择这条路之前,其实还有一个更轻松的选择,它可以通过分离我们需要关心的功能(关注点分离),让你获得额外的好处:

使用 ChildViewControllers

和完全抛弃 tableViewController 不同,你还可以将它作为 childviewcontroller 添加到其他 viewController 中。这样,parentViewController 在管理其他的你需要的新加的界面元素的同时,tableViewController 还可以继续管理它的 tableView。

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

如果你使用这个解决方案,你就必须在 childViewController 和 parentViewController 之间建立消息传递的渠道。比如,如果用户选择了一个 tableView 中的 cell,parentViewController 需要知道这个事件来推入其他 viewController。根据使用习惯,通常最清晰的方式是为这个 tableViewController 定义一个 delegate protocol,然后到 parentViewController 中去实现。

@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

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

分离关注点(Separating Concerns)

当处理 tableViews 的时候,有许多各种各样的任务,这些任务穿梭于 models,controllers 和 views 之间。为了避免让 viewControllers 做所有的事,我们将尽可能地把这些任务划分到合适的地方,这样有利于阅读、维护和测试。我们来具体看看如何在 viewControllers 和 views 之间分离关注点。

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

有时我们需要将想显示的 model 层中的数据传到 view 层中去显示。由于我们同时也希望让 model 和 view 之间明确分离,所以通常把这个任务转移到 tableView 的 datasource 中去处理:

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

但是这样的代码会让 datasource 变得混乱,因为它向 datasource 暴露了 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;
}

在我们的示例代码中,tableView 的 datasource 已经分解到独立的类中,它用一个设置 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 的状态

如果你想自定义 tableViews 默认的高亮或选择行为,你可以实现两个 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 方法的实现又基于了 viewController 知晓 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,datasource 方法很快就难以控制了。在我们示例程序中,photoDetailsTable 有两种不同类型的 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 的任务混杂在一起了。

此文章原文链接自己的个人博客: www.koalaliu.com ,因简书平台规范性以及用户量,搬至简书。

相关文章

  • 提高 TableView 的整洁度

    TableView 是 iOS 应用程序中非常通用的组件。许多代码和 tableView 都有直接或间接的关系,随...

  • 整洁的TableView代码

    UITableView是日常开发中使用的最为频繁的控件之一,干净整洁的TableView会方便开发者日后代码的维护...

  • iOS-整洁的TableView

    Apple 提供了 UITableViewController作为 TableView 专属的 ViewContr...

  • UIButton - 封装

    UIButton 是开发中高频使用的控件,一个好的封装能大大提高代码的整洁度 1. Target封装 .........

  • 瘦身UIViewController+更整洁的TableView

    本来文章标题想起为“OC开发之我想分享的一些东东”,然后大概整理了下,发现还挺多的,所以我觉得还是分开写好。想分享...

  • 【译】让你的tableView代码整洁

    tableView 是 iOS 应用程序中非常通用的组件,许多代码和tableView都有直接或者间接的关系,比如...

  • OPPO拍摄日记:框架构图

    选择一个角度,将目标主体放在框架内,以突出主体,减少干扰,提高画面整洁度,重点,多拍几张,选择一张最好看的。

  • 英典轮值每月公司大清洁流程和负责人

    为了提高公司的形象和卫生整洁度,能够吸引更多的高品消费的客人,由2018.7月份开始做轮公司大清洁责任有检视...

  • iOS图片浏览器

    效果图 核心代码 横向滚动TableView 1、TableView逆时针旋转90度,其宽度等于容器的高度,高度等...

  • 优化tableView的流畅度

    1 使用不透明视图。 **不透明的视图可以极大地提高渲染的速度。因此如非必要,可以将table cell及其子视...

网友评论

      本文标题:提高 TableView 的整洁度

      本文链接:https://www.haomeiwen.com/subject/xoalvxtx.html