iOS 相机

手机APP开发 2016-10-05

本章节主要为之前项目 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(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];
        }
    }
}

相关推荐