SlideShare uma empresa Scribd logo
1 de 67
Baixar para ler offline
创建 UITableViewCell 的⼦子类




范圣刚,princetoad@gmail.com, www.tfan.org
• UITableView 显⽰示的是⼀一个 UITableViewCell 的列
 表。对于⼤大多数应⽤用⽽而⾔言,带有 textLabel,
 detailTextLabel, 和 imageView 的基本单元格够⽤用

• 有时候我们想要能够显⽰示更详细信息,或者⼀一种
 不同布局的单元格,这时候我们就要⼦子类化
 UITableViewCell
HomepwnerItemCell
• 这⼀一节我们来创建⼀一个名为 HomepwnerCell 的可
 以更有⼒力的显⽰示 BNRItem 实例的 UITableViewCell
 的⼦子类

• 每⼀一个单元格将会显⽰示 BNRItem 的名称,以美元
 记的价格,以及它的图⽚片的⼀一个缩略图
创建 HomepwnerItemCell
UIView 和 UITableViewCell 的⼦子类化
• UITableViewCell 也是 UIView 的⼀一个⼦子类。通常我
 们⼦子类化 UIView (以及任何它的⼦子类)的时候,
 我们会重写它的 drawRect: ⽅方法来⾃自定义 view 的
 外形
• 但是⼦子类化 UITableViewCell 的时候,我们并不直
 接修改单元格的外观
⼦子类化 UITableViewCell 的⽅方法
• 每个单元格都有⼀一个名为 contentView 的⼦子视
 图,它是构成⼀一个单元格⼦子类布局的视图对象的
 容器
• 也就是说我们可以通过改变在单元格的
 contentView 中的视图对象来⼦子类化
 UITableViewCell。
• ⽐比⽅方说,我们可以创建 UITextField,UILabel 和
 UIButton 的实例并把它们加到 contentView

• 甚⾄至我们可以创建⼀一个 UIView 的⼦子类,重写它的
 drawRect: ,然后添加⼀一个它的实例到
 contentView
HomepwnerItemCell hierarchy
为什么要加到 contentView?
• 把⼦子视图添加到 contentView ,⽽而不是直接添加
 到 UITableViewCell 的⼦子类很重要。因为单元格在
 ⼀一个特定的时候将会调整 contentView 的⼤大⼩小。
• 例如,当 table view 进⼊入编辑模式时,
 contentView 会调整它的⼤大⼩小给编辑控件腾出空
 间。
• 如果我们直接把⼦子视图加到了 UITableViewCell, 编
 辑控件将会遮掩这些⼦子视图。

• 单元格在进⼊入编辑模式时不能调整它的⼤大⼩小,但
 是 contentView 可以并且也是这么做的。
HomepwnerItemCell 类




• 打开 Homepwner.xcodeproj。⽣生成⼀一个新的
 UITableViewCell 的⼦子类,命名为
 HomepwnerItemCell
配置 UITableViewCell ⼦子类的界⾯面
• 创建⼀一个空的 XIB ⽂文件,命名为
 HomepwnerItemCell.xib (Device Family 没有关系)

• 打开 HomepwnerItemCell.xib 并且拖动⼀一个
 UITableViewCell 实例到绘制区域

• 这个单元格需要显⽰示三个⽂文本元素和⼀一个图⽚片,
 所以我们拖拽三个 UILabel 和 ⼀一个 UIImageView
 到单元格上,分别显⽰示名称,价格,序列号和它
 的图⽚片的缩略图(把序列号字体调稍⼩小点,颜⾊色深
 灰)
单元格上控件的⼤大⼩小问题
• ⾸首先我们来看⼀一下单元格⼤大⼩小的问题。
• 虽然单元格在 XIB ⽂文件上具有特定的⼤大⼩小,但是
我们并不知道在应⽤用程序⾥里⾯面实际的宽度和⾼高度
会是多少。
• 因为我们的应⽤用将会运⾏行在 iPhone 和 iPad 上,⽽而
 且在 iPad 还有可能是 portrait 和 landscape
 oritentation。

• 单元格需要进⾏行横向调整来匹配它所在的窗⼝口⼤大
 ⼩小。这样我们就必须针对每个⼦子视图设置它的
 autosizing mask

• 在 size inspector ⾥里⾯面,我们来按照下⼀一⻚页的图⽰示
 来更改 subview 的 autosizing mask
HomepwnerItemCell 的Autoresizing masks




• image,name 和 serialNumber 位置都固定在左上
 ⾓角;价格标签位置固定在右上⾓角;

• name 和 serialNumber 宽度横向延伸
更改 Class
• 最后,我们点击 outline view 中的 cell,选择
 identity inspector。把它的 Class 改成
 HomepwnerItemCell
导出 HomepwnerItemCell 的属性
• 现在这个单元格视觉效果看起来还不错,但是我
们必须让它能够被⽤用起来。

• 也就是说当我们在
tableView:cellForRowAtIndexPath: ⾥里⾯面创建⼀一个
HomepwnerItemCell 的实例时,我们需要能够设
置每⼀一个 label 的 text 属性,以及 UIImageView 的
image。
• 下⼀一步我们就是要来为每⼀一个⼦子视图创建和连接
 HomepwnerItemCell 上的 outlets。

• 我们还是使⽤用前⼏几节⽤用到的 Controll-dragging 到
 源⽂文件的⽅方式来创建它们的 outlets。

• 但是对于 HomepwnerItemCell 的 outlets,情况⼜又
 有稍许不同:它们将会是 properites,⽽而不是简单
 的实例变量。
• 在 DetailViewController 中,没有 UITextFields 的
 outlets 被曝露为属性,因为没有其它的对象会访
 问它们。

• 在我们这种情况下,table view 的 data source 必
 须配置每⼀一个 subview。通过把这些 subviews 导
 出为 properties,数据源(ItemsViewController)
 在需要的时候将可以访问。
• 在 HomepwnerItemCell.xib ⽂文件打开的时候,在
 HomepwnerItemCell.h 上 Option-click 。

• 从每⼀一个 subview Control-drag 到
 HomepwnerItemCell.h 的⽅方法声明区域。

• 然后给每⼀一个 outlet 命名并且配置连接的每⼀一个
 属性,让它们如下图所⽰示:
Connections
• 操作结束后的 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
使⽤用 HomepwnerItemCell
• 在 ItemsViewController 的
   tableview:cellForRoAtIndexPath: ⽅方法中,我们将
   为表格中的每⼀一⾏行创建⼀一个 HomepwnerItemCell
   的实例。

 • 在 ItemsViewController.h 的顶部,导⼊入
   HomepwnerItemCell 的头⽂文件

#import "HomepwnerItemCell.h"
• 在之前的 tableview:cellForRowAtIndexPath: 的实现
 中,我们⾸首先会询问 table view 它是否有可重⽤用
 的 cell,然后如果没有的话我们再⽣生成⼀一个全新的

• 当使⽤用⼀一个 XIB ⽂文件来加载 UITableViewCell 的⼦子
 类时,这个过程稍有不同:我们在 table 第⼀一次加
 载时使⽤用⼀一个给定的 reuse identifier 向
 UITableView 来注册这个 XIB ⽂文件。
// 重写 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
• 要得到⼀一个 HomepwnerItemCell 的实例,我们向
 table view 请求 dequeue ⼀一个符合
 HomepwnerItemCell reuse identifier 的 Cell。

• 如果 table 有⼀一个可复⽤用的 HomepwnerItemCell 的
 实例,就会返回它;如果没有的话,就会加载
 HomepwnerItemCell.xib ,并且给你⼀一个 archived
 cell 的实例。

• 在 ItemsViewController.m 中,定位
 tableView:cellForRowAtIndexPath: 并进⾏行如下修
 改:
- (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;
}
• 构建并运⾏行,单元格将会如下图所⽰示:




• 要注意的两个问题:第⼀一,我们前⾯面使⽤用 UINib 来
 进⾏行注册,XIB 的初始化底层调⽤用的也是 Nib;第
 ⼆二,我们前⾯面的 XIB ⽂文件没有设置 File’s Owner, 因
 为 table view 注册的时候只会扫描
 UITableViewCell。所以 XIB ⽂文件⾥里⾯面也不能放置多
 个 UITableViewCell。
图像操作
• 要在单元格⾥里⾯面使⽤用图像,我们可以使⽤用 image
的缩略图。

• 要创建⼀一个 BNRItem 图像的缩略图,我们需要绘
制⼀一个全尺⼨寸图⽚片 scale-down 的版本到 offscreen
context, 然后在 BNRItem 实例中保持⼀一个指向这
个新图⽚片的指针。
• 同样我们也需要⼀一个位置来存储这个缩略图,以
 便在系统重启后可以重新加载。

• 全尺⼨寸的图⽚片我们保存在 BNRImageStore 中,必
 要的时候我们可以把它 flush 掉;⽽而缩略图⾜足够
 ⼩小,所以我们可以直接把它和 BNRItem 的其他实
 例变量⼀一起 archive 起来。
• 问题是缩略图是 UIImage 的⼀一个实例,⽽而 UIImage
 并不符合 NSCoding 的 protocol,所以我们⽆无法在
 NSCode 中直接对缩略图进⾏行编码。

• 我们可以把缩略图作为数据(PNG 格式)进⾏行编
 码,并且把它封装在⼀一个 NSData 对象中,NSData
 是符合 NSCoding 的。
• 打开 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;
• 当我们为 BNRItem 选择⼀一个图⽚片的时候,我们就
 把这个图⽚片赋给了 BNRItem。同时我们把它转成
 ⼀一个缩略图,保存成它的 thumbnail。

• 同时还会⽣生成 PNG 格式的 NSData,并且把它保存
 成 thumbnailData。

• thumbnailData 将会随着 BNRItem ⼀一起被
 archive,⼀一起被从 archive 中加载,并且从它的数
 据来重新创建 thumbnail。
• 在 BNRItem.m 中,为 thumbnail ⽣生成⼀一个 getter
     ⽅方法,必要的时候从数据⽣生成它。
// ⾃自定义 getter ⽅方法
- (UIImage *)thumbnail
{
    if (!thumbnailData) {
        return nil;
    }

     // 如果还没有从数据中⽣生成缩略图,创建它
     if (!thumbnail) {
         // 从数据⽣生成图⽚片
         thumbnail = [UIImage imageWithData:thumbnailData];
     }
     return thumbnail;
}
• 现在我们来实现 setThumbnailDataFromImage: ⽅方
 法

• 这个⽅方法将把⼀一个全尺⼨寸图⽚片⽣生成为缩略图,然
 后把 thumbnail 指向这个图⽚片
- (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();
}
• 在 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];
• 现在我们已经有了⼀一个可以⽤用在
     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;
}


构建并运⾏行,测试⼀一下缩略图的⽣生成和显⽰示
• 最后我们来把缩略图的数据添加到 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;
}
从 UITableViewCells 转发 action
• 我们希望⽤用户点击单元格中的⼀一个缩略图以后能
够看到⼀一个全尺⼨寸的图⽚片。

• 可以在 thumbnail 上⾯面加⼀一个透明的按钮
• 点击这个按钮以后,如果应⽤用运⾏行的 iPad 之上,
将会使⽤用 UIPopoverController 显⽰示⼀一个全尺⼨寸的
图⽚片。
• 打开 HomepwnerItemCell.xib ,在 UIImageView 上
      ⾯面拖拽⼀一个 UIButton,重新调整这个按钮的⼤大⼩小
      让它正好和 UIIImageView 的⼤大⼩小完全⼀一致。

    • 然后在 HomepwnerItemCell.h 上 Option-click 打开
      assistant editor。从按钮 Control-drag 到⽅方法区
      域,然后像下⾯面这样配置⼀一下。




- (IBAction)showImage:(id)sender;
• 为了不让这个按钮掩盖下⾯面的图⽚片,在 attributes
inspector ⾥里⾯面,把按钮的类型改成 Custom。

• 默认情况下,⼀一个 custom 的按钮不做任何绘制,
也就是说它是⼀一个透明的,上⾯面什么也没有的按
钮。
• 现在的问题是 HomepwnerItemCell 不是⼀一个
 controller,⽽而且也⽆无法访问任何拿到全尺⼨寸图⽚片
 需要的数据。

• 解决⽅方法是给 HomepwnerItemCell ⼀一个到
 ItemsViewController 的指针。

• 这样在 HomepwnerItemCell 收到从按钮来的
 action message时,它会发⼀一个新的消息到
 ItemsViewController,这样 controller 就可以获取
 图⽚片并且把它在 UIPopoverController 中显⽰示。
增加指针到单元格⼦子类
• ⾸首先给 HomepwnerItemCell 指向 controller 的指
    针,还有⼀一个指向 cell 所在的 table view 的指针。

 • 在 HomepwnerItemCell.m 中,synthesizie 这些属
    性
@property (weak, nonatomic) id controller;
@property (weak, nonatomic) UITableView *tableView;



@implementation HomepwnerItemCell
@synthesize controller;
@synthesize tableView;
• 现在我们需要当 cell 被创建时来设置这些属性。
 • 在 ItemsViewController.m 中,找到
   tableView:cellForRowAtIndexPath: ⽅方法,添加下⾯面
   代码:

    HomepwnerItemCell *cell = [tableView
dequeueReusableCellWithIdentifier:@"HomepwnerItemCell"];

    // 设置 HomepwnerItemCell 中传递消息⽤用的属性
    [cell setController:self];
    [cell setTableView:tableView];

    // 使⽤用上⾯面的 BNRItem 配置这个单元格
    [[cell nameLabel] setText:[p itemName]];
转发消息到控制器
• 现在单元格已经知道它的控制器和它要在上⾯面显
 ⽰示的 table view 了
• 当 showImage: 消息被发送到 HomepwnerItemCell
 时,我们希望 HomepwnerItemCell 可以告诉
 ItemsViewController 为在 index path 位置的单元
 格显⽰示图⽚片
• 在 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];
        }
    }
}
• 我们先来验证⼀一下是不是⼀一切⼯工作正常,在
   ItemViewController.m 中实现
   showImage:atIndexPath: 来打印出⺫⽬目前的 index
   path

 • 然后构建并运⾏行,点击⼀一个缩略图,看⼀一下控制
   台消息

// 实现 showImage:atIndexPath:
- (void)showImage:(id)sender atIndexPath:(NSIndexPath *)ip
{
    NSLog(@"即将为 %@ 显⽰示图⽚片", ip);
}
在popover controller中显⽰示图像
• 现在 ItemsViewController 需要修改
 showImage:atIndexPath: 来提取 BNRItem 和按钮被
 按下的单元格相关联的图⽚片

• 然后在 UIPopoverController 中显⽰示它的图⽚片
• 要在 popover 中显⽰示⼀一个图⽚片,我们需要⼀一个
 UIViewController 作为 popover 的 content view
 controller,这个 UIViewController 要能够显⽰示图⽚片

• 我们新建⼀一个 UIViewController 的⼦子类,命名为:
 ImageViewController, 选择 UIViewController 作为
 它的⽗父类,然后勾选“With XIB for user interface”
• 打开新建的这个 ImageViewController.xib, ⾸首先拖
 拽⼀一个 UIScrollView 到 View 上;然后拖拽⼀一个
 UIImageView 到 UIScrollView 上
ImageViewController XIB
• 在配置像具有这种堆叠在⼀一起的视图的 XIB ⽂文件
 时,很难对视图进⾏行选择,因为都是完全重叠
 的。

• 这时候我们可以从 outline view ⾥里⾯面的 objects 开
 始拖拽,⽽而不是从 canvas 区域的可视化部分

• 在 ImageViewController.h 中,给 interface 添加⼤大
 括号,然后建⽴立连接。这些连接应该是 weak ⽅方式
 的实例变量(imageView, scrollView)
• 然后在 ImageViewController.h 中增加⼀一个属性来
     持有这个 image

@interface ImageViewController : UIViewController
{
    __weak IBOutlet UIScrollView *scrollView;

     __weak IBOutlet UIImageView *imageView;
}

@property (nonatomic, strong) UIImage *image;
@end
• 当⼀一个 ImageViewController 的实例被创建时,它
 会被赋予⼀一个 image。

• 显⽰示的时候,它将会调整 imageView 来适应图
 ⽚片,然后告诉 scrollView 来更新它的 content size
 来适配。
• ⾸首先在 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]];
}
• popover 完成以后,我们就可以继续完成我们的
        showImage:atIndexPath: 了

     • ⾸首先在 ItemsViewController.h 中声明它符合
        UIPopoverControllerDelegate, 然后给它⼀一个实例
        变量来持有这个 popover

@interface ItemsViewController : UITableViewController <UIPopoverControllerDelegate>
{
    UIPopoverController *imagePopover;
}
• 然后在 ItemsViewController.m 顶部导⼊入需要的头
    ⽂文件

// 为 image popover 导⼊入相应头⽂文件
#import "BNRImageStore.h"
#import "ImageViewController.h"
• 然后完成 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];
    }
}
• 最后,在 ItemsViewController.m 中,实现⽤用户点
   击屏幕上任何位置都把 popover 去掉的功能

 • 构建并运⾏行,点击缩略图看⼀一下popover⾥里⾯面的
   image,点屏幕上的其他任何地⽅方,可以把
   popover 关闭

- (void)popoverControllerDidDismissPopover:(UIPopoverController
*)popoverController
{
    [imagePopover dismissPopoverAnimated:YES];
    imagePopover = nil;
}

Mais conteúdo relacionado

Destaque (6)

08 Notification and Rotation
08 Notification and Rotation08 Notification and Rotation
08 Notification and Rotation
 
iOS: Implementing a Custom View
iOS: Implementing a Custom ViewiOS: Implementing a Custom View
iOS: Implementing a Custom View
 
01 A Simple iOS Application
01 A Simple iOS Application01 A Simple iOS Application
01 A Simple iOS Application
 
04 Delegation and Core Location
04 Delegation and Core Location04 Delegation and Core Location
04 Delegation and Core Location
 
06 Subclassing UIView and UIScrollView
06 Subclassing UIView and UIScrollView06 Subclassing UIView and UIScrollView
06 Subclassing UIView and UIScrollView
 
PhoneGap 通信原理和插件系统
PhoneGap 通信原理和插件系统PhoneGap 通信原理和插件系统
PhoneGap 通信原理和插件系统
 

Semelhante a 15 Subclassing UITableViewCell

各種酷炫圖表繪製技術 Silverlight Toolkit 與 MS Chart 控制項大探索
各種酷炫圖表繪製技術 Silverlight Toolkit 與 MS Chart 控制項大探索各種酷炫圖表繪製技術 Silverlight Toolkit 與 MS Chart 控制項大探索
各種酷炫圖表繪製技術 Silverlight Toolkit 與 MS Chart 控制項大探索
Chui-Wen Chiu
 

Semelhante a 15 Subclassing UITableViewCell (20)

16 CoreData
16 CoreData16 CoreData
16 CoreData
 
I os 09
I os 09I os 09
I os 09
 
I os 14
I os 14I os 14
I os 14
 
11 UINavigationController
11 UINavigationController11 UINavigationController
11 UINavigationController
 
12 Camera
12 Camera12 Camera
12 Camera
 
iOS App 開發 -- Storybard 基礎練習、APP 上架、IAP
iOS App 開發 -- Storybard 基礎練習、APP 上架、IAPiOS App 開發 -- Storybard 基礎練習、APP 上架、IAP
iOS App 開發 -- Storybard 基礎練習、APP 上架、IAP
 
10 Editing UITableView
10 Editing UITableView10 Editing UITableView
10 Editing UITableView
 
I os 07
I os 07I os 07
I os 07
 
005
005005
005
 
13 UIPopoverController and Modal View Controller
13 UIPopoverController and Modal View Controller13 UIPopoverController and Modal View Controller
13 UIPopoverController and Modal View Controller
 
I os 01
I os 01I os 01
I os 01
 
I os 05
I os 05I os 05
I os 05
 
I os 16
I os 16I os 16
I os 16
 
掌星 移动互联网开发笔记-Vol001
掌星 移动互联网开发笔记-Vol001掌星 移动互联网开发笔记-Vol001
掌星 移动互联网开发笔记-Vol001
 
Core data lightweight_migration
Core data lightweight_migrationCore data lightweight_migration
Core data lightweight_migration
 
各種酷炫圖表繪製技術 Silverlight Toolkit 與 MS Chart 控制項大探索
各種酷炫圖表繪製技術 Silverlight Toolkit 與 MS Chart 控制項大探索各種酷炫圖表繪製技術 Silverlight Toolkit 與 MS Chart 控制項大探索
各種酷炫圖表繪製技術 Silverlight Toolkit 與 MS Chart 控制項大探索
 
I os 10
I os 10I os 10
I os 10
 
iPhone,ios,Object-C基础入门
iPhone,ios,Object-C基础入门iPhone,ios,Object-C基础入门
iPhone,ios,Object-C基础入门
 
iPhone,ios,Object-c基础入门
iPhone,ios,Object-c基础入门iPhone,ios,Object-c基础入门
iPhone,ios,Object-c基础入门
 
02 Objective-C
02 Objective-C02 Objective-C
02 Objective-C
 

Mais de Tom Fan

HTML5 Web workers
HTML5 Web workersHTML5 Web workers
HTML5 Web workers
Tom Fan
 
Web sockets
Web socketsWeb sockets
Web sockets
Tom Fan
 
Semantics
SemanticsSemantics
Semantics
Tom Fan
 
Multimedia
MultimediaMultimedia
Multimedia
Tom Fan
 
Intro to-html5
Intro to-html5Intro to-html5
Intro to-html5
Tom Fan
 
Html5 history
Html5 historyHtml5 history
Html5 history
Tom Fan
 
Geolocation
GeolocationGeolocation
Geolocation
Tom Fan
 
File api
File apiFile api
File api
Tom Fan
 
Deviceaccess
DeviceaccessDeviceaccess
Deviceaccess
Tom Fan
 
Webstorage
WebstorageWebstorage
Webstorage
Tom Fan
 
Html5 最重要的部分
Html5 最重要的部分Html5 最重要的部分
Html5 最重要的部分
Tom Fan
 
AT&T 的 HTML5 策略和应用现状
AT&T 的 HTML5 策略和应用现状AT&T 的 HTML5 策略和应用现状
AT&T 的 HTML5 策略和应用现状
Tom Fan
 
PhoneGap 2.0 开发
PhoneGap 2.0 开发PhoneGap 2.0 开发
PhoneGap 2.0 开发
Tom Fan
 
Android 平台 HTML5 应用开发
Android 平台 HTML5 应用开发Android 平台 HTML5 应用开发
Android 平台 HTML5 应用开发
Tom Fan
 
HTML5 生态系统和应用架构模型
HTML5 生态系统和应用架构模型HTML5 生态系统和应用架构模型
HTML5 生态系统和应用架构模型
Tom Fan
 

Mais de Tom Fan (20)

HTML5 Web workers
HTML5 Web workersHTML5 Web workers
HTML5 Web workers
 
Web sockets
Web socketsWeb sockets
Web sockets
 
Storage
StorageStorage
Storage
 
Semantics
SemanticsSemantics
Semantics
 
Multimedia
MultimediaMultimedia
Multimedia
 
Intro to-html5
Intro to-html5Intro to-html5
Intro to-html5
 
Html5 history
Html5 historyHtml5 history
Html5 history
 
Geolocation
GeolocationGeolocation
Geolocation
 
File api
File apiFile api
File api
 
Deviceaccess
DeviceaccessDeviceaccess
Deviceaccess
 
Css3
Css3Css3
Css3
 
Webstorage
WebstorageWebstorage
Webstorage
 
Html5 最重要的部分
Html5 最重要的部分Html5 最重要的部分
Html5 最重要的部分
 
AT&T 的 HTML5 策略和应用现状
AT&T 的 HTML5 策略和应用现状AT&T 的 HTML5 策略和应用现状
AT&T 的 HTML5 策略和应用现状
 
PhoneGap 2.0 开发
PhoneGap 2.0 开发PhoneGap 2.0 开发
PhoneGap 2.0 开发
 
Android 平台 HTML5 应用开发
Android 平台 HTML5 应用开发Android 平台 HTML5 应用开发
Android 平台 HTML5 应用开发
 
HTML5 生态系统和应用架构模型
HTML5 生态系统和应用架构模型HTML5 生态系统和应用架构模型
HTML5 生态系统和应用架构模型
 
18 NSUserDefaults
18 NSUserDefaults18 NSUserDefaults
18 NSUserDefaults
 
17 Localization
17 Localization17 Localization
17 Localization
 
14 Saving Loading and Application States
14 Saving Loading and Application States14 Saving Loading and Application States
14 Saving Loading and Application States
 

15 Subclassing UITableViewCell

  • 2. • UITableView 显⽰示的是⼀一个 UITableViewCell 的列 表。对于⼤大多数应⽤用⽽而⾔言,带有 textLabel, detailTextLabel, 和 imageView 的基本单元格够⽤用 • 有时候我们想要能够显⽰示更详细信息,或者⼀一种 不同布局的单元格,这时候我们就要⼦子类化 UITableViewCell
  • 3. HomepwnerItemCell • 这⼀一节我们来创建⼀一个名为 HomepwnerCell 的可 以更有⼒力的显⽰示 BNRItem 实例的 UITableViewCell 的⼦子类 • 每⼀一个单元格将会显⽰示 BNRItem 的名称,以美元 记的价格,以及它的图⽚片的⼀一个缩略图
  • 5. UIView 和 UITableViewCell 的⼦子类化 • UITableViewCell 也是 UIView 的⼀一个⼦子类。通常我 们⼦子类化 UIView (以及任何它的⼦子类)的时候, 我们会重写它的 drawRect: ⽅方法来⾃自定义 view 的 外形 • 但是⼦子类化 UITableViewCell 的时候,我们并不直 接修改单元格的外观
  • 6. ⼦子类化 UITableViewCell 的⽅方法 • 每个单元格都有⼀一个名为 contentView 的⼦子视 图,它是构成⼀一个单元格⼦子类布局的视图对象的 容器 • 也就是说我们可以通过改变在单元格的 contentView 中的视图对象来⼦子类化 UITableViewCell。
  • 7. • ⽐比⽅方说,我们可以创建 UITextField,UILabel 和 UIButton 的实例并把它们加到 contentView • 甚⾄至我们可以创建⼀一个 UIView 的⼦子类,重写它的 drawRect: ,然后添加⼀一个它的实例到 contentView
  • 9. 为什么要加到 contentView? • 把⼦子视图添加到 contentView ,⽽而不是直接添加 到 UITableViewCell 的⼦子类很重要。因为单元格在 ⼀一个特定的时候将会调整 contentView 的⼤大⼩小。 • 例如,当 table view 进⼊入编辑模式时, contentView 会调整它的⼤大⼩小给编辑控件腾出空 间。
  • 10. • 如果我们直接把⼦子视图加到了 UITableViewCell, 编 辑控件将会遮掩这些⼦子视图。 • 单元格在进⼊入编辑模式时不能调整它的⼤大⼩小,但 是 contentView 可以并且也是这么做的。
  • 11. HomepwnerItemCell 类 • 打开 Homepwner.xcodeproj。⽣生成⼀一个新的 UITableViewCell 的⼦子类,命名为 HomepwnerItemCell
  • 13. • 创建⼀一个空的 XIB ⽂文件,命名为 HomepwnerItemCell.xib (Device Family 没有关系) • 打开 HomepwnerItemCell.xib 并且拖动⼀一个 UITableViewCell 实例到绘制区域 • 这个单元格需要显⽰示三个⽂文本元素和⼀一个图⽚片, 所以我们拖拽三个 UILabel 和 ⼀一个 UIImageView 到单元格上,分别显⽰示名称,价格,序列号和它 的图⽚片的缩略图(把序列号字体调稍⼩小点,颜⾊色深 灰)
  • 14. 单元格上控件的⼤大⼩小问题 • ⾸首先我们来看⼀一下单元格⼤大⼩小的问题。 • 虽然单元格在 XIB ⽂文件上具有特定的⼤大⼩小,但是 我们并不知道在应⽤用程序⾥里⾯面实际的宽度和⾼高度 会是多少。
  • 15. • 因为我们的应⽤用将会运⾏行在 iPhone 和 iPad 上,⽽而 且在 iPad 还有可能是 portrait 和 landscape oritentation。 • 单元格需要进⾏行横向调整来匹配它所在的窗⼝口⼤大 ⼩小。这样我们就必须针对每个⼦子视图设置它的 autosizing mask • 在 size inspector ⾥里⾯面,我们来按照下⼀一⻚页的图⽰示 来更改 subview 的 autosizing mask
  • 16. HomepwnerItemCell 的Autoresizing masks • image,name 和 serialNumber 位置都固定在左上 ⾓角;价格标签位置固定在右上⾓角; • name 和 serialNumber 宽度横向延伸
  • 17. 更改 Class • 最后,我们点击 outline view 中的 cell,选择 identity inspector。把它的 Class 改成 HomepwnerItemCell
  • 19. • 现在这个单元格视觉效果看起来还不错,但是我 们必须让它能够被⽤用起来。 • 也就是说当我们在 tableView:cellForRowAtIndexPath: ⾥里⾯面创建⼀一个 HomepwnerItemCell 的实例时,我们需要能够设 置每⼀一个 label 的 text 属性,以及 UIImageView 的 image。
  • 20. • 下⼀一步我们就是要来为每⼀一个⼦子视图创建和连接 HomepwnerItemCell 上的 outlets。 • 我们还是使⽤用前⼏几节⽤用到的 Controll-dragging 到 源⽂文件的⽅方式来创建它们的 outlets。 • 但是对于 HomepwnerItemCell 的 outlets,情况⼜又 有稍许不同:它们将会是 properites,⽽而不是简单 的实例变量。
  • 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; }
  • 31. • 构建并运⾏行,单元格将会如下图所⽰示: • 要注意的两个问题:第⼀一,我们前⾯面使⽤用 UINib 来 进⾏行注册,XIB 的初始化底层调⽤用的也是 Nib;第 ⼆二,我们前⾯面的 XIB ⽂文件没有设置 File’s Owner, 因 为 table view 注册的时候只会扫描 UITableViewCell。所以 XIB ⽂文件⾥里⾯面也不能放置多 个 UITableViewCell。
  • 33. • 要在单元格⾥里⾯面使⽤用图像,我们可以使⽤用 image 的缩略图。 • 要创建⼀一个 BNRItem 图像的缩略图,我们需要绘 制⼀一个全尺⼨寸图⽚片 scale-down 的版本到 offscreen context, 然后在 BNRItem 实例中保持⼀一个指向这 个新图⽚片的指针。
  • 34. • 同样我们也需要⼀一个位置来存储这个缩略图,以 便在系统重启后可以重新加载。 • 全尺⼨寸的图⽚片我们保存在 BNRImageStore 中,必 要的时候我们可以把它 flush 掉;⽽而缩略图⾜足够 ⼩小,所以我们可以直接把它和 BNRItem 的其他实 例变量⼀一起 archive 起来。
  • 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; }
  • 39. • 现在我们来实现 setThumbnailDataFromImage: ⽅方 法 • 这个⽅方法将把⼀一个全尺⼨寸图⽚片⽣生成为缩略图,然 后把 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; }
  • 45. • 我们希望⽤用户点击单元格中的⼀一个缩略图以后能 够看到⼀一个全尺⼨寸的图⽚片。 • 可以在 thumbnail 上⾯面加⼀一个透明的按钮 • 点击这个按钮以后,如果应⽤用运⾏行的 iPad 之上, 将会使⽤用 UIPopoverController 显⽰示⼀一个全尺⼨寸的 图⽚片。
  • 46. • 打开 HomepwnerItemCell.xib ,在 UIImageView 上 ⾯面拖拽⼀一个 UIButton,重新调整这个按钮的⼤大⼩小 让它正好和 UIIImageView 的⼤大⼩小完全⼀一致。 • 然后在 HomepwnerItemCell.h 上 Option-click 打开 assistant editor。从按钮 Control-drag 到⽅方法区 域,然后像下⾯面这样配置⼀一下。 - (IBAction)showImage:(id)sender;
  • 47. • 为了不让这个按钮掩盖下⾯面的图⽚片,在 attributes inspector ⾥里⾯面,把按钮的类型改成 Custom。 • 默认情况下,⼀一个 custom 的按钮不做任何绘制, 也就是说它是⼀一个透明的,上⾯面什么也没有的按 钮。
  • 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]];
  • 52. 转发消息到控制器 • 现在单元格已经知道它的控制器和它要在上⾯面显 ⽰示的 table view 了 • 当 showImage: 消息被发送到 HomepwnerItemCell 时,我们希望 HomepwnerItemCell 可以告诉 ItemsViewController 为在 index path 位置的单元 格显⽰示图⽚片
  • 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”
  • 58. • 打开新建的这个 ImageViewController.xib, ⾸首先拖 拽⼀一个 UIScrollView 到 View 上;然后拖拽⼀一个 UIImageView 到 UIScrollView 上
  • 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
  • 62. • 当⼀一个 ImageViewController 的实例被创建时,它 会被赋予⼀一个 image。 • 显⽰示的时候,它将会调整 imageView 来适应图 ⽚片,然后告诉 scrollView 来更新它的 content size 来适配。
  • 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; }
  • 65. • 然后在 ItemsViewController.m 顶部导⼊入需要的头 ⽂文件 // 为 image popover 导⼊入相应头⽂文件 #import "BNRImageStore.h" #import "ImageViewController.h"
  • 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; }