Camera




范圣刚,princetoad@gmail.com, www.tfan.org
• 在这个主题我们将给 Homepwner 增加照⽚片
• 我们将呈现⼀一个 UIImagePickerController,这样⽤用
 户就可以获取并保存每个 item 的照⽚片。

• 这个图⽚片会被关联到⼀一个 BNRItem 实例,存储到
 ⼀一个 image store 中,并且在 item 的详细视图中可
 以看到
带图⽚片的 Homepwner
图⽚片显⽰示和 UIImageView
• 要显⽰示图⽚片,⼀一个简单的⽅方法就是把⼀一个
 UIImageView 放到屏幕上。打开 Hompwner 项
 ⺫⽬目,和 DetailViewController.xib,拖拽⼀一个
 UIImageView 到 view 上
contentMode - Aspect Fit
• UIImageView 根据它的 contentMode 属性来显⽰示图
 ⽚片,这个属性决定了如何定位以及如何在它的
 frame 内调整内容的⼤大⼩小。
• contentMode 默认值是
 UIViewContentModeScaleToFill,它将调整图⽚片来
 正好匹配 image view 的 bounds。为了不使图⽚片变
 形,我们把它改成 “Aspect Fit”
连接到 view controller
    • 控件设置好了以后,我们⽤用前⾯面提到的从
    UIImageView Control-drag 到
    DetailViewController.h 的实例变量区域的⽅方法,⽣生
    成⼀一个名为 imageView 的 outlet,选择 Weak 作为
    存储类型
@interface DetailViewController : UIViewController
<UINavigationControllerDelegate, UIImagePickerControllerDelegate,
UITextFieldDelegate>
{
    __weak IBOutlet UITextField *nameField;

    __weak IBOutlet UITextField *serialNumberField;

    __weak IBOutlet UILabel *dateLabel;
    __weak IBOutlet UITextField *valueField;

    __weak IBOutlet UIImageView *imageView;
}
拍照和 UIImagePickerController
• 我们需要⼀一个按钮来启动照⽚片获取的过程:在
 DetailViewController.xib 中,拖动⼀一个 UIToolbar
 到 DetailViewController 的 view 的底部

• UIToolbar 和 UINavigationBar 类似的是我们都可以
 给它添加 UIBarButtonItems。

• 不同的是,导航栏针对 bar button item 只有两个
 插槽,⽽而 toolbar 有⼀一个 bar button item 的数组,
 只要屏幕放的下我们可以往⾥里⾯面添加尽量多的
 UIBarButtonItem
Identifier -> Camera
• 默认情况下,在 XIB ⽂文件中新⽣生成的 UIToolbar 实
 例会带⼀一个 UIBarButtonItem。
• 选中这个 bar button item,打开 attribute
 inspector,把它的 Identifier 改成 Camera,这个
 item 就会显⽰示⼀一个 camera 图标
带 bar button item 的 UIToolbar
camera 按钮⽅方法
• 有了按钮以后,我们要在代码中声明它触发的⽅方
 法
• 选中这个 camera 按钮,然后从按钮 Control-drag
 到 DetailViewController.h 中⽅方法声明的区域
• 选择 Action,⽅方法命名为:takePicture:
• 这样 camera 按钮按下时就会发送这个消息给
 DetailViewController。
• ⽤用这种⽅方式连接⼀一个 action ⽅方法,同时会⾃自动在
 DetailViewController.m 中添加⼀一个 stub
 implementation
UIImagePickerController
• camera 按钮的⽅方法有了之后,我们就得看⼀一下怎
 么在这个 takePicture: ⽅方法⾥里⾯面实现拍照(或选取
 照⽚片)
• 我们可以使⽤用 UIImagePickerController,调⽤用系统
 本⾝身的应⽤用来获取照⽚片。
• 当创建这个类的实例时,我们必须指定它的
 sourceType 属性,并且分配给它⼀一个 delegate
sourceType 属性
• sourceType 是⼀一个⽤用来告诉 image picker 到哪⾥里
 获得照⽚片的常量:
 • UIImagePickerControllerSourceTypeCamera - 拍新照
  ⽚片
 • UIImagePickerControllerSourceTypeSavedPhotoAlbu
  m 和 UIImagePickerControllerSourceTypePhotoLibrary
  都是从保存的照⽚片中选取
• 在开始使⽤用
 UIImagePickerControllerSourceTypeCamera 之前要先
 判断设备有没有摄像头。

• ⽅方法是以 sourceType 常量作为参数发送
 isSourceTypeAvailable: 消息给
 UIImagePickerController
delegate 和消息
• 除了 sourceType 之外,UIImagePickerController 的
 实例还需要⼀一个 delegate 来处理来⾃自它的 view 的
 请求。
• 当⽤用户在 UIImagePickerController 界⾯面确认选取的
 图⽚片后,
 imagePickerController:didFinishPickingMediaWithInfo
 : 消息被发送给它的 delegate。(如果这个过程被
 cancel 掉的话,消息是:
 imagePickerControllerDidCancel:)
• 在 takePicker: 中增加初始化和设置 delegate 的代
 码
imagePicker 的初始化和设置
- (IBAction)takePicture:(id)sender {
    UIImagePickerController *imagePicker = [[UIImagePickerController alloc]
init];
    // 如果设备有 camera 的话,我们就采集⼀一个图⽚片,否则我们从 photo library 从拾取⼀一
个
    if ([UIImagePickerController
isSourceTypeAvailable:UIImagePickerControllerSourceTypeCamera]) {
        [imagePicker setSourceType:UIImagePickerControllerSourceTypeCamera];
    } else {
        [imagePicker
setSourceType:UIImagePickerControllerSourceTypePhotoLibrary];
    }

    [imagePicker setDelegate:self];
}


    • 初始化和设置完成之后,下⼀一步就是要把它显⽰示
    出来。imagePicker 和之前⽤用到的 UIViewController
    ⼦子类不同的是它要模态化呈现。
    • 模态化:占满全屏,直到完成⼯工作。
显⽰示 imagePicker
    • 要模态化的呈现⼀一个视图,发送
     presentViewController:animated:completion: 消
     息给当前其 view 在屏幕上显⽰示的
     UIViewController。
    • 传递的第⼀一个参数就是要呈现的 view controller,
     它的 view 会从屏幕底部向上滑动出来
    • 在 takePicture: 末尾增加显⽰示 imagePicker 的代码
     [imagePicker setDelegate:self];

     // 把 image picker 放到屏幕上(模态对话框)
     [self presentViewController:imagePicker animated:YES completion:nil];
}
UINavigationControllerDelegate
 • imagePicker 的初始化,设置和显⽰示都完成了,下
  ⼀一步就是获取 imagePicker 选择的图⽚片
 • 需要在 DetailViewController 中实现我们前⾯面提到
  的
  imagePickerController:didFinishPickingMediaWithInfo
  :




• UIImagePickerController 是 UINavigationController
 的⼦子类,在类声明中添加下⻚页这些 protocol
image 的独⽴立存储
@interface DetailViewController : UIViewController
<UINavigationControllerDelegate, UIImagePickerControllerDelegate>
{


 • 我们可以直接在didFinishPickingMediaWithInfo: 中
    直接把选择的 image 设置给 imageView
 • 但是我们最好为 image 创建⼀一个单独的存储。
• 我们把图⽚片放到 store 中,这样当
 DetailViewController 的 view 下次在屏幕上显⽰示
 时,我们让 DetailViewController 从 image store 中
 提取图⽚片,然后放到它⾃自⼰己的 imageView

• ⼀一般来讲,每次⼀一个 view controller 收到
 viewWillAppear: 消息时都应该重新使⽤用数据填充
 它的视图的⼦子视图
创建BNRImageStore
• 这样我们在真正显⽰示选中的图⽚片前,先创建⼀一个
       store 对它进⾏行管理。

 • image store 将会持有⽤用户会⽤用到的全部图⽚片。
 • ⽣生成⼀一个名为 BNRImageStore 的 NSObject 的⼦子类
 • ⽣生成下列接⼝口:
@interface BNRImageStore : NSObject
{
    NSMutableDictionary *dictionary;
}

+ (BNRImageStore *)sharedStore;

- (void)setImage:(UIImage *)i forKey:(NSString *)s;
- (UIImage *)imageForKey:(NSString *)s;
- (void)deleteImageForkey:(NSString *)s;

@end
实现 Singleton
 • 和 BNRItemStore ⼀一样,BNRImageStore 也需要是
   单例的。在 BNRImageStore.m 中编写下⾯面这些和
   BNRItemStore 类似的代码
+ (id)allocWithZone:(NSZone *)zone
{
    return [self sharedStore];
}
+ (BNRImageStore *)sharedStore
{
    static BNRImageStore *sharedStore = nil;
    if (!sharedStore) {
        sharedStore = [[super allocWithZone:NULL] init];
    }
    return sharedStore;
}
- (id)init
{
    self = [super init];
    if (self) {
        dictionary = [[NSMutableDictionary alloc] init];
    }
    return self;
}
实现头⽂文件中声明的另外三个⽅方法
- (void)setImage:(UIImage *)i forKey:(NSString *)s
{
    [dictionary setObject:i forKey:s];
}

- (UIImage *)imageForKey:(NSString *)s
{
    return [dictionary objectForKey:s];
}

- (void)deleteImageForkey:(NSString *)s
{
    if (!s) {
        return;
    }
    [dictionary removeObjectForKey:s];
}


 • setImage: 增加(更新)
 • imageForKey: 获取
 • deleteImageForKey: 删除
NSMutableDictionary
• 经过上⾯面⼀一些操作,基本的 image store 功能就完
 成了。
• 这⾥里⾯面我们使⽤用的是 NSMutableDictionary 来存
 储数据(key-value pair)
• key 是⼀一个唯⼀一值(⼀一般是字符串),value 是被
 存储在集合中的对象
NSDictionary
⽣生成和使⽤用键
• 要使⽤用这个 image store,⾸首先我们要想办法能够
    ⽣生成唯⼀一的 key 。
    • ⼀一个是因为在把图⽚片存到字典时要⽤用到;
    • 另外要把这个 key 给相关联的 BNRItem,在
      DetailViewController 需要从 image store 拿到⼀一张图⽚片
      时,要询问它的 item key 是多少,然后使⽤用这个 key
      在字典中搜索图⽚片

 • ⾸首先给 BNRItem 增加⼀一个属性来存储这个 key
@property (nonatomic, readonly, strong) NSDate *dateCreated;

@property (nonatomic, copy) NSString *imageKey;

@implementation BNRItem
@synthesize imageKey;
CFUUIDRef 和 CFUUIDCreate
 • 要保证 key 的唯⼀一性,可以使⽤用 UUID 的⽅方式
 • CFUUIDRef 类型的对象表⽰示⼀一个 UUID,下⾯面在
    imagePickerController:didFinishPickingMediaWithInfo
    : 中⽣生成⼀一个 UUID
    UIImage *image = [info
objectForKey:UIImagePickerControllerOriginalImage];

    // ⽣生成⼀一个 CFUUID 对象
    CFUUIDRef newUniqueID = CFUUIDCreate(kCFAllocatorDefault);


• CF对象的创建通过调⽤用以被创建的对象的类型开
  始,并且包含“Create”的函数(CFUUIDCreate)
• 指定的第⼀一个参数是内存如何分配,传⼊入
  kCFAllocateDefault 表⽰示由系统决定
CFUUIDCreateString 和 CFStringRef
 • CFUUIDRef 是⼀一个字节数组(15个uint8)
 • 因为我们要把 UUID 作为字典的 key,以及在后⾯面
   存储到⽂文件系统的时候会把它作为⽂文件名,所以
   需要把 UUID 转成字符串
 • 可以通过调⽤用 CFUUIDCreateString 从 CFUUIDRef
   ⽣生成⼀一个字符串对象
    // ⽣生成⼀一个 CFUUID 对象
    CFUUIDRef newUniqueID = CFUUIDCreate(kCFAllocatorDefault);
    // 从 unique identifier ⽣生成⼀一个字符串
    CFStringRef newUniqueIDString = CFUUIDCreateString(kCFAllocatorDefault,
newUniqueID);
类型转换
 • CFStringRef 和 NSString 需要相互转换
 • 转换类型,把它设置成选中的 BNRItem 的
   imageKey,同时把 image 放到 BNRImageStore 中
- (void)imagePickerController:(UIImagePickerController *)picker
didFinishPickingMediaWithInfo:(NSDictionary *)info
{
    UIImage *image = [info
objectForKey:UIImagePickerControllerOriginalImage];

    CFUUIDRef newUniqueID = CFUUIDCreate(kCFAllocatorDefault);
    CFStringRef newUniqueIDString = CFUUIDCreateString(kCFAllocatorDefault,
newUniqueID);
    // CFStringRef -> NSString
    // 使⽤用这个 unique ID 来设置 item 的 imageKey
    NSString *key = (__bridge NSString *)newUniqueIDString;
    [item setImageKey:key];

    // 使⽤用这个 key 把图⽚片保存到 BNRImageStore
    [[BNRImageStore sharedStore] setImage:image forKey:[item imageKey]];
Core Foundation 和 toll-free
         bridging
• 当⼀一个指向 Objective-C 对象的变量被销毁时,
 ARC 知道这个对象失去了⼀一个所有者。

• 但是 ARC 并不对 Core Foundation 的对象做这些
• 这样当 Core Foundation 的对象失去指针时,我们
 必须在丢掉这个指针前调⽤用⼀一个函数来告诉对象
 丢掉⼀一个所有者
• 如果我们没有在失去指针前调⽤用 CFRelease ,被指
向的对象仍然认为它有⼀一个所有者

• 在告诉它丢掉⼀一个所有者之前丢掉指针会引起内
存泄露:对象已经访问不了了,但还有所有者
CFRelease
• 增加代码,告诉被 newUniqueIDString 和
 newUniqueID 指向的对象丢掉⼀一个所有者,因为
 都是局部变量,⽅方法结束将销毁
 [[BNRImageStore sharedStore] setImage:image forKey:[item imageKey]];

 // lose owner
 CFRelease(newUniqueIDString);
 CFRelease(newUniqueID);

 // 把图⽚片放到我们屏幕上的 image view 中
 [imageView setImage:image];
__bridge
 NSString *key = (__bridge NSString *)newUniqueIDString;



• ARC 并不很会管理 Core Foundation 对象的内存
• 在我们类型转换⼀一个 Core Foundation 指针到 它的
 Objective-C 同级形式时,放⼀一个 __bridge 在调⽤用
 前⾯面,是告诉 ARC 不要像通常做的那样给 key 变
 量⼀一个所有权
结束 BNRImageStore
• 现在 BNRItemStore 可以存储图⽚片,并且 BNRItem
 有获得这个图⽚片的 key,我们需要告诉
 DetailViewController 怎样从选中的 BNRItem 抓取
 图⽚片,并放⼊入它的 imageView
Cache
获取并显⽰示照⽚片
 • DetailViewController 的 view 将会在两个时间上出
   现:当⽤用户轻击 ItemViewController 中的⼀一⾏行时,
   当 UIImagePickerController 被释放时
 • 在两种情况下,imageView 都应该被正在显⽰示的
   BNRItem 的图⽚片填充。在 DetailViewController.m
   中增加代码到 viewWillAppear:
    // 显⽰示图⽚片
    NSString *imageKey = [item imageKey];
    if (imageKey) {
        UIImage *imageToDisplay = [[BNRImageStore sharedStore]
imageForKey:imageKey];
        [imageView setImage:imageToDisplay];
    } else {
        [imageView setImage:nil];
    }
删除旧图⽚片
- (void)imagePickerController:(UIImagePickerController *)picker
didFinishPickingMediaWithInfo:(NSDictionary *)info
{
    // 如果存在旧图⽚片,先把它从 BNRImageStore 中删除
    NSString *oldKey = [item imageKey];
    if (oldKey) {
        [[BNRImageStore sharedStore] deleteImageForkey:oldKey];
    }

    // 从 info 字典中获得拾取的图⽚片
    UIImage *image = [info
objectForKey:UIImagePickerControllerOriginalImage];
释放键盘
• 让 DetailViewController 符合 UITextFieldDelegate
    protocol,实现 textFieldShouldReturn: ⽅方法来让⽂文
    本框字段在换⾏行键被按下时释放它的 first
    responder 状态以释放键盘
@interface DetailViewController : UIViewController
<UINavigationControllerDelegate, UIImagePickerControllerDelegate,
UITextFieldDelegate>

- (BOOL)textFieldShouldReturn:(UITextField *)textField
{
    [textField resignFirstResponder];
    return YES;
}
UIControll 和 Touch Up Inside 事件
• 我们希望⽤用户在 DetailViewController 的 view 上的
 任何地⽅方轻击都释放键盘,我们可以通过给 view
 发送 endEditing: 消息实现释放键盘
• UIButton 可以在轻击时发送⼀一个 action 消息给它
 的 target,这种 target-action 特性是从 UIControl
 继承的
• 在 DetailViewController.xib 中选择 View 对象,在
 identity inspector 中把 view 的类改成 UIControl

• 然后从 view Control-drag 到⽅方法声明区域,事件
 选择 ”Touch Up Inside“
- (IBAction)backgroundTapped:(id)sender {
    [[self view] endEditing:YES];
}

12 Camera

  • 1.
  • 2.
    • 在这个主题我们将给 Homepwner增加照⽚片 • 我们将呈现⼀一个 UIImagePickerController,这样⽤用 户就可以获取并保存每个 item 的照⽚片。 • 这个图⽚片会被关联到⼀一个 BNRItem 实例,存储到 ⼀一个 image store 中,并且在 item 的详细视图中可 以看到
  • 3.
  • 4.
    图⽚片显⽰示和 UIImageView • 要显⽰示图⽚片,⼀一个简单的⽅方法就是把⼀一个 UIImageView 放到屏幕上。打开 Hompwner 项 ⺫⽬目,和 DetailViewController.xib,拖拽⼀一个 UIImageView 到 view 上
  • 5.
    contentMode - AspectFit • UIImageView 根据它的 contentMode 属性来显⽰示图 ⽚片,这个属性决定了如何定位以及如何在它的 frame 内调整内容的⼤大⼩小。 • contentMode 默认值是 UIViewContentModeScaleToFill,它将调整图⽚片来 正好匹配 image view 的 bounds。为了不使图⽚片变 形,我们把它改成 “Aspect Fit”
  • 6.
    连接到 view controller • 控件设置好了以后,我们⽤用前⾯面提到的从 UIImageView Control-drag 到 DetailViewController.h 的实例变量区域的⽅方法,⽣生 成⼀一个名为 imageView 的 outlet,选择 Weak 作为 存储类型 @interface DetailViewController : UIViewController <UINavigationControllerDelegate, UIImagePickerControllerDelegate, UITextFieldDelegate> { __weak IBOutlet UITextField *nameField; __weak IBOutlet UITextField *serialNumberField; __weak IBOutlet UILabel *dateLabel; __weak IBOutlet UITextField *valueField; __weak IBOutlet UIImageView *imageView; }
  • 7.
  • 8.
    • 我们需要⼀一个按钮来启动照⽚片获取的过程:在 DetailViewController.xib中,拖动⼀一个 UIToolbar 到 DetailViewController 的 view 的底部 • UIToolbar 和 UINavigationBar 类似的是我们都可以 给它添加 UIBarButtonItems。 • 不同的是,导航栏针对 bar button item 只有两个 插槽,⽽而 toolbar 有⼀一个 bar button item 的数组, 只要屏幕放的下我们可以往⾥里⾯面添加尽量多的 UIBarButtonItem
  • 9.
    Identifier -> Camera •默认情况下,在 XIB ⽂文件中新⽣生成的 UIToolbar 实 例会带⼀一个 UIBarButtonItem。 • 选中这个 bar button item,打开 attribute inspector,把它的 Identifier 改成 Camera,这个 item 就会显⽰示⼀一个 camera 图标
  • 10.
    带 bar buttonitem 的 UIToolbar
  • 11.
    camera 按钮⽅方法 • 有了按钮以后,我们要在代码中声明它触发的⽅方 法 • 选中这个 camera 按钮,然后从按钮 Control-drag 到 DetailViewController.h 中⽅方法声明的区域 • 选择 Action,⽅方法命名为:takePicture: • 这样 camera 按钮按下时就会发送这个消息给 DetailViewController。 • ⽤用这种⽅方式连接⼀一个 action ⽅方法,同时会⾃自动在 DetailViewController.m 中添加⼀一个 stub implementation
  • 12.
    UIImagePickerController • camera 按钮的⽅方法有了之后,我们就得看⼀一下怎 么在这个 takePicture: ⽅方法⾥里⾯面实现拍照(或选取 照⽚片) • 我们可以使⽤用 UIImagePickerController,调⽤用系统 本⾝身的应⽤用来获取照⽚片。 • 当创建这个类的实例时,我们必须指定它的 sourceType 属性,并且分配给它⼀一个 delegate
  • 13.
    sourceType 属性 • sourceType是⼀一个⽤用来告诉 image picker 到哪⾥里 获得照⽚片的常量: • UIImagePickerControllerSourceTypeCamera - 拍新照 ⽚片 • UIImagePickerControllerSourceTypeSavedPhotoAlbu m 和 UIImagePickerControllerSourceTypePhotoLibrary 都是从保存的照⽚片中选取
  • 14.
    • 在开始使⽤用 UIImagePickerControllerSourceTypeCamera之前要先 判断设备有没有摄像头。 • ⽅方法是以 sourceType 常量作为参数发送 isSourceTypeAvailable: 消息给 UIImagePickerController
  • 15.
    delegate 和消息 • 除了sourceType 之外,UIImagePickerController 的 实例还需要⼀一个 delegate 来处理来⾃自它的 view 的 请求。 • 当⽤用户在 UIImagePickerController 界⾯面确认选取的 图⽚片后, imagePickerController:didFinishPickingMediaWithInfo : 消息被发送给它的 delegate。(如果这个过程被 cancel 掉的话,消息是: imagePickerControllerDidCancel:) • 在 takePicker: 中增加初始化和设置 delegate 的代 码
  • 16.
    imagePicker 的初始化和设置 - (IBAction)takePicture:(id)sender{ UIImagePickerController *imagePicker = [[UIImagePickerController alloc] init]; // 如果设备有 camera 的话,我们就采集⼀一个图⽚片,否则我们从 photo library 从拾取⼀一 个 if ([UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypeCamera]) { [imagePicker setSourceType:UIImagePickerControllerSourceTypeCamera]; } else { [imagePicker setSourceType:UIImagePickerControllerSourceTypePhotoLibrary]; } [imagePicker setDelegate:self]; } • 初始化和设置完成之后,下⼀一步就是要把它显⽰示 出来。imagePicker 和之前⽤用到的 UIViewController ⼦子类不同的是它要模态化呈现。 • 模态化:占满全屏,直到完成⼯工作。
  • 17.
    显⽰示 imagePicker • 要模态化的呈现⼀一个视图,发送 presentViewController:animated:completion: 消 息给当前其 view 在屏幕上显⽰示的 UIViewController。 • 传递的第⼀一个参数就是要呈现的 view controller, 它的 view 会从屏幕底部向上滑动出来 • 在 takePicture: 末尾增加显⽰示 imagePicker 的代码 [imagePicker setDelegate:self]; // 把 image picker 放到屏幕上(模态对话框) [self presentViewController:imagePicker animated:YES completion:nil]; }
  • 18.
    UINavigationControllerDelegate • imagePicker的初始化,设置和显⽰示都完成了,下 ⼀一步就是获取 imagePicker 选择的图⽚片 • 需要在 DetailViewController 中实现我们前⾯面提到 的 imagePickerController:didFinishPickingMediaWithInfo : • UIImagePickerController 是 UINavigationController 的⼦子类,在类声明中添加下⻚页这些 protocol
  • 19.
    image 的独⽴立存储 @interface DetailViewController: UIViewController <UINavigationControllerDelegate, UIImagePickerControllerDelegate> { • 我们可以直接在didFinishPickingMediaWithInfo: 中 直接把选择的 image 设置给 imageView • 但是我们最好为 image 创建⼀一个单独的存储。
  • 20.
    • 我们把图⽚片放到 store中,这样当 DetailViewController 的 view 下次在屏幕上显⽰示 时,我们让 DetailViewController 从 image store 中 提取图⽚片,然后放到它⾃自⼰己的 imageView • ⼀一般来讲,每次⼀一个 view controller 收到 viewWillAppear: 消息时都应该重新使⽤用数据填充 它的视图的⼦子视图
  • 21.
  • 22.
    • 这样我们在真正显⽰示选中的图⽚片前,先创建⼀一个 store 对它进⾏行管理。 • image store 将会持有⽤用户会⽤用到的全部图⽚片。 • ⽣生成⼀一个名为 BNRImageStore 的 NSObject 的⼦子类 • ⽣生成下列接⼝口: @interface BNRImageStore : NSObject { NSMutableDictionary *dictionary; } + (BNRImageStore *)sharedStore; - (void)setImage:(UIImage *)i forKey:(NSString *)s; - (UIImage *)imageForKey:(NSString *)s; - (void)deleteImageForkey:(NSString *)s; @end
  • 23.
    实现 Singleton •和 BNRItemStore ⼀一样,BNRImageStore 也需要是 单例的。在 BNRImageStore.m 中编写下⾯面这些和 BNRItemStore 类似的代码 + (id)allocWithZone:(NSZone *)zone { return [self sharedStore]; } + (BNRImageStore *)sharedStore { static BNRImageStore *sharedStore = nil; if (!sharedStore) { sharedStore = [[super allocWithZone:NULL] init]; } return sharedStore; } - (id)init { self = [super init]; if (self) { dictionary = [[NSMutableDictionary alloc] init]; } return self; }
  • 24.
    实现头⽂文件中声明的另外三个⽅方法 - (void)setImage:(UIImage *)iforKey:(NSString *)s { [dictionary setObject:i forKey:s]; } - (UIImage *)imageForKey:(NSString *)s { return [dictionary objectForKey:s]; } - (void)deleteImageForkey:(NSString *)s { if (!s) { return; } [dictionary removeObjectForKey:s]; } • setImage: 增加(更新) • imageForKey: 获取 • deleteImageForKey: 删除
  • 25.
    NSMutableDictionary • 经过上⾯面⼀一些操作,基本的 imagestore 功能就完 成了。 • 这⾥里⾯面我们使⽤用的是 NSMutableDictionary 来存 储数据(key-value pair) • key 是⼀一个唯⼀一值(⼀一般是字符串),value 是被 存储在集合中的对象
  • 26.
  • 27.
  • 28.
    • 要使⽤用这个 imagestore,⾸首先我们要想办法能够 ⽣生成唯⼀一的 key 。 • ⼀一个是因为在把图⽚片存到字典时要⽤用到; • 另外要把这个 key 给相关联的 BNRItem,在 DetailViewController 需要从 image store 拿到⼀一张图⽚片 时,要询问它的 item key 是多少,然后使⽤用这个 key 在字典中搜索图⽚片 • ⾸首先给 BNRItem 增加⼀一个属性来存储这个 key @property (nonatomic, readonly, strong) NSDate *dateCreated; @property (nonatomic, copy) NSString *imageKey; @implementation BNRItem @synthesize imageKey;
  • 29.
    CFUUIDRef 和 CFUUIDCreate • 要保证 key 的唯⼀一性,可以使⽤用 UUID 的⽅方式 • CFUUIDRef 类型的对象表⽰示⼀一个 UUID,下⾯面在 imagePickerController:didFinishPickingMediaWithInfo : 中⽣生成⼀一个 UUID UIImage *image = [info objectForKey:UIImagePickerControllerOriginalImage]; // ⽣生成⼀一个 CFUUID 对象 CFUUIDRef newUniqueID = CFUUIDCreate(kCFAllocatorDefault); • CF对象的创建通过调⽤用以被创建的对象的类型开 始,并且包含“Create”的函数(CFUUIDCreate) • 指定的第⼀一个参数是内存如何分配,传⼊入 kCFAllocateDefault 表⽰示由系统决定
  • 30.
    CFUUIDCreateString 和 CFStringRef • CFUUIDRef 是⼀一个字节数组(15个uint8) • 因为我们要把 UUID 作为字典的 key,以及在后⾯面 存储到⽂文件系统的时候会把它作为⽂文件名,所以 需要把 UUID 转成字符串 • 可以通过调⽤用 CFUUIDCreateString 从 CFUUIDRef ⽣生成⼀一个字符串对象 // ⽣生成⼀一个 CFUUID 对象 CFUUIDRef newUniqueID = CFUUIDCreate(kCFAllocatorDefault); // 从 unique identifier ⽣生成⼀一个字符串 CFStringRef newUniqueIDString = CFUUIDCreateString(kCFAllocatorDefault, newUniqueID);
  • 31.
    类型转换 • CFStringRef和 NSString 需要相互转换 • 转换类型,把它设置成选中的 BNRItem 的 imageKey,同时把 image 放到 BNRImageStore 中 - (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info { UIImage *image = [info objectForKey:UIImagePickerControllerOriginalImage]; CFUUIDRef newUniqueID = CFUUIDCreate(kCFAllocatorDefault); CFStringRef newUniqueIDString = CFUUIDCreateString(kCFAllocatorDefault, newUniqueID); // CFStringRef -> NSString // 使⽤用这个 unique ID 来设置 item 的 imageKey NSString *key = (__bridge NSString *)newUniqueIDString; [item setImageKey:key]; // 使⽤用这个 key 把图⽚片保存到 BNRImageStore [[BNRImageStore sharedStore] setImage:image forKey:[item imageKey]];
  • 32.
    Core Foundation 和toll-free bridging
  • 33.
    • 当⼀一个指向 Objective-C对象的变量被销毁时, ARC 知道这个对象失去了⼀一个所有者。 • 但是 ARC 并不对 Core Foundation 的对象做这些 • 这样当 Core Foundation 的对象失去指针时,我们 必须在丢掉这个指针前调⽤用⼀一个函数来告诉对象 丢掉⼀一个所有者
  • 34.
    • 如果我们没有在失去指针前调⽤用 CFRelease,被指 向的对象仍然认为它有⼀一个所有者 • 在告诉它丢掉⼀一个所有者之前丢掉指针会引起内 存泄露:对象已经访问不了了,但还有所有者
  • 35.
    CFRelease • 增加代码,告诉被 newUniqueIDString和 newUniqueID 指向的对象丢掉⼀一个所有者,因为 都是局部变量,⽅方法结束将销毁 [[BNRImageStore sharedStore] setImage:image forKey:[item imageKey]]; // lose owner CFRelease(newUniqueIDString); CFRelease(newUniqueID); // 把图⽚片放到我们屏幕上的 image view 中 [imageView setImage:image];
  • 36.
    __bridge NSString *key= (__bridge NSString *)newUniqueIDString; • ARC 并不很会管理 Core Foundation 对象的内存 • 在我们类型转换⼀一个 Core Foundation 指针到 它的 Objective-C 同级形式时,放⼀一个 __bridge 在调⽤用 前⾯面,是告诉 ARC 不要像通常做的那样给 key 变 量⼀一个所有权
  • 37.
    结束 BNRImageStore • 现在BNRItemStore 可以存储图⽚片,并且 BNRItem 有获得这个图⽚片的 key,我们需要告诉 DetailViewController 怎样从选中的 BNRItem 抓取 图⽚片,并放⼊入它的 imageView
  • 38.
  • 39.
    获取并显⽰示照⽚片 • DetailViewController的 view 将会在两个时间上出 现:当⽤用户轻击 ItemViewController 中的⼀一⾏行时, 当 UIImagePickerController 被释放时 • 在两种情况下,imageView 都应该被正在显⽰示的 BNRItem 的图⽚片填充。在 DetailViewController.m 中增加代码到 viewWillAppear: // 显⽰示图⽚片 NSString *imageKey = [item imageKey]; if (imageKey) { UIImage *imageToDisplay = [[BNRImageStore sharedStore] imageForKey:imageKey]; [imageView setImage:imageToDisplay]; } else { [imageView setImage:nil]; }
  • 40.
    删除旧图⽚片 - (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary*)info { // 如果存在旧图⽚片,先把它从 BNRImageStore 中删除 NSString *oldKey = [item imageKey]; if (oldKey) { [[BNRImageStore sharedStore] deleteImageForkey:oldKey]; } // 从 info 字典中获得拾取的图⽚片 UIImage *image = [info objectForKey:UIImagePickerControllerOriginalImage];
  • 41.
  • 42.
    • 让 DetailViewController符合 UITextFieldDelegate protocol,实现 textFieldShouldReturn: ⽅方法来让⽂文 本框字段在换⾏行键被按下时释放它的 first responder 状态以释放键盘 @interface DetailViewController : UIViewController <UINavigationControllerDelegate, UIImagePickerControllerDelegate, UITextFieldDelegate> - (BOOL)textFieldShouldReturn:(UITextField *)textField { [textField resignFirstResponder]; return YES; }
  • 43.
    UIControll 和 TouchUp Inside 事件 • 我们希望⽤用户在 DetailViewController 的 view 上的 任何地⽅方轻击都释放键盘,我们可以通过给 view 发送 endEditing: 消息实现释放键盘 • UIButton 可以在轻击时发送⼀一个 action 消息给它 的 target,这种 target-action 特性是从 UIControl 继承的
  • 44.
    • 在 DetailViewController.xib中选择 View 对象,在 identity inspector 中把 view 的类改成 UIControl • 然后从 view Control-drag 到⽅方法声明区域,事件 选择 ”Touch Up Inside“
  • 45.
    - (IBAction)backgroundTapped:(id)sender { [[self view] endEditing:YES]; }