Mais conteúdo relacionado
Semelhante a 15 Subclassing UITableViewCell (20)
15 Subclassing UITableViewCell
- 2. • UITableView 显⽰示的是⼀一个 UITableViewCell 的列
表。对于⼤大多数应⽤用⽽而⾔言,带有 textLabel,
detailTextLabel, 和 imageView 的基本单元格够⽤用
• 有时候我们想要能够显⽰示更详细信息,或者⼀一种
不同布局的单元格,这时候我们就要⼦子类化
UITableViewCell
- 5. UIView 和 UITableViewCell 的⼦子类化
• UITableViewCell 也是 UIView 的⼀一个⼦子类。通常我
们⼦子类化 UIView (以及任何它的⼦子类)的时候,
我们会重写它的 drawRect: ⽅方法来⾃自定义 view 的
外形
• 但是⼦子类化 UITableViewCell 的时候,我们并不直
接修改单元格的外观
- 6. ⼦子类化 UITableViewCell 的⽅方法
• 每个单元格都有⼀一个名为 contentView 的⼦子视
图,它是构成⼀一个单元格⼦子类布局的视图对象的
容器
• 也就是说我们可以通过改变在单元格的
contentView 中的视图对象来⼦子类化
UITableViewCell。
- 9. 为什么要加到 contentView?
• 把⼦子视图添加到 contentView ,⽽而不是直接添加
到 UITableViewCell 的⼦子类很重要。因为单元格在
⼀一个特定的时候将会调整 contentView 的⼤大⼩小。
• 例如,当 table view 进⼊入编辑模式时,
contentView 会调整它的⼤大⼩小给编辑控件腾出空
间。
- 13. • 创建⼀一个空的 XIB ⽂文件,命名为
HomepwnerItemCell.xib (Device Family 没有关系)
• 打开 HomepwnerItemCell.xib 并且拖动⼀一个
UITableViewCell 实例到绘制区域
• 这个单元格需要显⽰示三个⽂文本元素和⼀一个图⽚片,
所以我们拖拽三个 UILabel 和 ⼀一个 UIImageView
到单元格上,分别显⽰示名称,价格,序列号和它
的图⽚片的缩略图(把序列号字体调稍⼩小点,颜⾊色深
灰)
- 15. • 因为我们的应⽤用将会运⾏行在 iPhone 和 iPad 上,⽽而
且在 iPad 还有可能是 portrait 和 landscape
oritentation。
• 单元格需要进⾏行横向调整来匹配它所在的窗⼝口⼤大
⼩小。这样我们就必须针对每个⼦子视图设置它的
autosizing mask
• 在 size inspector ⾥里⾯面,我们来按照下⼀一⻚页的图⽰示
来更改 subview 的 autosizing mask
- 17. 更改 Class
• 最后,我们点击 outline view 中的 cell,选择
identity inspector。把它的 Class 改成
HomepwnerItemCell
- 21. • 在 DetailViewController 中,没有 UITextFields 的
outlets 被曝露为属性,因为没有其它的对象会访
问它们。
• 在我们这种情况下,table view 的 data source 必
须配置每⼀一个 subview。通过把这些 subviews 导
出为 properties,数据源(ItemsViewController)
在需要的时候将可以访问。
- 22. • 在 HomepwnerItemCell.xib ⽂文件打开的时候,在
HomepwnerItemCell.h 上 Option-click 。
• 从每⼀一个 subview Control-drag 到
HomepwnerItemCell.h 的⽅方法声明区域。
• 然后给每⼀一个 outlet 命名并且配置连接的每⼀一个
属性,让它们如下图所⽰示:
- 24. • 操作结束后的 HomepwnerItemCell.h 看起来应该
像下⾯面这样:
@interface HomepwnerItemCell : UITableViewCell
@property (weak, nonatomic) IBOutlet UIImageView *thumbnailView;
@property (weak, nonatomic) IBOutlet UILabel *nameLabel;
@property (weak, nonatomic) IBOutlet UILabel *serialNumberLabel;
@property (weak, nonatomic) IBOutlet UILabel *valueLabel;
@end
- 26. • 在 ItemsViewController 的
tableview:cellForRoAtIndexPath: ⽅方法中,我们将
为表格中的每⼀一⾏行创建⼀一个 HomepwnerItemCell
的实例。
• 在 ItemsViewController.h 的顶部,导⼊入
HomepwnerItemCell 的头⽂文件
#import "HomepwnerItemCell.h"
- 27. • 在之前的 tableview:cellForRowAtIndexPath: 的实现
中,我们⾸首先会询问 table view 它是否有可重⽤用
的 cell,然后如果没有的话我们再⽣生成⼀一个全新的
• 当使⽤用⼀一个 XIB ⽂文件来加载 UITableViewCell 的⼦子
类时,这个过程稍有不同:我们在 table 第⼀一次加
载时使⽤用⼀一个给定的 reuse identifier 向
UITableView 来注册这个 XIB ⽂文件。
- 28. // 重写 viewDidLoad 为 HomepwnerItemCell 复⽤用标识注册 HomepwnerItemCell.xib
- (void)viewDidLoad
{
[super viewDidLoad];
// 加载 xib ⽂文件
UINib *nib = [UINib nibWithNibName:@"HomepwnerItemCell" bundle:nil];
// 注册这个包含 cell 的 NIB
[[self tableView] registerNib:nib
forCellReuseIdentifier:@"HomepwnerItemCell"];
}
• 在 ItemsViewController.m 中 重写 viewDidLoad 来
为 HomepwnerItemCell reuse identifier 注册
HomepwnerItemCell.xib
- 29. • 要得到⼀一个 HomepwnerItemCell 的实例,我们向
table view 请求 dequeue ⼀一个符合
HomepwnerItemCell reuse identifier 的 Cell。
• 如果 table 有⼀一个可复⽤用的 HomepwnerItemCell 的
实例,就会返回它;如果没有的话,就会加载
HomepwnerItemCell.xib ,并且给你⼀一个 archived
cell 的实例。
• 在 ItemsViewController.m 中,定位
tableView:cellForRowAtIndexPath: 并进⾏行如下修
改:
- 30. - (UITableViewCell *)tableView:(UITableView *)tableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
// // ⾸首先检查是否有可以重⽤用的单元格,如果存在的化就使⽤用
// UITableViewCell *cell = [tableView
dequeueReusableCellWithIdentifier:@"UITableViewCell"];
// if (!cell) {
// cell = [[UITableViewCell alloc]
initWithStyle:UITableViewCellStyleDefault
reuseIdentifier:@"UITableViewCell"];
// }
// else {
// NSLog(@"重⽤用: %d", [indexPath row]);
// }
BNRItem *p = [[[BNRItemStore defaultStore] allItems]
objectAtIndex:[indexPath row]];
// [[cell textLabel] setText:[p description]];
// 把原来相关的复⽤用代码删掉
// 获得⼀一个新的或者循环使⽤用的 cell
HomepwnerItemCell *cell = [tableView
dequeueReusableCellWithIdentifier:@"HomepwnerItemCell"];
// 使⽤用上⾯面的 BNRItem 配置这个单元格
[[cell nameLabel] setText:[p itemName]];
[[cell serialNumberLabel] setText:[p serialNumber]];
[[cell valueLabel] setText:[NSString stringWithFormat:@"$%d", [p
valueInDollars]]];
return cell;
}
- 35. • 问题是缩略图是 UIImage 的⼀一个实例,⽽而 UIImage
并不符合 NSCoding 的 protocol,所以我们⽆无法在
NSCode 中直接对缩略图进⾏行编码。
• 我们可以把缩略图作为数据(PNG 格式)进⾏行编
码,并且把它封装在⼀一个 NSData 对象中,NSData
是符合 NSCoding 的。
- 36. • 打开 BNRItem.h , 声明两个新的属性:⼀一个
UIImage,⼀一个 NSData。
• 同样我们还需要⼀一个⽅方法来把全尺⼨寸的图⽚片转换
成⼀一个缩略图。
• 在 BNRItem.m 中,synthesize 这两个属性。
@interface BNRItem : NSObject <NSCoding>
{
}
// 分别保存图⽚片和数据的两个属性,以及⼀一个把全尺⼨寸图⽚片转成缩略图的⽅方法
@property (nonatomic, strong) UIImage *thumbnail;
@property (nonatomic, strong) NSData *thumbnailData;
- (void)setThumbnailDataFromImage:(UIImage *)image;
@synthesize thumbnail, thumbnailData;
- 37. • 当我们为 BNRItem 选择⼀一个图⽚片的时候,我们就
把这个图⽚片赋给了 BNRItem。同时我们把它转成
⼀一个缩略图,保存成它的 thumbnail。
• 同时还会⽣生成 PNG 格式的 NSData,并且把它保存
成 thumbnailData。
• thumbnailData 将会随着 BNRItem ⼀一起被
archive,⼀一起被从 archive 中加载,并且从它的数
据来重新创建 thumbnail。
- 38. • 在 BNRItem.m 中,为 thumbnail ⽣生成⼀一个 getter
⽅方法,必要的时候从数据⽣生成它。
// ⾃自定义 getter ⽅方法
- (UIImage *)thumbnail
{
if (!thumbnailData) {
return nil;
}
// 如果还没有从数据中⽣生成缩略图,创建它
if (!thumbnail) {
// 从数据⽣生成图⽚片
thumbnail = [UIImage imageWithData:thumbnailData];
}
return thumbnail;
}
- 40. - (void)setThumbnailDataFromImage:(UIImage *)image
{
CGSize origImageSize = [image size];
// thumnail 的矩形⼤大⼩小
CGRect newRect = CGRectMake(0, 0, 40, 40);
// 计算缩放⽐比
float ratio = MAX(newRect.size.width / origImageSize.width,
newRect.size.height / origImageSize.height);
// ⽣生成⼀一个带缩放因⼦子的透明位图上下⽂文
UIGraphicsBeginImageContextWithOptions(newRect.size, NO, 0.0);
// ⽣生成⼀一个圆⾓角矩形路径
UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:newRect
cornerRadius:5.0];
// 后续绘制 clip 到这个圆⾓角矩形
[path addClip];
// 图⽚片放到缩略图中间
CGRect projectRect;
projectRect.size.width = ratio * origImageSize.width;
projectRect.size.height = ratio * origImageSize.height;
projectRect.origin.x = (newRect.size.width - projectRect.size.width) /
2.0;
projectRect.origin.y = (newRect.size.height - projectRect.size.height) /
2.0;
// 把图⽚片绘制上来
[image drawInRect:projectRect];
// 从图⽚片上下⽂文获得图⽚片,作为我们的缩略图保存
UIImage *smallImage = UIGraphicsGetImageFromCurrentImageContext();
[self setThumbnail:smallImage];
// 得到该图⽚片 PNG 形式,把它作为我们可以 archive 的数据
NSData *data = UIImagePNGRepresentation(smallImage);
[self setThumbnailData:data];
// 完成以后,清除图⽚片上下⽂文资源
UIGraphicsEndImageContext();
}
- 41. • 在 DetailViewController.m 中,修改
imagePickerController:didFinishPickingMediaWithIn
fo:,在采集到原始图⽚片时⽣生成缩略图
- (void)imagePickerController:(UIImagePickerController *)picker
didFinishPickingMediaWithInfo:(NSDictionary *)info
{
// 如果存在就图⽚片,先从 BNRImageStore 中删除
NSString *oldKey = [item imageKey];
if (oldKey) {
[[BNRImageStore sharedStore] deleteImageForkey:oldKey];
}
// 从 info 字典中获得拾取的图⽚片
UIImage *image = [info
objectForKey:UIImagePickerControllerOriginalImage];
// 拾取图⽚片以后⽣生成缩略图
[item setThumbnailDataFromImage:image];
- 42. • 现在我们已经有了⼀一个可以⽤用在
ItemsViewController 的 table view 的缩略图。
• 在 ItemsViewController.m 中,更新
tableView:cellForRowAtIndexPath:
// 使⽤用上⾯面的 BNRItem 配置这个单元格
[[cell nameLabel] setText:[p itemName]];
[[cell serialNumberLabel] setText:[p serialNumber]];
[[cell valueLabel] setText:[NSString stringWithFormat:@"$%d", [p
valueInDollars]]];
// 设置缩略图
[[cell thumbnailView] setImage:[p thumbnail]];
return cell;
}
构建并运⾏行,测试⼀一下缩略图的⽣生成和显⽰示
- 43. • 最后我们来把缩略图的数据添加到 archive,修改
BNRItem.m。然后构建并运⾏行,退出再启动应⽤用
- (void)encodeWithCoder:(NSCoder *)aCoder
{
[aCoder encodeObject:itemName forKey:@"itemName"];
[aCoder encodeObject:serialNumber forKey:@"serialNumber"];
[aCoder encodeObject:dateCreated forKey:@"dateCreated"];
[aCoder encodeObject:imageKey forKey:@"imageKey"];
[aCoder encodeInt:valueInDollars forKey:@"valueInDollars"];
[aCoder encodeObject:thumbnailData forKey:@"thumbnailData"];
}
- (id)initWithCoder:(NSCoder *)aDecoder
{
self = [super init];
if (self) {
[self setItemName:[aDecoder decodeObjectForKey:@"itemName"]];
[self setSerialNumber:[aDecoder
decodeObjectForKey:@"serialNumber"]];
[self setImageKey:[aDecoder decodeObjectForKey:@"imageKey"]];
[self setValueInDollars:[aDecoder
decodeIntForKey:@"valueInDollars"]];
dateCreated = [aDecoder decodeObjectForKey:@"dateCreated"];
thumbnailData = [aDecoder decodeObjectForKey:@"thumbnailData"];
}
return self;
}
- 46. • 打开 HomepwnerItemCell.xib ,在 UIImageView 上
⾯面拖拽⼀一个 UIButton,重新调整这个按钮的⼤大⼩小
让它正好和 UIIImageView 的⼤大⼩小完全⼀一致。
• 然后在 HomepwnerItemCell.h 上 Option-click 打开
assistant editor。从按钮 Control-drag 到⽅方法区
域,然后像下⾯面这样配置⼀一下。
- (IBAction)showImage:(id)sender;
- 48. • 现在的问题是 HomepwnerItemCell 不是⼀一个
controller,⽽而且也⽆无法访问任何拿到全尺⼨寸图⽚片
需要的数据。
• 解决⽅方法是给 HomepwnerItemCell ⼀一个到
ItemsViewController 的指针。
• 这样在 HomepwnerItemCell 收到从按钮来的
action message时,它会发⼀一个新的消息到
ItemsViewController,这样 controller 就可以获取
图⽚片并且把它在 UIPopoverController 中显⽰示。
- 50. • ⾸首先给 HomepwnerItemCell 指向 controller 的指
针,还有⼀一个指向 cell 所在的 table view 的指针。
• 在 HomepwnerItemCell.m 中,synthesizie 这些属
性
@property (weak, nonatomic) id controller;
@property (weak, nonatomic) UITableView *tableView;
@implementation HomepwnerItemCell
@synthesize controller;
@synthesize tableView;
- 51. • 现在我们需要当 cell 被创建时来设置这些属性。
• 在 ItemsViewController.m 中,找到
tableView:cellForRowAtIndexPath: ⽅方法,添加下⾯面
代码:
HomepwnerItemCell *cell = [tableView
dequeueReusableCellWithIdentifier:@"HomepwnerItemCell"];
// 设置 HomepwnerItemCell 中传递消息⽤用的属性
[cell setController:self];
[cell setTableView:tableView];
// 使⽤用上⾯面的 BNRItem 配置这个单元格
[[cell nameLabel] setText:[p itemName]];
- 53. • 在 HomepwnerItemCell.m 中实现 showImage: , 从
table view 拿到它的 index path,并且给 controller
发送 showImage:atIndexPath: 消息
- (IBAction)showImage:(id)sender {
// 拿到该⽅方法的名字,“showImage”
NSString *selector = NSStringFromSelector(_cmd);
// selector 现在变成了 “showImage:atIndexPath:”
selector = [selector stringByAppendingString:@"atIndexPath:"];
// 从字符串准备⼀一个 selector
SEL newSelector = NSSelectorFromString(selector);
// 拿到 indexPath
NSIndexPath *indexPath = [[self tableView] indexPathForCell:self];
// // 调⽤用 controller 的⽅方法
// [[self controller] showImage:sender atIndexPath:indexPath];
// ⾸首先检查
if (indexPath) {
if ([[self controller] respondsToSelector:newSelector]) {
// 使⽤用 performSelector:withObject:withObject: 发送动态消息
[[self controller] performSelector:newSelector withObject:sender
withObject:indexPath];
}
}
}
- 54. • 我们先来验证⼀一下是不是⼀一切⼯工作正常,在
ItemViewController.m 中实现
showImage:atIndexPath: 来打印出⺫⽬目前的 index
path
• 然后构建并运⾏行,点击⼀一个缩略图,看⼀一下控制
台消息
// 实现 showImage:atIndexPath:
- (void)showImage:(id)sender atIndexPath:(NSIndexPath *)ip
{
NSLog(@"即将为 %@ 显⽰示图⽚片", ip);
}
- 56. • 现在 ItemsViewController 需要修改
showImage:atIndexPath: 来提取 BNRItem 和按钮被
按下的单元格相关联的图⽚片
• 然后在 UIPopoverController 中显⽰示它的图⽚片
- 57. • 要在 popover 中显⽰示⼀一个图⽚片,我们需要⼀一个
UIViewController 作为 popover 的 content view
controller,这个 UIViewController 要能够显⽰示图⽚片
• 我们新建⼀一个 UIViewController 的⼦子类,命名为:
ImageViewController, 选择 UIViewController 作为
它的⽗父类,然后勾选“With XIB for user interface”
- 60. • 在配置像具有这种堆叠在⼀一起的视图的 XIB ⽂文件
时,很难对视图进⾏行选择,因为都是完全重叠
的。
• 这时候我们可以从 outline view ⾥里⾯面的 objects 开
始拖拽,⽽而不是从 canvas 区域的可视化部分
• 在 ImageViewController.h 中,给 interface 添加⼤大
括号,然后建⽴立连接。这些连接应该是 weak ⽅方式
的实例变量(imageView, scrollView)
- 61. • 然后在 ImageViewController.h 中增加⼀一个属性来
持有这个 image
@interface ImageViewController : UIViewController
{
__weak IBOutlet UIScrollView *scrollView;
__weak IBOutlet UIImageView *imageView;
}
@property (nonatomic, strong) UIImage *image;
@end
- 63. • ⾸首先在 ImageViewController.m 中,synthesize 这
个 image 属性
• 然后实现 viewWillAppear: 来配置这些视图
@implementation ImageViewController
@synthesize image;
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
CGSize sz = [[self image] size];
[scrollView setContentSize:sz];
[imageView setFrame:CGRectMake(0, 0, sz.width, sz.height)];
[imageView setImage:[self image]];
}
- 64. • popover 完成以后,我们就可以继续完成我们的
showImage:atIndexPath: 了
• ⾸首先在 ItemsViewController.h 中声明它符合
UIPopoverControllerDelegate, 然后给它⼀一个实例
变量来持有这个 popover
@interface ItemsViewController : UITableViewController <UIPopoverControllerDelegate>
{
UIPopoverController *imagePopover;
}
- 66. • 然后完成 showImage:atIndexPath:的实现
// 实现 showImage:atIndexPath:
- (void)showImage:(id)sender atIndexPath:(NSIndexPath *)ip
{
NSLog(@"即将为 %@ 显⽰示图⽚片", ip);
// 实现 image popover
if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPad ) {
// 从 index path 得到 item
BNRItem *i = [[[BNRItemStore defaultStore] allItems] objectAtIndex:[ip row]];
NSString *imageKey = [i imageKey];
// 如果没有图⽚片,就不需要显⽰示
UIImage *img = [[BNRImageStore sharedStore] imageForKey:imageKey];
if (!img) {
return;
}
CGRect rect = [[self view] convertRect:[sender bounds] fromView:sender];
// ⽣生成⼀一个新的 ImageViewController 然后设置它的 image
ImageViewController *ivc = [[ImageViewController alloc] init];
[ivc setImage:img];
// 使⽤用 ImageViewController ⽣生成⼀一个 popover
// 600*600
imagePopover = [[UIPopoverController alloc] initWithContentViewController:ivc];
[imagePopover setDelegate:self];
[imagePopover setPopoverContentSize:CGSizeMake(600, 600)];
[imagePopover presentPopoverFromRect:rect inView:[self view]
permittedArrowDirections:UIPopoverArrowDirectionAny
animated:YES];
}
}
- 67. • 最后,在 ItemsViewController.m 中,实现⽤用户点
击屏幕上任何位置都把 popover 去掉的功能
• 构建并运⾏行,点击缩略图看⼀一下popover⾥里⾯面的
image,点屏幕上的其他任何地⽅方,可以把
popover 关闭
- (void)popoverControllerDidDismissPopover:(UIPopoverController
*)popoverController
{
[imagePopover dismissPopoverAnimated:YES];
imagePopover = nil;
}