iOS 相机
本章节主要为之前项目 JXHomepwner 添加照片功能(项目地址)。具体任务就是显示一个 UIImagePickerController 对象,使用户能够为 JXItem 对象拍照并保存。拍摄的照片会和相应的JXItem 对象建立关联,当用户进入某个 JXItem 对象的详细视图的时候,可以看见之前拍摄的照片。
照片的文件可能很大,最后与 JXItem 对象的其他数据分开保存。我们将建立一个用于存储数据的类 JXImageStore ,负责保存 JXItem 对象的照片。JXImageStore 可以按需要获取并缓存照片,还可以在设备内存过低的时候清空缓存中的照片。
- 通过 UIImageView 对象显示照片
首先要将照片赋值给JXDetailViewController 对象,才能在该对象的视图中显示。要在视图中显示照片信息,一个最简单的方法就是 UIImageView 对象。在 XIB 中放置一个 UIImageView 控件。
UIImageView 对象会根据其 contentModel 属性来显示一张指定的图片模式。 contentModel 属性的作用是确定图片的 frame 内的显示位置和缩放模式。其默认值是 UIViewContentModelScaleToFill 。当其属性值是默认值时,UIImageView 对象会在显示图片时缩放图片的大小,使其能够填满整个视图空间,但是可能会改变图片的宽高比。
#import "JXDetailViewController.h" #import "JXItem.h" @interface JXDetailViewController () @property (weak, nonatomic) IBOutlet UITextField *nameField; @property (weak, nonatomic) IBOutlet UITextField *seriaNumberField; @property (weak, nonatomic) IBOutlet UITextField *valueField; @property (weak, nonatomic) IBOutlet UILabel *dateLabel; @property (weak, nonatomic) IBOutlet UIImageView *imageView; @end @implementation JXDetailViewController - (void)viewWillDisappear:(BOOL)animated { [super viewWillDisappear:animated]; // 取消当前的第一响应对象 [self.view endEditing:YES]; // 将修改保存到 JXItem JXItem * item = self.item; item.itemName = self.nameField.text; item.serialnumber = self.seriaNumberField.text; item.valueInDollars = [self.valueField.text integerValue]; } - (void)viewDidLoad { [super viewDidLoad]; JXItem * item = self.item; self.nameField.text = item.itemName; self.seriaNumberField.text = item.itemName; self.valueField.text = [NSString stringWithFormat:@"%ld",item.valueInDollars]; // 创建 NSDdateFoemateter 对象,用于将 NSDate 对象转换成简单的日期字符串 static NSDateFormatter * dateFormatter = nil; if (!dateFormatter) { dateFormatter = [[NSDateFormatter alloc] init]; dateFormatter.dateStyle = NSDateFormatterMediumStyle; dateFormatter.timeStyle = NSDateFormatterNoStyle; } // 将转换后得到的日期字符串设置为 dateLabel 的标题 self.dateLabel.text = [dateFormatter stringFromDate:item.createDate]; } - (void)setItem:(JXItem *)item { _item = item; self.navigationItem.title = _item.itemName; } @end
添加相机按钮
为应用程序添加一个按钮,用于在启动拍照。为此,我们需要先创建一个 UIToolbar 对象,然后该对象放置在 JXDetailViewController 对象的视图底部,最后将按钮放置到 UIToolbar 对象上。
UIToolbar 的工作方式和 UINavigationBar 很相似,同样可以加入 UIBarButtonItem 对象。区别就是UINavigationBar 只能左右两端放置按钮,但是 UIToolbar 对象可以有一组UIBarButtonItem 对象。只要屏幕能够容纳,UIToolbar 对象自身并没有显示可以存放的 UIBarButtonItem 对象的个数。
建立关联
关联之后代码如下
#import "JXDetailViewController.h" #import "JXItem.h" @interface JXDetailViewController () @property (weak, nonatomic) IBOutlet UITextField *nameField; @property (weak, nonatomic) IBOutlet UITextField *seriaNumberField; @property (weak, nonatomic) IBOutlet UITextField *valueField; @property (weak, nonatomic) IBOutlet UILabel *dateLabel; @property (weak, nonatomic) IBOutlet UIImageView *imageView; @end @implementation JXDetailViewController - (IBAction)takePicture:(id)sender { } - (void)viewWillDisappear:(BOOL)animated { [super viewWillDisappear:animated]; // 取消当前的第一响应对象 [self.view endEditing:YES]; // 将修改保存到 JXItem JXItem * item = self.item; item.itemName = self.nameField.text; item.serialnumber = self.seriaNumberField.text; item.valueInDollars = [self.valueField.text integerValue]; } - (void)viewDidLoad { [super viewDidLoad]; JXItem * item = self.item; self.nameField.text = item.itemName; self.seriaNumberField.text = item.itemName; self.valueField.text = [NSString stringWithFormat:@"%ld",item.valueInDollars]; // 创建 NSDdateFoemateter 对象,用于将 NSDate 对象转换成简单的日期字符串 static NSDateFormatter * dateFormatter = nil; if (!dateFormatter) { dateFormatter = [[NSDateFormatter alloc] init]; dateFormatter.dateStyle = NSDateFormatterMediumStyle; dateFormatter.timeStyle = NSDateFormatterNoStyle; } // 将转换后得到的日期字符串设置为 dateLabel 的标题 self.dateLabel.text = [dateFormatter stringFromDate:item.createDate]; } - (void)setItem:(JXItem *)item { _item = item; self.navigationItem.title = _item.itemName; } @end
- 通过 UIImagePickerController 拍摄照片
接下来,我们需要在 takePicture: 方法中创建并显示 UIImagePickerController 对象。创建该对象时,必须为新创建的对象设置 sourceType 属性和 delegate 属性。
设置 UIImagePickerController对象的源
设置 sourceType 属性时必须使用特性的常量,这些常量表示UIImagePicker、Controller 对象获取照片的源。目前我们有三种可使用的常量。
1. UIImagePickerControllerSourceTypeCamera :用于用户拍摄一张新的图片
2. UIImagePickerControllerSourceTypePhotoLibrary :用于显示界面,让用户选择相册,然后从选中的相册中选一张照片
3. UIImagePickerControllerSourceTypeSavephotosAlbum :用于让用户从最近拍摄的照片里选择一张照片。
对于没有相机的设备(也就只有在模拟器上了),选取第一种类型是无效的,所以我们在使用第一种 变量之前,应该先向UIImagePickerController 类发送 isSourceTypeAvailable: 消息,检查设备时候支持相机。发送该消息时,需要传入待检查的选取类型常量。
+ (BOOL)isSourceTypeAvailable:(UIImagePickerControllerSourceType)sourceType; // returns YES if source is available (i.e. camera present)
该方法会返回一个布尔值,用来判定设备是否支持。
#import "JXDetailViewController.h" #import "JXItem.h" @interface JXDetailViewController () @property (weak, nonatomic) IBOutlet UITextField *nameField; @property (weak, nonatomic) IBOutlet UITextField *seriaNumberField; @property (weak, nonatomic) IBOutlet UITextField *valueField; @property (weak, nonatomic) IBOutlet UILabel *dateLabel; @property (weak, nonatomic) IBOutlet UIImageView *imageView; @property (weak, nonatomic) IBOutlet UIToolbar *toolbar; @end @implementation JXDetailViewController - (IBAction)takePicture:(id)sender { UIImagePickerController * imagePicker = [[UIImagePickerController alloc] init]; // 如果设备支持相机,就使用拍照模式 if ([UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypeCamera]) { imagePicker.sourceType = UIImagePickerControllerSourceTypeCamera; } else { imagePicker.sourceType = UIImagePickerControllerSourceTypePhotoLibrary; } } - (void)viewWillDisappear:(BOOL)animated { [super viewWillDisappear:animated]; // 取消当前的第一响应对象 [self.view endEditing:YES]; // 将修改保存到 JXItem JXItem * item = self.item; item.itemName = self.nameField.text; item.serialnumber = self.seriaNumberField.text; item.valueInDollars = [self.valueField.text integerValue]; } - (void)viewDidLoad { [super viewDidLoad]; JXItem * item = self.item; self.nameField.text = item.itemName; self.seriaNumberField.text = item.itemName; self.valueField.text = [NSString stringWithFormat:@"%ld",item.valueInDollars]; // 创建 NSDdateFoemateter 对象,用于将 NSDate 对象转换成简单的日期字符串 static NSDateFormatter * dateFormatter = nil; if (!dateFormatter) { dateFormatter = [[NSDateFormatter alloc] init]; dateFormatter.dateStyle = NSDateFormatterMediumStyle; dateFormatter.timeStyle = NSDateFormatterNoStyle; } // 将转换后得到的日期字符串设置为 dateLabel 的标题 self.dateLabel.text = [dateFormatter stringFromDate:item.createDate]; } - (void)setItem:(JXItem *)item { _item = item; self.navigationItem.title = _item.itemName; } @end
设置 UIImagePickerController 对象的委托
除了 sourceType 属性之外,还需要为 UIImagePickerController 对象设置委托。用户从 UIImagePickerController 对象中选择了一张图片之后,委托会受到
- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingImage:(UIImage *)image editingInfo:(nullable NSDictionary<NSString *,id> *)editingInfo NS_DEPRECATED_IOS(2_0, 3_0);(已经废弃)
- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary<NSString *,id> *)info
如果用户取消选中,那么会收到
- (void)imagePickerControllerDidCancel:(UIImagePickerController *)picker;
UIImagePickerController 对象的委托通常设置为需要获取照片的对象。因此,遵守协议 UINavigationControllerDelegate 和 UIImagePickerControllerDelegate 然后设置委托。
#import "JXDetailViewController.h" #import "JXItem.h" @interface JXDetailViewController ()<UINavigationControllerDelegate,UIImagePickerControllerDelegate> @property (weak, nonatomic) IBOutlet UITextField *nameField; @property (weak, nonatomic) IBOutlet UITextField *seriaNumberField; @property (weak, nonatomic) IBOutlet UITextField *valueField; @property (weak, nonatomic) IBOutlet UILabel *dateLabel; @property (weak, nonatomic) IBOutlet UIImageView *imageView; @property (weak, nonatomic) IBOutlet UIToolbar *toolbar; @end @implementation JXDetailViewController - (IBAction)takePicture:(id)sender { UIImagePickerController * imagePicker = [[UIImagePickerController alloc] init]; // 如果设备支持相机,就使用拍照模式 if ([UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypeCamera]) { imagePicker.sourceType = UIImagePickerControllerSourceTypeCamera; } else { imagePicker.sourceType = UIImagePickerControllerSourceTypePhotoLibrary; } imagePicker.delegate = self; } - (void)viewWillDisappear:(BOOL)animated { [super viewWillDisappear:animated]; // 取消当前的第一响应对象 [self.view endEditing:YES]; // 将修改保存到 JXItem JXItem * item = self.item; item.itemName = self.nameField.text; item.serialnumber = self.seriaNumberField.text; item.valueInDollars = [self.valueField.text integerValue]; } - (void)viewDidLoad { [super viewDidLoad]; JXItem * item = self.item; self.nameField.text = item.itemName; self.seriaNumberField.text = item.itemName; self.valueField.text = [NSString stringWithFormat:@"%ld",item.valueInDollars]; // 创建 NSDdateFoemateter 对象,用于将 NSDate 对象转换成简单的日期字符串 static NSDateFormatter * dateFormatter = nil; if (!dateFormatter) { dateFormatter = [[NSDateFormatter alloc] init]; dateFormatter.dateStyle = NSDateFormatterMediumStyle; dateFormatter.timeStyle = NSDateFormatterNoStyle; } // 将转换后得到的日期字符串设置为 dateLabel 的标题 self.dateLabel.text = [dateFormatter stringFromDate:item.createDate]; } - (void)setItem:(JXItem *)item { _item = item; self.navigationItem.title = _item.itemName; } @end
以模态的形式显示 UIImagePickerController 对象
为 UIImagePickerController 对象设置了源类型和委托之后,就可以在屏幕中显示该对象。和之前的 UIViewController 子类对象不同,该对象必须以模态(modal)形式显示。
要以模态形式显示某个视图控制器,需要向窗口当前显示的 UIViewController 对象发送
- (void)presentViewController:(UIViewController *)viewControllerToPresent animated: (BOOL)flag completion:(void (^ __nullable)(void))completion NS_AVAILABLE_IOS(5_0);
同时第一个参数为需要显示的视图控制器,第二个参数设置时候有动画效果,第三个参数为显示之后需要进行什么操作。
#import "JXDetailViewController.h" #import "JXItem.h" @interface JXDetailViewController ()<UINavigationControllerDelegate,UIImagePickerControllerDelegate> @property (weak, nonatomic) IBOutlet UITextField *nameField; @property (weak, nonatomic) IBOutlet UITextField *seriaNumberField; @property (weak, nonatomic) IBOutlet UITextField *valueField; @property (weak, nonatomic) IBOutlet UILabel *dateLabel; @property (weak, nonatomic) IBOutlet UIImageView *imageView; @property (weak, nonatomic) IBOutlet UIToolbar *toolbar; @end @implementation JXDetailViewController - (IBAction)takePicture:(id)sender { UIImagePickerController * imagePicker = [[UIImagePickerController alloc] init]; // 如果设备支持相机,就使用拍照模式 if ([UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypeCamera]) { imagePicker.sourceType = UIImagePickerControllerSourceTypeCamera; } else { imagePicker.sourceType = UIImagePickerControllerSourceTypePhotoLibrary; } imagePicker.delegate = self; [self presentViewController:imagePicker animated:YES completion:nil]; } - (void)viewWillDisappear:(BOOL)animated { [super viewWillDisappear:animated]; // 取消当前的第一响应对象 [self.view endEditing:YES]; // 将修改保存到 JXItem JXItem * item = self.item; item.itemName = self.nameField.text; item.serialnumber = self.seriaNumberField.text; item.valueInDollars = [self.valueField.text integerValue]; } - (void)viewDidLoad { [super viewDidLoad]; JXItem * item = self.item; self.nameField.text = item.itemName; self.seriaNumberField.text = item.itemName; self.valueField.text = [NSString stringWithFormat:@"%ld",item.valueInDollars]; // 创建 NSDdateFoemateter 对象,用于将 NSDate 对象转换成简单的日期字符串 static NSDateFormatter * dateFormatter = nil; if (!dateFormatter) { dateFormatter = [[NSDateFormatter alloc] init]; dateFormatter.dateStyle = NSDateFormatterMediumStyle; dateFormatter.timeStyle = NSDateFormatterNoStyle; } // 将转换后得到的日期字符串设置为 dateLabel 的标题 self.dateLabel.text = [dateFormatter stringFromDate:item.createDate]; } - (void)setItem:(JXItem *)item { _item = item; self.navigationItem.title = _item.itemName; } @end
构建并运行,发现会crash,在iOS 10 上我们需要设置一些权限。
相机权限: Privacy - Camera Usage Description 是否允许此App使用你的相机?
相册权限: Privacy - Photo Library Usage Description 是否允许此App访问你的媒体资料库?
保存照片
选择一张照片之后,UIImagePickerController 对象就会自动关闭,返回我们之前界面,这时候我们之前界面是不可能有任何数据的,因为我们选择照片之后没有任何一句代码是保存我们选中的照片的。所以我们需要通过上面介绍的代理方法来进行选中后的保存回调。
#import "JXDetailViewController.h" #import "JXItem.h" @interface JXDetailViewController ()<UINavigationControllerDelegate,UIImagePickerControllerDelegate> @property (weak, nonatomic) IBOutlet UITextField *nameField; @property (weak, nonatomic) IBOutlet UITextField *seriaNumberField; @property (weak, nonatomic) IBOutlet UITextField *valueField; @property (weak, nonatomic) IBOutlet UILabel *dateLabel; @property (weak, nonatomic) IBOutlet UIImageView *imageView; @property (weak, nonatomic) IBOutlet UIToolbar *toolbar; @end @implementation JXDetailViewController - (IBAction)takePicture:(id)sender { UIImagePickerController * imagePicker = [[UIImagePickerController alloc] init]; // 如果设备支持相机,就使用拍照模式 if ([UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypeCamera]) { imagePicker.sourceType = UIImagePickerControllerSourceTypeCamera; } else { imagePicker.sourceType = UIImagePickerControllerSourceTypePhotoLibrary; } imagePicker.delegate = self; [self presentViewController:imagePicker animated:YES completion:nil]; } - (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary<NSString *,id> *)info { // 通过 info 字典获取选中的照片 UIImage * image = info[UIImagePickerControllerOriginalImage]; // 将照片放入 UIImageView 对象 self.imageView.image = image; // 关闭 UIImagePickerController 对象 [self dismissViewControllerAnimated:YES completion:nil]; } - (void)viewWillDisappear:(BOOL)animated { [super viewWillDisappear:animated]; // 取消当前的第一响应对象 [self.view endEditing:YES]; // 将修改保存到 JXItem JXItem * item = self.item; item.itemName = self.nameField.text; item.serialnumber = self.seriaNumberField.text; item.valueInDollars = [self.valueField.text integerValue]; } - (void)viewDidLoad { [super viewDidLoad]; JXItem * item = self.item; self.nameField.text = item.itemName; self.seriaNumberField.text = item.itemName; self.valueField.text = [NSString stringWithFormat:@"%ld",item.valueInDollars]; // 创建 NSDdateFoemateter 对象,用于将 NSDate 对象转换成简单的日期字符串 static NSDateFormatter * dateFormatter = nil; if (!dateFormatter) { dateFormatter = [[NSDateFormatter alloc] init]; dateFormatter.dateStyle = NSDateFormatterMediumStyle; dateFormatter.timeStyle = NSDateFormatterNoStyle; } // 将转换后得到的日期字符串设置为 dateLabel 的标题 self.dateLabel.text = [dateFormatter stringFromDate:item.createDate]; } - (void)setItem:(JXItem *)item { _item = item; self.navigationItem.title = _item.itemName; } @end
- 创建 JXImageStore
JXImageStore 对象将负责保存用户所拍摄的所有照片。
#import <UIKit/UIKit.h> @interface JXImageStore : NSObject + (instancetype)sharedStore; /** * 保存图片 * * @param image 图片(字典中值) * @param key 图片名称(字典中键) */ - (void)setImage:(UIImage *)image forKey:(NSString *)key; /** * 取出图片 * * @param key 图片名称 * * @return 取出的图片 */ - (UIImage *)imageForKey:(NSString *)key; /** * 删除图片 * * @param key 根据key删除图片 */ - (void)deleteImageForKey:(NSString *)key; @end
#import "JXImageStore.h" @interface JXImageStore () /** 存储照片 */ @property (nonatomic,strong) NSMutableDictionary * dictionary; @end @implementation JXImageStore + (instancetype)sharedStore { static JXImageStore * shareStore = nil; if (!shareStore) { shareStore = [[self alloc] init]; } return shareStore; } - (instancetype)init { self = [super init]; if (self) { self.dictionary = [NSMutableDictionary dictionary]; } return self; } - (void)setImage:(UIImage *)image forKey:(NSString *)key { self.dictionary[key] = image; } - (UIImage *)imageForKey:(NSString *)key { return self.dictionary[key]; } - (void)deleteImageForKey:(NSString *)key { if (!key) return; [self.dictionary removeObjectForKey:key]; } @end
- NSDictionary
JXImageStore 的属性 dictionary 是一个指向 NSMutableDictionary 对象的指针。和数组对象类似,字典对象也是 Collection 对象,也有可修改和不可修改版本。
字典对象是由 键值对 组成的。这里的键一定是可哈希的补课修改的对象,通常我们使用NSString对象来做键。
字典非常有用,其中最常用的就是可变数据结构和查询表。
对于可变数据结构。为了在代码中描述一个模型对象,常见的做法是创建一个 NSObject 的子类,然后添加模型对象的相关属性。例如,对于一个表示 ‘人’ 的模型对象来说,可以创建一个名为 Person 的 NSObject 的子类,然后添加姓名,年龄和其他所有需要的属性。类似的, NSDictionary 也可以用来描述模型对象。还是以 ‘人’ 为例, NSDictionary 中可以针对姓名、年龄和其他所需要的属性保存响应的键值对。
使用NSDictionary 和与我们自定义的模型的区别就是,我们自定义的模型要求事先明确定义好各项属性,并且之后我们无法动态添加新的属性,也无法删除属性,我们唯一能做的就是更改属性值。相反,我们使用字典,那么需要我们自定义模型的属性在字典中就是一系列的键值对,这样就有很大的操作性了。我们可以自由的做增删改操作。
当然并不是所有的模型对象都可以通过 NSDictionary 来描述。大部分模型对象具有严格的定义和特殊的数据处理方式,不适合采用简单的键值对来管理数据。
对于查询表。我们可能会在代码中见过这样的代码
- (void)changeCharacterClass:(id)sender { NSString * enterText = nil; NSString * cc = nil; if ([enterText isEqualToString:@"W"]) { cc = @"w"; } else if ([enterText isEqualToString:@"A"]) { cc = @"a"; } else if ([enterText isEqualToString:@"B"]) { cc = @"b"; } }
但是当我们需要编写包含大量 if-else 或者switch 语句的代码时,通常应该考虑替换为 NSDictionary 。字典可以事先在两组对象之间建立一对一的映射关系。
NSMutableDictionary *lookup = [[NSMutableDictionary alloc] init]; [lookup setObject:@"a" forKey:@"A"]; [lookup setObject:@"b" forKey:@"B"]; [lookup setObject:@"w" forKey:@"W"];
有了 lookup 查询表, changeCharacterClass 方法就会变得很简单了
- (void)changeCharacterClass:(id)sender { NSString * enterText = nil; NSString * cc = nil; cc = [lookup objectForKey:enterText]; }
使用NSDictionary 查询表的另一个优点就是:不需要再方法中硬编码所有数据,相反,可以将数据保存在文件系统或者远程服务器中,甚至可以由用户动态添加或者编辑等。
JXImageStore 将使用 NSDictionary 查询表存储照片。JXImageStore 将会为每一张照片生成唯一的键,之后可以通过键来查找对应的照片。
- 创建并使用键
将照片加入 JXImageStore 对象时,需要针对不同的照片使用不用的键,然后将这个赋值给响应的JXItem 对象。当 JXDetailViewController 对象要从 JXImageStore 对象载入照片时,需要先从 JXItem 对象得到照片的键,然后通过 JXImageStore 对象查询相对应的值。
#import <Foundation/Foundation.h> @interface JXItem : NSObject /** 创建日期 */ @property (nonatomic,strong,readonly) NSDate * createDate; /** 名称 */ @property (nonatomic,strong) NSString * itemName; /** 编号 */ @property (nonatomic,strong) NSString * serialnumber; /** 价值 */ @property (nonatomic,assign) NSInteger valueInDollars; /** JXImageStore中的键 */ @property (nonatomic,strong) NSString * itemKey; + (instancetype)randomItem; /** * JXItem类指定的初始化方法 * @return 类对象 */ - (instancetype)initWithItemName:(NSString *)name valueInDollars:(NSInteger)value serialNumber:(NSString *)sNumber; - (instancetype)initWithItemName:(NSString *)name; @end
照片的键不能重复,否则无法通过我们创建的对象准确的保存照片信息。这里我们将使用Cocoa Touch 提供的一种机制来生成无重复的标识。这种机制可以生成唯一标识(UUID,也叫GUID)。每个 NSUUID 类的对象都标识一个唯一的 UUID 。UUID是基于时间,计数器,和硬件标识(通常为无线网卡的MAC地址)
等数据计算生成的。
#import "JXDetailViewController.h" #import "JXItem.h" #import "JXImageStore.h" @interface JXDetailViewController ()<UINavigationControllerDelegate,UIImagePickerControllerDelegate> @property (weak, nonatomic) IBOutlet UITextField *nameField; @property (weak, nonatomic) IBOutlet UITextField *seriaNumberField; @property (weak, nonatomic) IBOutlet UITextField *valueField; @property (weak, nonatomic) IBOutlet UILabel *dateLabel; @property (weak, nonatomic) IBOutlet UIImageView *imageView; @property (weak, nonatomic) IBOutlet UIToolbar *toolbar; @end
#import "JXItem.h" @implementation JXItem + (instancetype)randomItem { // 创建不可变数组对象,包含三个形容词 NSArray * randomAdjectiveList = @[ @"Fluffy", @"Rusty", @"Shiny" ]; // 创建不可变数组对象,包含三个名词 NSArray * randomNounList = @[ @"Bear", @"Spork", @"Mac" ]; // 根据数组对象所含的对象的个数,得到随机索引 // 注意:运算符%是模运算符,运算后得到的是余数 NSInteger adjectiveIndex = arc4random() % randomAdjectiveList.count; NSInteger nounIndex = arc4random() % randomNounList.count; // 注意,类型为NSInteger 的变量不是对象 NSString * randomName = [NSString stringWithFormat:@"%@ %@",randomAdjectiveList[adjectiveIndex],randomNounList[nounIndex]]; NSInteger randomValue = arc4random_uniform(100); NSString * randomSerialNumber = [NSString stringWithFormat:@"%c%c%c%c", '0' + arc4random_uniform(10), 'A' + arc4random_uniform(26), '0' + arc4random_uniform(10), 'A' + arc4random_uniform(26)]; JXItem * newItem = [[self alloc] initWithItemName:randomName valueInDollars:randomValue serialNumber:randomSerialNumber]; return newItem; } - (NSString *)description { NSString * descriptionString = [NSString stringWithFormat:@"%@ (%@):Worth $%zd, recorded on %@",self.itemName,self.serialnumber,self.valueInDollars,self.createDate]; return descriptionString; } - (instancetype)initWithItemName:(NSString *)name valueInDollars:(NSInteger)value serialNumber:(NSString *)sNumber { // 调用父类的指定初始化方法 self = [super init]; // 父类的指定初始化方法是否成功创建了对象 if (self) { // 为实例变量设置初始值 _itemName = name; _valueInDollars = value; _serialnumber = sNumber; // 设置_createDate为当前时间 _createDate = [NSDate date]; // 创建一个 NSUUID 对象 NSUUID * uuid = [[NSUUID alloc] init]; NSString * key = [uuid UUIDString]; _itemKey = key; } // 返回初始化后的对象的新地址 return self; } - (instancetype)initWithItemName:(NSString *)name { return [self initWithItemName:name valueInDollars:0 serialNumber:@""]; } - (instancetype)init { return [self initWithItemName:@"Item"]; } - (void)dealloc { NSLog(@"Destoryed:%@",self); } @end
#import "JXDetailViewController.h" #import "JXItem.h" #import "JXImageStore.h" @interface JXDetailViewController ()<UINavigationControllerDelegate,UIImagePickerControllerDelegate> @property (weak, nonatomic) IBOutlet UITextField *nameField; @property (weak, nonatomic) IBOutlet UITextField *seriaNumberField; @property (weak, nonatomic) IBOutlet UITextField *valueField; @property (weak, nonatomic) IBOutlet UILabel *dateLabel; @property (weak, nonatomic) IBOutlet UIImageView *imageView; @property (weak, nonatomic) IBOutlet UIToolbar *toolbar; @end @implementation JXDetailViewController - (IBAction)takePicture:(id)sender { UIImagePickerController * imagePicker = [[UIImagePickerController alloc] init]; // 如果设备支持相机,就使用拍照模式 if ([UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypeCamera]) { imagePicker.sourceType = UIImagePickerControllerSourceTypeCamera; } else { imagePicker.sourceType = UIImagePickerControllerSourceTypePhotoLibrary; } imagePicker.delegate = self; [self presentViewController:imagePicker animated:YES completion:nil]; } - (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary<NSString *,id> *)info { // 通过 info 字典获取选中的照片 UIImage * image = info[UIImagePickerControllerOriginalImage]; // 以 itemKey 为键,将照片存到自定义类中 [[JXImageStore sharedStore] setImage:image forKey:self.item.itemKey]; // 将照片放入 UIImageView 对象 self.imageView.image = image; // 关闭 UIImagePickerController 对象 [self dismissViewControllerAnimated:YES completion:nil]; } - (void)viewWillDisappear:(BOOL)animated { [super viewWillDisappear:animated]; // 取消当前的第一响应对象 [self.view endEditing:YES]; // 将修改保存到 JXItem JXItem * item = self.item; item.itemName = self.nameField.text; item.serialnumber = self.seriaNumberField.text; item.valueInDollars = [self.valueField.text integerValue]; } - (void)viewDidLoad { [super viewDidLoad]; JXItem * item = self.item; self.nameField.text = item.itemName; self.seriaNumberField.text = item.itemName; self.valueField.text = [NSString stringWithFormat:@"%ld",item.valueInDollars]; // 创建 NSDdateFoemateter 对象,用于将 NSDate 对象转换成简单的日期字符串 static NSDateFormatter * dateFormatter = nil; if (!dateFormatter) { dateFormatter = [[NSDateFormatter alloc] init]; dateFormatter.dateStyle = NSDateFormatterMediumStyle; dateFormatter.timeStyle = NSDateFormatterNoStyle; } // 将转换后得到的日期字符串设置为 dateLabel 的标题 self.dateLabel.text = [dateFormatter stringFromDate:item.createDate]; } - (void)setItem:(JXItem *)item { _item = item; self.navigationItem.title = _item.itemName; } @end
每当 JXDetailViewController 获取到 UIImage 对象之后,都会将其存入到 JXImageStore 对象。
类似的,在用户删除了某个 JXItem 对象后,需要同时在 JXImageStore 对象中删除对应的 UIImage 对象。
#import "JXItemStore.h" #import "JXItem.h" #import "JXImageStore.h" @interface JXItemStore () /** 可变数组,用来操作 JXItem 对象 */ @property (nonatomic,strong) NSMutableArray * privateItems; @end @implementation JXItemStore // 单粒对象 + (instancetype)sharedStore { static JXItemStore * sharedStore = nil; // 判断是否需要创建一个 sharedStore 对象 if (!sharedStore) { sharedStore = [[self alloc] init]; } return sharedStore; } - (NSArray *)allItem { return [self.privateItems copy]; } - (JXItem *)createItem { JXItem * item = [JXItem randomItem]; [self.privateItems addObject:item]; return item; } /** * 还可以调用 [self.privateItems removeObject:item] * [self.privateItems removeObjectIdenticalTo:item] 与上面的方法的区别就是:上面的方法会枚举数组,向每一个数组发送 isEqual: 消息。 * isEqual: 的作用是判断当前对象和传入对象所包含的数据是否相等。可能会复写 这个方法。 * removeObjectIdenticalTo: 方法不会比较对象所包含的数据,只会比较指向对象的指针 * * @param item 需要删除的对象 */ - (void)removeItem:(JXItem *)item { [self.privateItems removeObjectIdenticalTo:item]; [[JXImageStore sharedStore] deleteImageForKey:item.itemKey]; } - (void)moveItemAtIndex:(NSInteger)fromIndex toIndex:(NSInteger)toIndex { // 如果起始位置和最终位置相同,则不懂 if (fromIndex == toIndex) return; // 需要移动的对象的指针 JXItem * item = self.privateItems[fromIndex]; // 将 item 从 allItem 数组中移除 [self.privateItems removeObjectAtIndex:fromIndex]; // 根据新的索引位置,将item 插入到allItem 数组中 [self.privateItems insertObject:item atIndex:toIndex]; } #pragma mark - 懒加载 - (NSMutableArray *)privateItems{ if (_privateItems == nil) { _privateItems = [[NSMutableArray alloc] init]; } return _privateItems; } @end
- 使用 JXImageStore
当 JXHomepwner 需要显示 JXDetailViewController 对象的视图时,该对象需要通过当前选中的 JXItem 对象的 itemKey 属性来从 JXImageStore 对象中得到相应的照片。
#import "JXDetailViewController.h" #import "JXItem.h" #import "JXImageStore.h" @interface JXDetailViewController ()<UINavigationControllerDelegate,UIImagePickerControllerDelegate> @property (weak, nonatomic) IBOutlet UITextField *nameField; @property (weak, nonatomic) IBOutlet UITextField *seriaNumberField; @property (weak, nonatomic) IBOutlet UITextField *valueField; @property (weak, nonatomic) IBOutlet UILabel *dateLabel; @property (weak, nonatomic) IBOutlet UIImageView *imageView; @property (weak, nonatomic) IBOutlet UIToolbar *toolbar; @end @implementation JXDetailViewController - (IBAction)takePicture:(id)sender { UIImagePickerController * imagePicker = [[UIImagePickerController alloc] init]; // 如果设备支持相机,就使用拍照模式 if ([UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypeCamera]) { imagePicker.sourceType = UIImagePickerControllerSourceTypeCamera; } else { imagePicker.sourceType = UIImagePickerControllerSourceTypePhotoLibrary; } imagePicker.delegate = self; [self presentViewController:imagePicker animated:YES completion:nil]; } - (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary<NSString *,id> *)info { // 通过 info 字典获取选中的照片 UIImage * image = info[UIImagePickerControllerOriginalImage]; // 以 itemKey 为键,将照片存到自定义类中 [[JXImageStore sharedStore] setImage:image forKey:self.item.itemKey]; // 将照片放入 UIImageView 对象 self.imageView.image = image; // 关闭 UIImagePickerController 对象 [self dismissViewControllerAnimated:YES completion:nil]; } - (void)viewWillDisappear:(BOOL)animated { [super viewWillDisappear:animated]; // 取消当前的第一响应对象 [self.view endEditing:YES]; // 将修改保存到 JXItem JXItem * item = self.item; item.itemName = self.nameField.text; item.serialnumber = self.seriaNumberField.text; item.valueInDollars = [self.valueField.text integerValue]; } - (void)viewDidLoad { [super viewDidLoad]; JXItem * item = self.item; self.nameField.text = item.itemName; self.seriaNumberField.text = item.itemName; self.valueField.text = [NSString stringWithFormat:@"%ld",item.valueInDollars]; // 创建 NSDdateFoemateter 对象,用于将 NSDate 对象转换成简单的日期字符串 static NSDateFormatter * dateFormatter = nil; if (!dateFormatter) { dateFormatter = [[NSDateFormatter alloc] init]; dateFormatter.dateStyle = NSDateFormatterMediumStyle; dateFormatter.timeStyle = NSDateFormatterNoStyle; } // 将转换后得到的日期字符串设置为 dateLabel 的标题 self.dateLabel.text = [dateFormatter stringFromDate:item.createDate]; } - (void)viewWillAppear:(BOOL)animated { [super viewWillAppear:animated]; JXItem * item = self.item; // 根据 itemKey,获取照片 UIImage * imageToDisplay = [[JXImageStore sharedStore] imageForKey:item.itemKey]; // 将得到的图片赋值 self.imageView.image = imageToDisplay; } - (void)setItem:(JXItem *)item { _item = item; self.navigationItem.title = _item.itemName; } @end
- 关闭键盘
#import "JXDetailViewController.h" #import "JXItem.h" #import "JXImageStore.h" @interface JXDetailViewController ()<UINavigationControllerDelegate,UIImagePickerControllerDelegate,UITextFieldDelegate> @property (weak, nonatomic) IBOutlet UITextField *nameField; @property (weak, nonatomic) IBOutlet UITextField *seriaNumberField; @property (weak, nonatomic) IBOutlet UITextField *valueField; @property (weak, nonatomic) IBOutlet UILabel *dateLabel; @property (weak, nonatomic) IBOutlet UIImageView *imageView; @property (weak, nonatomic) IBOutlet UIToolbar *toolbar; @end @implementation JXDetailViewController - (IBAction)takePicture:(id)sender { UIImagePickerController * imagePicker = [[UIImagePickerController alloc] init]; // 如果设备支持相机,就使用拍照模式 if ([UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypeCamera]) { imagePicker.sourceType = UIImagePickerControllerSourceTypeCamera; } else { imagePicker.sourceType = UIImagePickerControllerSourceTypePhotoLibrary; } imagePicker.delegate = self; [self presentViewController:imagePicker animated:YES completion:nil]; } - (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary<NSString *,id> *)info { // 通过 info 字典获取选中的照片 UIImage * image = info[UIImagePickerControllerOriginalImage]; // 以 itemKey 为键,将照片存到自定义类中 [[JXImageStore sharedStore] setImage:image forKey:self.item.itemKey]; // 将照片放入 UIImageView 对象 self.imageView.image = image; // 关闭 UIImagePickerController 对象 [self dismissViewControllerAnimated:YES completion:nil]; } - (void)viewWillDisappear:(BOOL)animated { [super viewWillDisappear:animated]; // 取消当前的第一响应对象 [self.view endEditing:YES]; // 将修改保存到 JXItem JXItem * item = self.item; item.itemName = self.nameField.text; item.serialnumber = self.seriaNumberField.text; item.valueInDollars = [self.valueField.text integerValue]; } - (void)viewDidLoad { [super viewDidLoad]; JXItem * item = self.item; self.nameField.text = item.itemName; self.seriaNumberField.text = item.itemName; self.valueField.text = [NSString stringWithFormat:@"%ld",item.valueInDollars]; // 创建 NSDdateFoemateter 对象,用于将 NSDate 对象转换成简单的日期字符串 static NSDateFormatter * dateFormatter = nil; if (!dateFormatter) { dateFormatter = [[NSDateFormatter alloc] init]; dateFormatter.dateStyle = NSDateFormatterMediumStyle; dateFormatter.timeStyle = NSDateFormatterNoStyle; } // 将转换后得到的日期字符串设置为 dateLabel 的标题 self.dateLabel.text = [dateFormatter stringFromDate:item.createDate]; } - (void)viewWillAppear:(BOOL)animated { [super viewWillAppear:animated]; JXItem * item = self.item; // 根据 itemKey,获取照片 UIImage * imageToDisplay = [[JXImageStore sharedStore] imageForKey:item.itemKey]; // 将得到的图片赋值 self.imageView.image = imageToDisplay; } - (void)setItem:(JXItem *)item { _item = item; self.navigationItem.title = _item.itemName; } - (BOOL)textFieldShouldReturn:(UITextField *)textField { [textField resignFirstResponder]; return YES; } @end
- 摄像
前面我们知道了如何解决拍照、保存、读取等问题。现在我们简单了解一下摄像的问题。
UIImagePickerController 对象可以选择的媒体类型又两种,分别为静态照片和视频。 mediaTypes 数组默认只包含产概念股字符串 kUTTypeImage 因此,如果不修改对象的 mediaTypes 属性,那么用户就只能够使用相机拍摄照片。
添加摄像只需将常量字符串 kUTTypeMovie 加入到 mediaTypes 数组中即可。对于那些不支持摄像的设备(那么我们就放弃吧)同样可以使用
+ (nullable NSArray<NSString *> *)availableMediaTypesForSourceType:(UIImagePickerControllerSourceType)sourceType; // returns array of available media types (i.e. kUTTypeImage)
使用
UIImagePickerController * imagePicker = [[UIImagePickerController alloc] init]; NSArray * availableTypes = [UIImagePickerController availableMediaTypesForSourceType:UIImagePickerControllerSourceTypeCamera]; imagePicker.mediaTypes = availableTypes; imagePicker.sourceType = UIImagePickerControllerSourceTypeCamera; imagePicker.delegate = self;
加入摄像功能的 UIImagePickerController 界面会多出一个开关,可以在照相模式或者摄像模式之间切换。如果我们选中的是摄像模式,就需要在
- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary<NSString *,id> *)info
处理结果。当我们处理的是静态照片的时候,传入到上述方法中的 info 参数会有包含一个 UIImage 对象,以对应整张图片。但是针对摄像,UIIImagePickerController 对象会将拍摄到的视频存入临时目录,因为移动内存有限。当用户拍摄结束的时候,该对象的委托对象就会收到上述消息,并且在 info 参数中会有一个包含视频的文件路径,获取方式
- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary<NSString *,id> *)info { NSURL * mediaURL = info[UIImagePickerControllerMediaURL]; }
但是临时目录是不安全的,随时可能会被清楚,所以我们应该将其移动到其他目录
- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary<NSString *,id> *)info { NSURL * mediaURL = info[UIImagePickerControllerMediaURL]; if (mediaURL) { // 确定设备支持视频 if (UIVideoAtPathIsCompatibleWithSavedPhotosAlbum([mediaURL path])) { // 将视频存入相册 UISaveVideoAtPathToSavedPhotosAlbum([mediaURL path], nil, nil, nil); // 删除临时目录下的视频 [[NSFileManager defaultManager] removeItemAtPath:[mediaURL path] error:nil]; } } }