深入理解表视图的单元格(cells)
原文连接
<这部分内容有点多,但很重要 ,有不对的欢迎指正! 转载需注明>
一个表视图通过cell对象来绘制可见的单元格,只要这些单元格是可见的就会将他们放入缓存中。Cell继承自UITableViewCell类。如果视图控制器实现了UITabelViewDataSource协议,通过实现tabelView:cellForRowAtIndexPath:方法,数据源会为表视图提供所有cell对象,即单元格。
在本章节,你将会学到:
一个cell对象内部可以划分不同的区域,这些区域表视形式随着表视图模式的不同而发生变化。通常情况下,大部分的表视图都会包含这些内容:文本、图片和其他一些特殊的标示符。图5-1展示了一个cell中最主要的部分。
图5-1表视图中cell对象的区域分布
右侧比较小的区域是为特殊标示符预留的:扩展指示器、细节展示按钮和一些控制视图对象如滑块、开关等,也可以自定义视图。
当你的表视图进入编辑模式的时候,所有的cell对象的控制视图对象(如果你配置了这样的操作装置)会出现在它的左侧,图5-2展示了它的区域变化。
图5-2进入编辑状态下cell各部分的状态
编辑操作控件可以是删除控件(带有红色减号的圆形)或者是插入控件(带有绿色加号的圆形)。Cell的内容为了给编辑操作控件让出一定位置,所以要向右侧移动。如果cell对象配置了排列功能,排列控件出现在cell的右侧,紧靠着cell的扩展视图。排列控件是一组平行线,用来重新排列表视图中的单元格(cell),用户按下排列控件,然后就可以拖拽单元格(cell)。
如果一个cell对象可以重用(非常常见的情况)你可以在storyboard中为它设置一个重用标示符(通常是一个字符串)。在程序执行中,表视图以列队的形式将它们在内部存储下来。当表视图向数据源请求一个cell对象去显示的时候,数据源通过向表视图发送一个dequeueReusableCellWithIdentifier:消息,并传递一个重用标示符,来使用内部列队中的cell对象。数据源会在返回一个cell对象之前设置cell的内容和一些特殊的属性。这个被重用的cell对象可以使性能大大提高,因为它可以避免重复创建。
当列队中存在多个cell对象,并且每个cell对象都又自己的重用标示符,这时你可以将不同类型的cell对象构建成一个表视图。例如,可以将其中一些cell对象设置为预定义的UITableViewCell,它们的内容全是基于图片和文字,而另外一些cell对象可以设置为自定义的UITableViewCell子类,它们的内容全是基于一些特殊的格式。
你有三种途径为表视图创建cell对象。你可以使用一系列现成的cell类型,或者在这些现成的cell对象中添加子视图(可以在Interface Builder中完成),你还可以创建一个继承自UITableViewCell子类来创建cell对象。
使用cell对象的预定义类型
当直接使用UITableViewCell类时,你可以直接使用一系列现成的cell类型。“StandardStyles for Table View Cells”描述了这些标准的cell类型,并且展示了一些图片例子。这些cell类型是一些枚举类型的常量,在UITableViewCell.h文件中声明了这些常量:
typedef enum{
UITableViewCellStyleDefault,
UITableViewCellStyleValue1,
UITableViewCellStyleValue2,
UITableViewCellStyleSubtitle
} UITableViewCellStyle;
这些cell对象中一般包括两种类型的内容:一个或多个字符串文本,有时候还包括一张图片。图5-3展示了文字和图片的大致区域。图片、文字按照从左到右的顺序排列。
图5-3 系统默认的UITabelViewCell类型的cell对象
UITableViewCell类为它的实例cell对象内容定义了三个属性:
由于前两个属性都是标签类型的,所以你可以依据UILabel类属性来设置字体、对齐方式、字体颜色、换行等(也可以设置当cell对象高亮时的字体颜色)。至于imageView属性,你可以设置一个可替换的图片,比如当你的图片视图高亮时要呈现的图片,这些功能UIImageView类都有提供。
图5-4的例子就是使用UITableViewCell类绘制的单元格,它使用UITableViewCellStyleSubtitle这个样式属性,它的子视图包含了一张图片、主标题、副标题。
图5-4 一个带有图片和文字的表视图
列表5-1配置一个带有文本和图像的UITableViewCell对象
- (UITableViewCell *)tableView(UITableView *)tableView cellForRowAtIndexPath(NSIndexPath *)indexPath
{
UITableViewCell *cell = [tabelView dequeueReusableCellWithIdentifier:@”MyIdentifier”];
if (cell == nil)
{
cell = [[UITableViewCell alloc] initWithStyle:UITabelViewStyleSubtitle reuseIdentifier:@”MyIdentifier”];
cell.selectionStyle = UITableViewCellSelectionStyleNone;
}
NSDictionary *item = (NSDictionary *)[self.content objectAtIndex:indexPath.row];
cell.textLabel.text = [item objectForKey:@”mainTitleKey”];
cell.detailTextLabel.text = [item objectForKey:@”secondaryTitleKey”];
NSString *path = [[NSBundle mainBundle] pathForResource:[item objectForKey:@”imageKey”] ofType:@”png”];
UIImage *theImage = [UIImage imageWithContentOfFile:path];
cell.imageView.image = theImage;
return cell;
}
表视图的数据源执行tableView:cellForRowAtIndexPath:方法的时候,即使重用cell对象,也会重新设置cell对象的所有内容。
当你创建一个UITableViewCell对象的时候,你可以设置一些其他属性(但不是必须的),如下:
列表5-2替换cell单元格的背景颜色
- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath {
if (indexPath.row%2 == 0) {
UIColor *altCellColor = [UIColor colorWithWhite:0.7 alpha:0.1];
cell.backgroundColor = altCellColor;
}
}
列表5-2举例说明了表视图API中一个重要的知识点。表视图在绘制单元格之前向它的代理发送了tableView:willDisplayCell:forRowAtIndexPath:消息,如果代理选择执行这个方法,它就会在单元格呈现之前的最后时刻修改cell单元格。通过次方法,代理只能修改表视图的早些时候设置的基本属性,比如选择状态和背景颜色,但不能修改单元格的内容。
四种预定义类型的UITableViewCell对象已经能够满足大部分应用。这些现成的cell单元格对象包含一到两个文本、一张图像和其他扩展控件。可以定义文本的字体、颜色、和其他特征,图像只支持点选和正常状态两种模式。
Cell单元格的灵活、实用,所以预定义类型不能满足所有应用的需求。比如标签文本必须放在单元格内的固定位置,图像必须出现在单元格的左侧。如果你想让单元格拥有不同的组件,并且将它们置于不同的位置,或者你想让单元格拥有不通的行为特征,你有两种途径:
下面将要讨论这两种途径。
通过storyboard加载表视图单元格
在storyboard中,cell单元格可分为动态和静态两种类型。动态单元格:表视图拥有数量较多而且不受限制的cell单元格。静态单元格:表视图中的单元格数量在程序编译阶段就已经能够确定。专门用作详细内容展示的表视图可以采用静态单元格。
你可以直接在表视图对象里设计动态或静态单元格的内容。图5-5展示了storyboard中主表视图和详情展示表视图。例子中,主表视图包含了动态单元格,而详情展示表视图包含静态单元格。
图5-5 一个storyboard中的表视图单元格
下面演示如何往自定义cell单元格里加载数据。
创建动态内容单元格的技巧
在这小节中你将在storyboard中创建一个自定义cell单元格。在程序执行时,数据源会将这些cell单元格排列,一切准备就绪后,便将它们绘制在表视图中。见图5-6
图5-6将自定义的cell单元格绘制到表视图当中
数据源通过两种方式存取cell单元格的子视图。第一种是通过设置UIView类的tag属性存取,另一种是通过outlet输出值存取。尽管使用tag值需要将它与代码里向关联起来,但是还是比较方便的。使用outlet输出值存取需要做稍多工作,因为你需要自定义一个UITableViewCell子类。下面是两种方式的详细描述。
第一种方式:
创建一个项目工程,使用storyboard来为表视图加载自定义cell单元格
列表5-3通过使用tag值为单元格添加数据
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"MyIdentifier"];
UILabel *label;
label = (UILabel *)[cell viewWithTag:1];
label.text = [NSString stringWithFormat:@"%d", indexPath.row];
label = (UILabel *)[cell viewWithTag:2];
label.text = [NSString stringWithFormat:@"%d", NUMBER_OF_ROWS - indexPath.row];
return cell;
}
如果你不太倾向于使用tag属性,你可以使用另一种方法来为cell单元格设置内容。自定义一个UITableViewCell子类,并为你要设置的对象声明outlet属性。
在storyboard中,将单元格里的对象与自定义类中的outlet属性关联起来(也就是连线)。为自定义cell单元格内容使用outlets:
@interface MyTableViewCell : UITableViewCell
@property (nonatomic, weak) IBOutlet UILabel *firstLabel;
@property (nonatomic, weak) IBOutlet UILabel *secondLabel;
@end
@synthesize firstLabel, secondLabel;
#import "MyTableViewCell.h"
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
MyTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"MyIdentifier"];
cell.firstLabel.text = [NSString stringWithFormat:@"%d", indexPath.row];
cell.secondLabel.text = [NSString stringWithFormat:@"%d", NUMBER_OF_ROWS - indexPath.row];
return cell;
}
创建静态内容单元格的技巧
在这部分中,你将创建一个由静态单元格组成的表视图。在程序运行的时,从storyboard中将表视图加载进来,这是表视图会立即获取这些单元格,并将他们分节排列出来。如图5-7.
图5-7多行单元格表视图
像创建动态内容单元格列表一样,首先要为你的项目增加一个UITableViewController子类,然后分别为第一个单元格的label和最后一个单元格的滑块、滑块值lable声明一个outlet属性,如列表5-5.
列表5-5 为静态内容单元格对象声明outlet属性
@interface DetailViewController : UITableViewController
@property (strong, nonatomic) id detailItem;
@property (weak, nonatomic) IBOutlet UILabel *masterRowLabel;
@property (weak, nonatomic) IBOutlet UILabel *sliderValueLabel;
@property (weak, nonatomic) IBOutlet UISlider *slider;
- (IBAction)logHello;
- (IBAction)sliderValueChanged:(UISlider *)slider;
@end
图5-8 为你的静态内容单元格内容连线
接下来为静态单元格列表部署数据,在DetailViewController.m文件中实现一个configureView()方法。在这个例子中detailItem的值为由主视图控制器中prepareForSegue:sender:方法传递过来的一个NSString类型的字符串。这个字符串为单元格的数量。
列表5-6 为用户界面设置数据
- (void)configureView
{
if (self.detailItem) {
self.masterRowLabel.text = [self.detailItem description];
}
self.sliderValueLabel.text = [NSString stringWithFormat:@"%1.1f", self.slider.value];
}
通过编程的方式为单元格视图添加子视图
一个表视图中的cell单元格都是也一个视图(UITableViewCell继承自UIView)。作为一个视图,它拥有一个contentView属性。可以将子视图通过单元格contentView属性添加到单元格中。然后调整它们的位置,这里的坐标是相对于它们的父视图坐标来定位。你可以在编程中部署他们,也可以像前面叙述那样在Interface Builder中部署。
用编程的方式部署相对来说比较简单。它不需要你去自定义一个UITableViewCell的子类去控制所有自定义视图的实现。如果你使用这种途径,尽可能的避免将这些子视图设为透明的,透明视图影响滑动时的性能,子视图应该是不透明的,并且跟cell单元格拥有同样的背景颜色。如果表视图的单元格是可选的,当单元格选中的时,一定要确保它的子视图也为选中状态。如果子视图设置了highlighted属性,它的子视图会自动变成选中状态。
你可能想自动定义你的文本和图像在cell单元格中的位置。比如,你想让图像向右对齐,主标题和副标题紧靠着图像的左侧。像图5-9这样。
图5-9 为cell单元格自定义子视图
列表5-7的代码例子,展示了数据源如何通过编程的方式来绘制和排版表视图的cell单元格。在tableView:cellForRowAtIndexPath:方法中,首先检查表视图中是否已经存在带有重用标示符的cell单元格对象。如果不存在,数据源将创建一个,并为它添加两个label标签和一个imageView视图作为其子视图,这里注意子视图的坐标是相对于它的父视图坐标。然后为这些对象设置属性。
列表5-7为cell单元格的内容视图添加子视图
#define MAINLABEL_TAG 1
#define SECONDLABEL_TAG 2
#define PHOTO_TAG 3
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = @"ImageOnRightCell";
UILabel *mainLabel, *secondLabel;
UIImageView *photo;
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
cell.accessoryType = UITableViewCellAccessoryDetailDisclosureButton;
mainLabel = [[[UILabel alloc] initWithFrame:CGRectMake(0.0, 0.0, 220.0, 15.0)]];
mainLabel.tag = MAINLABEL_TAG;
mainLabel.font = [UIFont systemFontOfSize:14.0];
mainLabel.textAlignment = UITextAlignmentRight;
mainLabel.textColor = [UIColor blackColor];
mainLabel.autoresizingMask = UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleHeight;
[cell.contentView addSubview:mainLabel];
secondLabel = [[[UILabel alloc] initWithFrame:CGRectMake(0.0, 20.0, 220.0, 25.0)]];
secondLabel.tag = SECONDLABEL_TAG;
secondLabel.font = [UIFont systemFontOfSize:12.0];
secondLabel.textAlignment = UITextAlignmentRight;
secondLabel.textColor = [UIColor darkGrayColor];
secondLabel.autoresizingMask = UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleHeight;
[cell.contentView addSubview:secondLabel];
photo = [[[UIImageView alloc] initWithFrame:CGRectMake(225.0, 0.0, 80.0, 45.0)]];
photo.tag = PHOTO_TAG;
photo.autoresizingMask = UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleHeight;
[cell.contentView addSubview:photo];
} else {
mainLabel = (UILabel *)[cell.contentView viewWithTag:MAINLABEL_TAG];
secondLabel = (UILabel *)[cell.contentView viewWithTag:SECONDLABEL_TAG];
photo = (UIImageView *)[cell.contentView viewWithTag:PHOTO_TAG];
}
NSDictionary *aDict = [self.list objectAtIndex:indexPath.row];
mainLabel.text = [aDict objectForKey:@"mainTitleKey"];
secondLabel.text = [aDict objectForKey:@"secondaryTitleKey"];
NSString *imagePath = [[NSBundle mainBundle] pathForResource:[aDict objectForKey:@"imageKey"] ofType:@"png"];
UIImage *theImage = [UIImage imageWithContentsOfFile:imagePath];
photo.image = theImage;
return cell;
}
当数据源创建cell单元格的时候,它会为每个单元格的子视图数值一个tag属性。使用这个tag值通过viewWithTag:方法可以在单元格的主视图层级中找到相对应的子视图的引用,之后便可以修改相应的内容。
这段代码创建了一个预定义样式的UITableViewCell对象(UITableViewCellStyleDefault)。因为预定义样式单元格的内容(textLabel,detailTextLabel,imageView)属性初始值为nil,所以你可以使用任何预定义样式的单元格作为你自定义单元格的模版。