iOS 相机

  本章节主要为之前项目 JXHomepwner 添加照片功能(项目地址)。具体任务就是显示一个 UIImagePickerController 对象,使用户能够为 JXItem 对象拍照并保存。拍摄的照片会和相应的 JXItem 对象建立关联,当用户进入某个 JXItem 对象的详细视图的时候,可以看见之前拍摄的照片。

  照片的文件可能很大,最后与 JXItem 对象的其他数据分开保存。我们将建立一个用于存储数据的类 JXImageStore ,负责保存 JXItem 对象的照片。JXImageStore 可以按需要获取并缓存照片,还可以在设备内存过低的时候清空缓存中的照片。

  • 通过 UIImageView 对象显示照片

  首先要将照片赋值给 JXDetailViewController 对象,才能在该对象的视图中显示。要在视图中显示照片信息,一个最简单的方法就是 UIImageView 对象。在 XIB 中放置一个 UIImageView 控件。

  UIImageView 对象会根据其  contentModel 属性来显示一张指定的图片模式。 contentModel 属性的作用是确定图片的  frame 内的显示位置和缩放模式。其默认值是  UIViewContentModelScaleToFill 。当其属性值是默认值时,UIImageView 对象会在显示图片时缩放图片的大小,使其能够填满整个视图空间,但是可能会改变图片的宽高比。

iOS 相机

#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 对象的个数。

iOS 相机

  建立关联

iOS 相机

  关联之后代码如下

#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(); NSString * randomSerialNumber = [NSString stringWithFormat:@"%c%c%c%c",
'' + arc4random_uniform(),
'A' + arc4random_uniform(),
'' + arc4random_uniform(),
'A' + arc4random_uniform()]; 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: 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];
}
}
}
上一篇:2018.09.26洛谷P3957 跳房子(二分+单调队列优化dp)


下一篇:POJ 2018 Best Cow Fences (二分答案构造新权值 or 斜率优化)