更加简洁的tableview

Clean table view code(更加干净的tableview代码)


tableview在ios中被广泛的应用,所有有很多的代码直接或者间接地和tableview产生关系,包括数据提供和数据更新的以及控制tableview行为和选择某一行的反应的代码,下面将展示一些让tableview更加简洁更加结构紧凑的方法。

UITableViewController vs.
UIViewController


苹果提供了UITableViewController来展示tableview,UITableViewController具有了很多很好用的特性供我们使用,使得我们可以从无用的重复代码中得到解脱。但是在另一方面,UITableViewController被设置为只能管理一个tableview,并且是全屏展示的,虽然在很多情况下这已经足够满足我们的需要了,但是还是会由一些情况我们会遇到,相应的我们也有解决方案。下面将会展示

Features of Table View
Controllers(特性展示)

UITableViewController在首次加载的时候会进行tableview数据的加载,更加具体来说就是UITableViewController通过响应键盘通知或者其他的小的如滑动指示器和清空tableview的选择等任务来
帮助我们切换tableview的编辑模式。为了能够完成这些效果,在重写任意的view开头的方法都需要调用super的相应方法。

UITableViewController和其他的标准控制器相比有一个独特的卖点:对于苹果的"push
 to
refresh"(滑动刷新)的实现,在当前情况下,唯一的官方文档记录的使用UIRefreshControl的方式是在UITableViewController中实现的。其实有很多方法使其他的控件也能使用这个UIRefreshControl,但是这些很有可能在下次更新的时候无法得到IOS的支持。

Limitations of Table View
Controllers

UITableViewController的view通常只能是tableview。如果你决定在不久的将来想要在tableview的附近展示其他的view,如果可以成功将是非常幸运的。

如果是通过代码或者XIB来实现的界面,那么把UITableViewController转化为普通的控制器是很简单的,但是如果是通过storyboard来实现的UI那么将是很困难的,因为你不得不重新来拖拽控制器,并且把之前的控制器中的所有的东西都移动到新的控制器中。

最后,你需要为这个普通的控制器添加table view
controller在转换过程中丢失的属性,这些丢失的特性大多只需要在viewWillAppear 或viewDidAppear.方法中用一行搞定,改变tableview编辑状态的功能则需要实现一个决定tableview编辑状态属性的类方法。

 在继续进行下去之前,这里其实还有一个更加简单的选择,而且需要考虑的更加的少。

Child View Controllers

除了完全替换UITableViewController之外,还可以将这个控制器添加到另外一个控制器中作为子控制器,这样的话这个UITableViewController可以继续管理tableview,而其他的事情则交给它的父控制器

?





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

}

  如果采用了这种方式,那么需要创建一个子控制器和父控制器之间联系的通道UITableViewController中的tableview的某一行那么这个消息需要传送到UITableViewController的父控制器,父控制器以此来决定跳转的页面,在这种情况下通常的处理是使用delegate,让父控制器遵守UITableViewController的代理来实现信息的传递

@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

  正如你所看到的,采用这种结构的结果是虽然这样的代码分的很清楚,也具有较好的重用性,但代价是需要建立连接通道。这样的情况下有可能使问题更加复杂或者简单。

Separating Concerns

 在处理tableview的问题的时候往往会将很多不同的贯穿于视图模型控制器的任务包含在其中。为了防止控制器变成这一群问题发生的地点,我们试着将不同的任务放在最适合处理的地方进行,这样使得我们的代码更加的可读更加的紧凑和可测试。

Bridging the Gap
Between Model Objects and Cells(逾越model对象和cell之间的鸿沟)

 有时候我们需要将需要展示在view层的数据叫出来,但是我们依然想要保持模型和视图之间的独立性,通常我们会将这个任务转交给tableview的数据源属性

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

  

Making Cells Reusable

有些时候我们只需要使用同一类型的cell便可以完成很多模型对象的展示,所以我们应该更进一步让我们的cell具有重用性。

首先我们在cell内部定义一个协议,需要被这个cell展示的模型需要遵守这个协议,然后我们简单的改变 cell的category中的configure方法,以此接受所有的遵守协议的对象类型,这些简单的操作让cell从任何特定的模型对象中脱离出来,让其可以应用在不同的数据类型中。

Handling Cell State Within the
Cell

如果想要改变tableview的选中亮度或者选中的行为,那么可以通过实现两个代理方法来进行操作,通过这两个方法我们可以让选中的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的实现细节。如果我们希望cel出内存l或者重新设计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

  一般来说,我们尽量将view层的代码具体实现细节和controller的代码具体实现细节分离开来。一个代理需要知道一个view的集中不同状态,但是

它不应该知道如何改变view树或者怎么设置属性能够得到子控件的一些状态值,所有的这些逻辑都应该屏蔽在view自己内部,然后对外提供一个接口就可以了。

Handling Multiple Cell
Types(多种类型的cell类型)

 如果你的tableview中又多种类型的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就可以了

Table View Editing

tableview提供了很好用的特性,可以方便的记录和删除cell,在所有的事件中,tableview的数据源通过代理方法得到通知,但是我们却经常在这些方法中发现一些改变数据的域逻辑。

改变数据这项任务很明显是属于模型层的,而这些模型应该暴漏一些保存和删除数据的接口给外界使用,这些接口可以通过数据源方法调用,这样的话控制器就扮演了view和model之间的桥梁,但是却并不知道model的内部方法的具体实现细节,这样做的另外一个好处是,模型逻辑变得更加的容易进行测试,因为model没有再和控制器的其他任务交织在一起,变得更加纯洁了。

Conclusion

所有的控制器都应该扮演的时model和view之间交流的桥梁,但不能和特定属于model或者view的功能交织在一起,如果你能将这个原则牢记在新的话,那么代理和数据源方法将会变得更加的轻量级和简单。

 这样做不仅仅有效降低了控制器的大小和复杂度,而且将业务逻辑放在了最适合处理的地方进行处理,具体的实现细节被屏蔽起来了,而控制器面对的只是简单的API,这样最终的结果是代码变得更加的容易理解并且可以很方便的进行团队开发。

时间: 2024-10-07 08:52:47

更加简洁的tableview的相关文章

SYRefresh 一款简洁易用的刷新控件 支持tableview,collectionview水平垂直刷新功能

SYRefresh 地址: https://github.com/shushaoyong/SYRefresh 一款简洁易用的刷新控件 示例程序:   默认刷新控件使用方法: //添加头部刷新控件 ScrollView.sy_header = [SYRefreshView refreshWithHeight:40 isFooter:NO completionBlock:^{ dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 *

简洁的ios小界面

下午写写了个小东西小界面 有须要的能够直接拿过来用 ,简洁,挺好看,自我感觉: 写界面事实上就是自上而下的在view加空间,注意一下位置即可了 - (id)initWithFrame:(CGRect)frame { self = [super initWithFrame:frame]; if (self) { CGRect screenSize = [[UIScreen mainScreen]bounds]; //无货物信息图片 UIImageView *image = [[UIImageVie

swift入门之TableView

IOS8更新了,oc还将继续但新增了swift语言,能够代替oc编写ios应用,本文将使用swift作为编写语言,为大家提供step by step的教程. 工具 ios每次更新都须要更新xcode,这次也不例外,但使用xcode6,须要先升级到OS X 到Yosemite.具体的升级过程这里就不说了. 须要网盘下载的同学能够查看一下链接 http://bbs.pcbeta.com/viewthread-1516116-1-1.html 建立project xocde开启后选择File->New

tableview 使用visual format language自适应cell宽高,和横竖屏

再也不用担心,如何做适配了. 使用苹果官方提供的适配方案当然是最正宗的,比第三房框架可控. 可以适配各种屏幕尺寸,以及横竖屏,欢迎有独特简介的极客们,提出宝贵意见 开发过程中,时常会遇到cell自适应高度与横屏下的宽度,传统的做法要么是手动计算frame,要么就是使用xib. 第一种方式,工作量是巨大的(相信很多人深有体会). 第二种方式,对于协同开发和有些不用xib公司来说,有些可望不可及. 第三种方式,手写constraints.手写过的猿猿们一定体会到会有多少行代码. 其实还有一种看起来不

简洁的代码

转载自 : http://www.jianshu.com/p/2db0e6b6ecdb 最近在review整个项目的代码,因为代码量很大,参与开发的人很多,所以代码很多地方写得不够简洁.这里总结出一些代码片段,用来简化代码. 1.让TableView多余的Cell不可见.原来的实现:给TableView增加一个空的FooterView.但是当很多地方都需有这个需求时,类似的代码就重复出现. UIView *view = [UIView new]; view.backgroundColor = [

TableView的accessoryButtonTappedForRow方法执行的时机

敲代码时遇到了这个问题,别偷懒,写下来备查. 当你在IB中对TableView中的accessory(注意,我说的是cell中的accessory,而不是cell)创建segue时,如果你在VC中同时实现以下3个方法,请问调用的次序是神马!? //1 func tableView(_ tableView: UITableView, accessoryButtonTappedForRowWith indexPath: IndexPath) //2 override func shouldPerfo

IOS TableView详解

一.建立 UITableView DataTable = [[UITableView alloc] initWithFrame:CGRectMake(0, 0, 320, 420)]; [DataTable setDelegate:self]; [DataTable setDataSource:self]; [self.view addSubview:DataTable]; [DataTable release]; 二.UITableView各Method说明 //Section总数 - (NS

设置tableview的滚动范围--iOS开发系列---项目中成长的知识三

设置tableview的滚动范围 有时候tableview的footerview上的内容需要向上拖动界面一定距离才能够看见, 项目中因为我需要在footerviw上添加一个按钮,而这个按钮又因为这个原因点不中,所以找到了解决办法! 添加如下方法即可 -(void)scrollViewDidScroll:(UIScrollView *)scrollView { self.tableView.contentSize = CGSizeMake(0,MZT_SCREEN_HEIGHT); }

tableView cell性能优化

通过一个标识表去缓冲池中寻找可循环利用的cell 如果缓存池找不到可循环利用的cell,创建一个新的cell,给cell贴个标识 给cell设置新的数据 代码如下cellForRowAtIndexPath方法中 //dequeue查找队列 //cell标识,static修饰局部变量:可以保证局部变量只分配一次存储空间 static NSString *ID = @"A"; UITableViewCell *cell = [tableView dequeueReusableCellWit