手机APP开发 2016-10-05
本章节主要为之前项目 JXHomepwner 添加照片功能(项目地址)。具体任务就是显示一个 UIImagePickerController 对象,使用户能够为 JXItem 对象拍照并保存。拍摄的照片会和相应的JXItem 对象建立关联,当用户进入某个 JXItem 对象的详细视图的时候,可以看见之前拍摄的照片。
照片的文件可能很大,最后与 JXItem 对象的其他数据分开保存。我们将建立一个用于存储数据的类 JXImageStore ,负责保存 JXItem 对象的照片。JXImageStore 可以按需要获取并缓存照片,还可以在设备内存过低的时候清空缓存中的照片。
首先要将照片赋值给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
接下来,我们需要在 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 对象将负责保存用户所拍摄的所有照片。
#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
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
当 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]; } } }