12 Camera

452 views

Published on

iOS Camera, 拍照,获取图像

Published in: Technology
0 Comments
1 Like
Statistics
Notes
  • Be the first to comment

No Downloads
Views
Total views
452
On SlideShare
0
From Embeds
0
Number of Embeds
2
Actions
Shares
0
Downloads
4
Comments
0
Likes
1
Embeds 0
No embeds

No notes for slide

12 Camera

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

×