如何使不同子视图的UITableViewCell可重用?
我有一个UITableView
,我自然而然地显示了所有相同类的UITableViewCells
,我们称之为MyCell
。 所以我有一个MyCell.xib
,一个MyCell.h
和一个MyCell.m
。
不幸的是,这个单元格包含一个子视图,它包含不同的内容,例如train subview
和car subview
。 所以,如果UITableView
需要一个新的单元格,它总是一个MyCell
但有时它包含一个列车子视图,有时还有一个汽车子视图。
现在,这是我的问题:如何使MyCell
正确的可重用? 单元本身是可重复使用的(在.xib中,我定义了它的标识符),但它的子视图必须一次又一次地为每个单元格创build。 我的第一个想法是改变MyCell
的标识符取决于它的内容,但不幸的是,reuseIdentifier不能在运行时改变。 然而,我可以实现我自己的- (NSString *) reuseIdentifier {}
,我猜这会起作用,尽pipe我不认为它是很棒的风格。 有一个更好的方法吗?
提前谢谢了!
编辑:我知道我需要补充说,子视图存储在他们自己的类/ xibs,以保持他们的代码分离。
我build议你为每一种单元格创build自己的类,而不是给单元格添加子视图。 如果你有种类:火车,汽车,自行车,船和飞机,我会创build五个子类。
据我了解,苹果的标识符重用机制就是这样的情况:不同types的单元格获得自己的标识符,而不是每一个单元格一个特殊的。 只是指出我如何解释整个事情。
在苹果公司的iOS视图编程指南/ 单元对象的特性中 ,第三个paragrpah提供了对重用标识符含义的一些了解。
我写了一个小的TableViewCellFactory类,使我的生活更轻松地使用界面生成器来创build单元格,并在几分钟之内让我的应用程序。
首先是关于如何使用cellForRowAtIndexPath
和工厂以及为单元格设置内容的一个小例子。
我用需要tableView的工厂创build一个新的单元,以便处理重用逻辑。 接下来是让方法填充单元格的内容。 在这种情况下,它是一个显示一些文字的video剪辑的单元格。
数据源委托方法和帮助程序
- (UITableViewCell *)tableView:(UITableView *)aTableView cellForRowAtIndexPath:(NSIndexPath *)anIndexPath { VideoClipTableViewCell *cell = [TableViewCellFactory videoClipTableViewCellWithTableView:aTableView]; [self configureVideoClipCellWithCell:cell andIndexPath:anIndexPath]; // code to decide what kind of cell not shown, but it could be here, just move the model // access code from the configure cell up here and decide on what you get return cell; }
接下来是数据源助手将内容放入单元格。 从我的模型数组中获取内容并设置属性。 请注意,这是通过引用做一切事情,没有任何返回。
- (void)configureVideoClipCellWithCell:(VideoClipTableViewCell *)aCell andIndexPath:(NSIndexPath *)anIndexPath { VideoClip *videoClip = [videoClips objectAtIndex:anIndexPath.row]; aCell.videoTitleLabel.text = videoClip.title; aCell.dateLabel.text = videoClip.date; // more data setting ... }
TableViewFactory
这个类主要包括方便的方法和一些样板方法来做实际的工作。
// Convenience static method to create a VideoClipTableViewCell + (VideoClipTableViewCell *)videoClipTableViewCellWithTableView:(UITableView *)aTableView { return [self loadCellWithName:@"VideoClipTableViewCell" tableView:aTableView]; } // method to simplify cell loading + (id)loadCellWithName:(NSString *)aName tableView:(UITableView *)aTableView { return [self loadCellWithName:aName className:aName identifier:aName tableView:aTableView]; } // method with actually tries to create the cell + (id)loadCellWithName:(NSString *)aName className:(NSString *)aClassName identifier:(NSString *)anIdentifier tableView:(UITableView *)aTableView { UITableViewCell *cell = [aTableView dequeueReusableCellWithIdentifier:anIdentifier]; if (cell == nil) { UINib * nib = [UINib nibWithNibName:aName bundle:nil]; NSArray * nibContent = nil; nibContent = [nib instantiateWithOwner:nil options:nil]; for (id item in nibContent) { if ([item isKindOfClass:NSClassFromString(aClassName)]) { cell = item; } } } return cell; }
我抛出了整个错误和exception处理,只是为了保持这个例子的简短。 如果有人有兴趣,我会添加代码。
关于这个用法的一些重要的事情是:
-
连接的类名称,重用标识符和笔尖名称都是相同的,因此可以使用一个string常量创build单元格,否则必须使用long
loadCellWithName
。 -
不要忘记在界面构build器中设置重用标识符。
-
笔尖只能包含一个TableViewCell(可以用一些编码来改变)
-
不要设置文件所有者的出口,使用那些tableViewCell
-
将单元格的类标识设置为最先创build的相应类
-
看看截图
对自定义单元格进行子类化的想法
将自己的单元划分子类,向其中添加一些属性,在IB中使用sockets提供它们,在IB中为您的nib文件select新的扩展类是很容易的。
主要的问题是接口本身。 在界面构build器中基于自定义单元格来创build不同types的单元格并不容易。 第一种方法是复制笔尖文件,将其重新命名,并将其与所有现有参考文献一起使用,并将新的参考文献链接到不同的出版物。 但是如果基本单元必须改变会发生什么? 通过各种inheritance单元可能是一个乏味的任务。
我只是在界面生成器中使用cocoa爱上的IBPlugins ,偶然发现了自定义视图 。 这是一个很好的教程,如何在IB中扩展组件库。 我们的自定义基本单元可以成为图书馆中的一个项目,并成为我们一直在寻找的模板。 我认为这种方法是正确的select方式。 不过,看着必要的步骤,这不仅仅是在5分钟内完成的。
接口构build器是一个有用的工具,使我们能够快速创build视图,但是当涉及到通过子类化的可重用性时,需要采取很多步骤来创build可维护的视图。 太遗憾了
只用代码创build视图我认为如果涉及多个inheritance级别或许多祖先类只有一个基本视图,则使用子类化会更好。
编辑
另一方面,苹果警告说,在一个单元格中过度使用子视图:
但是,如果一个单元格的内容由三个或四个子视图组成,则滚动性能可能会受到影响。 在这种情况下(特别是如果单元格不可编辑),请考虑直接在单元格的内容视图的一个子视图中绘制。 本指南的要点是,在实现自定义表格视图单元格时,请注意在最佳滚动性能和最佳编辑或重新sorting性能之间进行权衡。
现在任何方法都有其缺点和优点:
-
太男人的子视图会打击performance,很容易用IB来完成
-
使用代码进行绘制会导致难以维护代码库,但性能会更好
-
跳过IB使模板单元类的子类更容易
-
用IB与nib文件很难实现通过子类化的层次结构
有几种不同的方法来做到这一点。 您需要一种方式来访问该子视图,并重新设置或重新使用它。
-
您可以使用您自己的具有火车或汽车视图属性的单元格类来inheritanceUITableViewCell。 这样,当单元格被重用时,您可以访问和更改该视图。
-
为每种types的单元分配一个不同的标识符:
`
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { static NSString *CarCellIdentifier = @"CarCell"; static NSString *TrainCellIdentifier = @"TrainCell"; if(indexPath == carCellNeeded) { UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CarCellIdentifier]; if (cell == nil) { cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CarCellIdentifier] autorelease]; [cell addSubview:carView]; } } else if(indexPath == trainCellNeeded){ UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:TrainCellIdentifier]; if (cell == nil) { cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:TrainCellIdentifier] autorelease]; [cell addSubview:trainView]; } } return cell; }
- 或者为你正在添加的子视图指定一个特殊的标记,当细胞再次回来重新使用时,你可以通过它的标记来访问特定的子视图。
我将两个自定义子视图添加到笔尖,并将它们连接到sockets。 根据内容,当你configuration你的单元格的内容时,我会隐藏其中的一个。
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { static NSString *CellIdentifier = @"CellIdentifier"; UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier]; if (!cell) { cell = /* load from nib */ } if (/*indexPath conditionForTrainCell*/) { cell.trainSubview.hidden = NO; cell.carSubview.hidden = YES; // configure train cell } else { cell.trainSubview.hidden = YES; cell.carSubview.hidden = NO; // configure car cell } return cell; }
最简单的就是制作一个自定义的UITableViewCell子类并为其创build一个xib。 将xib中的根视图设置为uitableviewCell,并将其类设置为您的UITableViewCell子类。 将文件所有者类设置为您的TableViewController子类,并添加所有您想要的子视图。 那么你可以简单地:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { static NSString * CellIdentifier = @"cellIdentifier"; TableViewMessageCell * cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier]; if (cell == nil) { cell = [[[NSBundle mainBundle] loadNibNamed:NSStringFromClass([TableViewMessageCell class]) owner:self options:nil] lastObject]; } Message * message = [self.fetchedResultsController objectAtIndexPath:indexPath]; cell.message = message; return cell; }