现在的app中,对照片和视频的操作非常频繁,如:各类软件个人图像的选取,社交软件照片及视频的分享,评论上图等。。。IOS8中更新的PHImageManager对于想要操控手机图片或视频的程序猿们无疑是最好的选择。
获取手机照片并回显
大家都应该用过手机QQ,当我们要用QQ发送图片给小伙伴时,点击发送图片icon,QQ会非常贴心的将最近几张照片给我们呈现出来,供我们选择。用PHImageManager能很快的实现这样一个功能。在此做个简单的类似的实现:
在项目build phase中添加Libraries Photos.framework 并在vc中引入头文件
#import <Photos/Photos.h>
获取访问照片权限
- (void)requestPermissons { //获取当前状态:PHAuthorizationStatusNotDetermined--未知 // PHAuthorizationStatusRestricted--受限制的(用户不能更改某个app的此状态,或许某些活动限制设置能改变如:家长模式) // PHAuthorizationStatusDenied--拒绝访问 // PHAuthorizationStatusAuthorized--可以访问 PHAuthorizationStatus status = [PHPhotoLibrary authorizationStatus]; __weak RCCachingLocalImage *weakSelf = self; if (status == PHAuthorizationStatusNotDetermined) { [PHPhotoLibrary requestAuthorization:^(PHAuthorizationStatus status) { if (status == PHAuthorizationStatusAuthorized) { //获取手机照片库 //[weakSelf loadImages]; }else{ //弹出提示信息 [weakSelf alertMsg]; } }]; }else if(status == PHAuthorizationStatusAuthorized){ //获取手机照片库 //[self loadImages]; }else{ //弹出提示信息 [self alertMsg]; } }
构建这样一个场景:在当前viewcontroller中点击button 然后全屏显示最新拍摄的照片或者截屏图片。UI代码如下:
在Extension中定义全局变量
@interface RCCachingLocalImage() @property (nonatomic, strong) NSMutableArray *assets;//用于存储所有的照片。 @property (nonatomic, strong) UIButton *showImgBnt; @property (nonatomic, strong) UIImageView *imgView; @end
然后再.m中加入
- (void)viewDidLoad { [super viewDidLoad]; self.view.backgroundColor = [UIColor whiteColor]; [self requestPermissons]; UIButton *navButton = [UIButton new]; navButton.frame = CGRectMake(130, CGRectGetHeight(self.view.frame) - 200, CGRectGetWidth(self.view.frame)-2*130, 40); navButton.backgroundColor = [UIColor blueColor]; [navButton setTitle:@"Show Image" forState:UIControlStateNormal]; [navButton addTarget:self action:@selector(showImage) forControlEvents:UIControlEventTouchUpInside]; [self.view addSubview:navButton]; self.showImgBtn = navButton; UIImageView *imageView = [UIImageView new]; imageView.backgroundColor = [UIColor greenColor]; imageView.frame = self.view.frame; [self.view addSubview:imageView]; self.imgView = imageView; self.imgView.hidden = YES; }
实现showImage方法 显示图片,隐藏Button
- (void)showImage
{
self.showImgBnt.hidden = YES;
self.imgView.hidden = NO;
//获取相册里所有的照片
PHFetchResult *result = [PHAsset fetchAssetsWithMediaType:PHAssetMediaTypeImage options:nil];
self.assets = [[NSMutableArray alloc] init];
[result enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
if ([obj isKindOfClass:[PHAsset class]]) {
[self.assets addObject:obj];
}
}];
//最后一张图为最新
PHAsset *asset = [self.assets lastObject];
PHImageManager *manager = [PHImageManager defaultManager];
[manager requestImageForAsset:asset
targetSize:PHImageManagerMaximumSize
contentMode:PHImageContentModeAspectFill
options:nil
resultHandler:^(UIImage * _Nullable result, NSDictionary * _Nullable info) {
[self.imgView setImage:result];
}];
}
有两个方法看着比较陌生
第一个:
+ (PHFetchResult<PHAsset *> *)fetchAssetsWithMediaType:(PHAssetMediaType)mediaType options:(nullable PHFetchOptions *)options;
该方法用作遍历资源库 返回特定类型的资源,mediaType可选四个值,对应关系分别为:PHAssetMediaTypeUnknown–所有 PHAssetMediaTypeImage–图片 PHAssetMediaTypeVideo–视频 PHAssetMediaTypeAudio–音频。
PHFetchResult对应一组资源,PHAsset对应单个资源。
options是搜索条件 如:搜索自己喜爱的照片 并且按照时间上升顺序排列:
PHFetchOptions *option = [[PHFetchOptions alloc] init];
option.predicate = [NSPredicate predicateWithFormat:@"favorite == YES"];
option.sortDescriptors = @[[NSSortDescriptor sortDescriptorWithKey:@"creationDate" ascending:YES]];
PHFetchResult *result = [PHAsset fetchAssetsWithMediaType:PHAssetMediaTypeImage options:option];
第二个:
- (PHImageRequestID)requestImageForAsset:(PHAsset *)asset targetSize:(CGSize)targetSize contentMode:(PHImageContentMode)contentMode options:(nullable PHImageRequestOptions *)options resultHandler:(void (^)(UIImage *__nullable result, NSDictionary *__nullable info))resultHandler;
该方法用作获取特定的PHAsset所对应的图片,返回值为PHImageRequestID类型(实际上是32位int类型)的ID,可以用作取消加载还未加载完成的该请求。- (void)cancelImageRequest:(PHImageRequestID)requestID;
targetSize是你所需要返回的image尺寸,PHImageManagerMaximumSize为原图片尺寸
contentMode是如果原image尺寸与targetSize不相等,用哪种填充的mode,有两个可选值:PHImageContentModeAspectFill–缩放长宽完全填充 PHImageContentModeAspectFit–只缩放长宽中比较大的那个,来等于targetSize。
options:定义请求方式,可为nil,下文会详细介绍,
resultHandler:block 当image完全请求过来后调用。
现在 运行程序就能看到效果啦。
获取图片并缓存
如果需求为要在一个页面,同时显示大量图片,for循环调用上诉方法肯定是不行的,requestImageForAsset会有延时,而达到这一效果,我们可以先缓存下来所有的图片,然后用requestImageForAsset,根据PHImageManage的加载机制,能做到同时显示。
缓存图片方法代码如下:
- (void)loadImages
{
NSLog(@"loadImages");
PHFetchResult *result = [PHAsset fetchAssetsWithMediaType:PHAssetMediaTypeImage options:nil];
NSLog(@"%lu",[result count]);
self.assets = [[NSMutableArray alloc] init];
[result enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
if ([obj isKindOfClass:[PHAsset class]]) {
[self.assets addObject:obj];
}
}];
PHCachingImageManager *cachingImage = [[PHCachingImageManager alloc] init];
[cachingImage startCachingImagesForAssets:self.assets
targetSize:PHImageManagerMaximumSize
contentMode:PHImageContentModeAspectFit
options:nil];
}
经过此缓存后,再用RequestImageForAsset:targetSize:contentMode:options:resultHandler:方法时便能直接从缓存中加载image,从而直接执行resultHandler内的block,特别注意,无延时加载的前提条件是,此时的targetSize 与 缓存时的targetSize必须一样,不然此方法会去图片库重新请求资源加载成image而不会走缓存。
图片请求回显方式详解 PHImageRequestOptions
之前的RequestImageForAsset 和 startCachingImagesForAssets两个方法中 option都设置的是nil。PHImageRequestOptions是用来控制加载什么一级用什么样的方式。下面介绍其主要属性:
* networkAccessAllowed Bool 如果需要,是否允许从iCloud下载图片。
* normalizedCropRect CGRect 指定一个rect来截取原图片,rect以原图片为坐标系。
* synchronous Bool 同步加载。默认是NO。(只有当resultHandler block执行完后 该方法才执行完毕)。
* deliveryMode PHImageRequestOptionsDeliveryMode
* PHImageRequestOptionsDeliveryMode是一个enum类型的,它囊括了非常复杂的操作,有三个值分别对应:
- PHImageRequestOptionsDeliveryModeOpportunistic 当选用此项时,Photos会在你请求时给你提供一个或者多个结果,这就意味着resultHandler block可能会执行一次或多次,例如
Photos会先给你一个低分辨率的图片让你暂时显示,然后加载出高质量的图片后再次给你。如果PHImageManager已经pre-cache了图片,那result handler便只会执行一次。另外,如果synchronous属性为NO,此选项是不起作用的。 - PHImageRequestOptionsDeliveryModeHighQualityFormat 当请求image时 不管请求需要多长时间完成,Photos只会提供高质量的image。另外,当synhronous属性为YES 或者使用requestImageDataForAsset:options:resultHandler: 方法时,这个选项是默认且唯一的。
- PHImageRequestOptionsDeliveryModeFastFormat 当请求image时,高质量的图片或低质量的图片,谁加载的快 便显示谁。另外Photos也能通过检查info字典(resultHandler中的参数)里的PHImageResultIsDegradedKey值来判定传高质量或者低质量的图片。
之前运行时,应该能发现 在图片出现前会有短暂的图片背景色显示。现在我们加上option后再请求图片:
- (void)showImage
{
self.showImgBnt.hidden = YES;
self.imgView.hidden = NO;
PHAsset *asset = [self.assets firstObject];
PHImageManager *manager = [PHImageManager defaultManager];
PHImageRequestOptions *option = [[PHImageRequestOptions alloc] init];
option.deliveryMode = PHImageRequestOptionsDeliveryModeHighQualityFormat;
option.synchronous = YES;
[manager requestImageForAsset:asset
targetSize:PHImageManagerMaximumSize
contentMode:PHImageContentModeAspectFill options:option
resultHandler:^(UIImage * _Nullable result, NSDictionary * _Nullable info) {
[self.imgView setImage:result];
}];
}
此时便不会有那个问题了。
ps:关于PHImageRequestOptionsDeliveryMode enum中值的意思我是翻译的apple官方文档,其中有一个逻辑错误就是,按照官方文档逻辑来看,在任何情况下PHImageRequestOptionsDeliveryModeOpportunistic属性都不会起作用。对此问题我也google过,然后发现mattt大大的文章中也是直接搬过来的,在Twitter上问了他,问题确实存在,以下是我与他的私信过程: