12 Camera

45
Camera 范圣刚,[email protected] , www.tfan.org

description

iOS Camera, 拍照,获取图像

Transcript of 12 Camera

Page 2: 12 Camera

•在这个主题我们将给 Homepwner 增加照⽚片

•我们将呈现⼀一个 UIImagePickerController,这样⽤用户就可以获取并保存每个 item 的照⽚片。

•这个图⽚片会被关联到⼀一个 BNRItem 实例,存储到⼀一个 image store 中,并且在 item 的详细视图中可以看到

Page 3: 12 Camera

带图⽚片的 Homepwner

Page 4: 12 Camera

图⽚片显⽰示和 UIImageView

•要显⽰示图⽚片,⼀一个简单的⽅方法就是把⼀一个 UIImageView 放到屏幕上。打开 Hompwner 项⺫⽬目,和 DetailViewController.xib,拖拽⼀一个 UIImageView 到 view 上

Page 5: 12 Camera

contentMode - Aspect Fit

•UIImageView 根据它的 contentMode 属性来显⽰示图⽚片,这个属性决定了如何定位以及如何在它的 frame 内调整内容的⼤大⼩小。

• contentMode 默认值是 UIViewContentModeScaleToFill,它将调整图⽚片来正好匹配 image view 的 bounds。为了不使图⽚片变形,我们把它改成 “Aspect Fit”

Page 6: 12 Camera

连接到 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;}

Page 7: 12 Camera

拍照和 UIImagePickerController

Page 8: 12 Camera

•我们需要⼀一个按钮来启动照⽚片获取的过程:在 DetailViewController.xib 中,拖动⼀一个 UIToolbar 到 DetailViewController 的 view 的底部

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

•不同的是,导航栏针对 bar button item 只有两个插槽,⽽而 toolbar 有⼀一个 bar button item 的数组,只要屏幕放的下我们可以往⾥里⾯面添加尽量多的 UIBarButtonItem

Page 9: 12 Camera

Identi!er -> Camera

•默认情况下,在 XIB ⽂文件中新⽣生成的 UIToolbar 实例会带⼀一个 UIBarButtonItem。

•选中这个 bar button item,打开 attribute inspector,把它的 Identi!er 改成 Camera,这个 item 就会显⽰示⼀一个 camera 图标

Page 10: 12 Camera

带 bar button item 的 UIToolbar

Page 11: 12 Camera

camera 按钮⽅方法•有了按钮以后,我们要在代码中声明它触发的⽅方法

•选中这个 camera 按钮,然后从按钮 Control-drag 到 DetailViewController.h 中⽅方法声明的区域

•选择 Action,⽅方法命名为:takePicture:

•这样 camera 按钮按下时就会发送这个消息给 DetailViewController。

•⽤用这种⽅方式连接⼀一个 action ⽅方法,同时会⾃自动在 DetailViewController.m 中添加⼀一个 stub implementation

Page 12: 12 Camera

UIImagePickerController

• camera 按钮的⽅方法有了之后,我们就得看⼀一下怎么在这个 takePicture: ⽅方法⾥里⾯面实现拍照(或选取照⽚片)

•我们可以使⽤用 UIImagePickerController,调⽤用系统本⾝身的应⽤用来获取照⽚片。

•当创建这个类的实例时,我们必须指定它的 sourceType 属性,并且分配给它⼀一个 delegate

Page 13: 12 Camera

sourceType 属性• sourceType 是⼀一个⽤用来告诉 image picker 到哪⾥里获得照⽚片的常量:• UIImagePickerControllerSourceTypeCamera - 拍新照⽚片

• UIImagePickerControllerSourceTypeSavedPhotoAlbum 和 UIImagePickerControllerSourceTypePhotoLibrary 都是从保存的照⽚片中选取

Page 14: 12 Camera

•在开始使⽤用 UIImagePickerControllerSourceTypeCamera 之前要先判断设备有没有摄像头。

•⽅方法是以 sourceType 常量作为参数发送 isSourceTypeAvailable: 消息给 UIImagePickerController

Page 15: 12 Camera

delegate 和消息•除了 sourceType 之外,UIImagePickerController 的实例还需要⼀一个 delegate 来处理来⾃自它的 view 的请求。

•当⽤用户在 UIImagePickerController 界⾯面确认选取的图⽚片后,imagePickerController:didFinishPickingMediaWithInfo: 消息被发送给它的 delegate。(如果这个过程被 cancel 掉的话,消息是:imagePickerControllerDidCancel:)

•在 takePicker: 中增加初始化和设置 delegate 的代码

Page 16: 12 Camera

imagePicker 的初始化和设置

•初始化和设置完成之后,下⼀一步就是要把它显⽰示出来。imagePicker 和之前⽤用到的 UIViewController ⼦子类不同的是它要模态化呈现。

•模态化:占满全屏,直到完成⼯工作。

- (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];}

Page 17: 12 Camera

显⽰示 imagePicker

•要模态化的呈现⼀一个视图,发送 presentViewController:animated:completion: 消息给当前其 view 在屏幕上显⽰示的 UIViewController。

•传递的第⼀一个参数就是要呈现的 view controller,它的 view 会从屏幕底部向上滑动出来

•在 takePicture: 末尾增加显⽰示 imagePicker 的代码 [imagePicker setDelegate:self]; // 把 image picker 放到屏幕上(模态对话框) [self presentViewController:imagePicker animated:YES completion:nil];}

Page 18: 12 Camera

UINavigationControllerDelegate

• imagePicker 的初始化,设置和显⽰示都完成了,下⼀一步就是获取 imagePicker 选择的图⽚片

•需要在 DetailViewController 中实现我们前⾯面提到的 imagePickerController:didFinishPickingMediaWithInfo:

•UIImagePickerController 是 UINavigationController 的⼦子类,在类声明中添加下⻚页这些 protocol

Page 19: 12 Camera

image 的独⽴立存储

•我们可以直接在didFinishPickingMediaWithInfo: 中直接把选择的 image 设置给 imageView

•但是我们最好为 image 创建⼀一个单独的存储。

@interface DetailViewController : UIViewController <UINavigationControllerDelegate, UIImagePickerControllerDelegate>{

Page 20: 12 Camera

•我们把图⽚片放到 store 中,这样当 DetailViewController 的 view 下次在屏幕上显⽰示时,我们让 DetailViewController 从 image store 中提取图⽚片,然后放到它⾃自⼰己的 imageView

•⼀一般来讲,每次⼀一个 view controller 收到 viewWillAppear: 消息时都应该重新使⽤用数据填充它的视图的⼦子视图

Page 21: 12 Camera

创建BNRImageStore

Page 22: 12 Camera

•这样我们在真正显⽰示选中的图⽚片前,先创建⼀一个 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

Page 23: 12 Camera

实现 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;}

Page 24: 12 Camera

实现头⽂文件中声明的另外三个⽅方法

• setImage: 增加(更新)

• imageForKey: 获取

• deleteImageForKey: 删除

- (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];}

Page 25: 12 Camera

NSMutableDictionary

•经过上⾯面⼀一些操作,基本的 image store 功能就完成了。

•这⾥里⾯面我们使⽤用的是 NSMutableDictionary 来存储数据(key-value pair)

• key 是⼀一个唯⼀一值(⼀一般是字符串),value 是被存储在集合中的对象

Page 26: 12 Camera

NSDictionary

Page 27: 12 Camera

⽣生成和使⽤用键

Page 28: 12 Camera

•要使⽤用这个 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;

Page 29: 12 Camera

CFUUIDRef 和 CFUUIDCreate

•要保证 key 的唯⼀一性,可以使⽤用 UUID 的⽅方式

• CFUUIDRef 类型的对象表⽰示⼀一个 UUID,下⾯面在 imagePickerController:didFinishPickingMediaWithInfo: 中⽣生成⼀一个 UUID

UIImage *image = [info objectForKey:UIImagePickerControllerOriginalImage]; // ⽣生成⼀一个 CFUUID 对象 CFUUIDRef newUniqueID = CFUUIDCreate(kCFAllocatorDefault);

• CF对象的创建通过调⽤用以被创建的对象的类型开始,并且包含“Create”的函数(CFUUIDCreate)

•指定的第⼀一个参数是内存如何分配,传⼊入 kCFAllocateDefault 表⽰示由系统决定

Page 30: 12 Camera

CFUUIDCreateString 和 CFStringRef

• CFUUIDRef 是⼀一个字节数组(15个uint8)

•因为我们要把 UUID 作为字典的 key,以及在后⾯面存储到⽂文件系统的时候会把它作为⽂文件名,所以需要把 UUID 转成字符串

•可以通过调⽤用 CFUUIDCreateString 从 CFUUIDRef ⽣生成⼀一个字符串对象

// ⽣生成⼀一个 CFUUID 对象 CFUUIDRef newUniqueID = CFUUIDCreate(kCFAllocatorDefault); // 从 unique identifier ⽣生成⼀一个字符串 CFStringRef newUniqueIDString = CFUUIDCreateString(kCFAllocatorDefault, newUniqueID);

Page 31: 12 Camera

类型转换• 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]];

Page 32: 12 Camera

Core Foundation 和 toll-free bridging

Page 33: 12 Camera

•当⼀一个指向 Objective-C 对象的变量被销毁时,ARC 知道这个对象失去了⼀一个所有者。

•但是 ARC 并不对 Core Foundation 的对象做这些

•这样当 Core Foundation 的对象失去指针时,我们必须在丢掉这个指针前调⽤用⼀一个函数来告诉对象丢掉⼀一个所有者

Page 34: 12 Camera

•如果我们没有在失去指针前调⽤用 CFRelease ,被指向的对象仍然认为它有⼀一个所有者

•在告诉它丢掉⼀一个所有者之前丢掉指针会引起内存泄露:对象已经访问不了了,但还有所有者

Page 35: 12 Camera

CFRelease

•增加代码,告诉被 newUniqueIDString 和 newUniqueID 指向的对象丢掉⼀一个所有者,因为都是局部变量,⽅方法结束将销毁

[[BNRImageStore sharedStore] setImage:image forKey:[item imageKey]]; // lose owner CFRelease(newUniqueIDString); CFRelease(newUniqueID); // 把图⽚片放到我们屏幕上的 image view 中 [imageView setImage:image];

Page 36: 12 Camera

__bridge

• ARC 并不很会管理 Core Foundation 对象的内存

•在我们类型转换⼀一个 Core Foundation 指针到 它的 Objective-C 同级形式时,放⼀一个 __bridge 在调⽤用前⾯面,是告诉 ARC 不要像通常做的那样给 key 变量⼀一个所有权

NSString *key = (__bridge NSString *)newUniqueIDString;

Page 37: 12 Camera

结束 BNRImageStore

•现在 BNRItemStore 可以存储图⽚片,并且 BNRItem 有获得这个图⽚片的 key,我们需要告诉 DetailViewController 怎样从选中的 BNRItem 抓取图⽚片,并放⼊入它的 imageView

Page 38: 12 Camera

Cache

Page 39: 12 Camera

获取并显⽰示照⽚片•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]; }

Page 40: 12 Camera

删除旧图⽚片- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info{ // 如果存在旧图⽚片,先把它从 BNRImageStore 中删除 NSString *oldKey = [item imageKey]; if (oldKey) { [[BNRImageStore sharedStore] deleteImageForkey:oldKey]; } // 从 info 字典中获得拾取的图⽚片 UIImage *image = [info objectForKey:UIImagePickerControllerOriginalImage];

Page 41: 12 Camera

释放键盘

Page 42: 12 Camera

•让 DetailViewController 符合 UITextFieldDelegate protocol,实现 textFieldShouldReturn: ⽅方法来让⽂文本框字段在换⾏行键被按下时释放它的 !rst responder 状态以释放键盘

- (BOOL)textFieldShouldReturn:(UITextField *)textField{ [textField resignFirstResponder]; return YES;}

@interface DetailViewController : UIViewController <UINavigationControllerDelegate, UIImagePickerControllerDelegate, UITextFieldDelegate>

Page 43: 12 Camera

UIControll 和 Touch Up Inside 事件•我们希望⽤用户在 DetailViewController 的 view 上的任何地⽅方轻击都释放键盘,我们可以通过给 view 发送 endEditing: 消息实现释放键盘

•UIButton 可以在轻击时发送⼀一个 action 消息给它的 target,这种 target-action 特性是从 UIControl 继承的

Page 44: 12 Camera

•在 DetailViewController.xib 中选择 View 对象,在 identity inspector 中把 view 的类改成 UIControl

•然后从 view Control-drag 到⽅方法声明区域,事件选择 ”Touch Up Inside“

Page 45: 12 Camera

- (IBAction)backgroundTapped:(id)sender { [[self view] endEditing:YES];}