iOS 开发之照片框架详解之二 —— PhotoKit 详解(下)

摘要:
三.常用方法的封装虽然PhotoKit的功能强大很多,但基于兼容iOS8.0以下版本的考虑,暂时可能仍无法抛弃ALAssetLibrary,这时候一个比较好的方案是基于ALAssetLibrary和PhotoKit封装出一系列模拟系统Asset类的自定义类,然后在其中封装好兼容ALAssetLibrary和PhotoKit的方法。*@paramphProgressHandler处理请求进度的handler,不在主线程上执行,在block中修改UI时注意需要手工放到主线程处理。**@wraningiOS8.0以下中并没有异步请求预览图的接口,因此实际上为同步请求,这时block中的第二个参数返回的为nil。

本文链接:http://kayosite.com/ios-development-and-detail-of-photo-framework-part-three.html

这里接着前文《iOS 开发之照片框架详解之二 —— PhotoKit 详解(上)》,主要是干货环节,列举了如何基于 PhotoKit 与 AlAssetLibrary 封装出通用的方法。

三. 常用方法的封装

虽然 PhotoKit 的功能强大很多,但基于兼容 iOS 8.0 以下版本的考虑,暂时可能仍无法抛弃 ALAssetLibrary,这时候一个比较好的方案是基于 ALAssetLibrary 和 PhotoKit 封装出一系列模拟系统 Asset 类的自定义类,然后在其中封装好兼容 ALAssetLibrary 和 PhotoKit 的方法。

这里列举了四种常用的封装好的方法:原图,缩略图,预览图,方向,下面直接上代码,代码中有相关注释解释其中的要点。其中下面的代码中常常出现的[[QMUIAssetsManager sharedInstance] phCachingImageManager] 是 QMUI 框架中封装的类以及单例方法,表示产生一个PHCachingImageManager 的单例,这样做的好处是PHCachingImageManager 需要占用较多的资源,因此使用单例可以避免无谓的资源消耗,另外请求图像等方法需要基于用一个PHCachingImageManager 实例才能进行进度续传,管理请求等操作。

1. 原图

由于原图的尺寸通常会比较大,因此建议使用异步拉取,但这里仍同时列举同步拉取的方法。这里需要留意如前文中所述,ALAssetRepresentation 中获取原图的接口fullResolutionImage 所得到的图像并没有带上系统相册“编辑”(选中,滤镜等)的效果,需要额外获取这些效果并手工叠加到图像上。

.h 文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/// Asset 的原图(包含系统相册“编辑”功能处理后的效果)
- (UIImage *)originImage;
/**
* 异步请求 Asset 的原图,包含了系统照片“编辑”功能处理后的效果(剪裁,旋转和滤镜等),可能会有网络请求
*
* @param completion 完成请求后调用的 block,参数中包含了请求的原图以及图片信息,在 iOS 8.0 或以上版本中,
* 这个 block 会被多次调用,其中第一次调用获取到的尺寸很小的低清图,然后不断调用,直接获取到高清图,
* 获取到高清图后 QMUIAsset 会缓存起这张高清图,这时 block 中的第二个参数(图片信息)返回的为 nil。
* @param phProgressHandler 处理请求进度的 handler,不在主线程上执行,在 block 中修改 UI 时注意需要手工放到主线程处理。
*
* @wraning iOS 8.0 以下中并没有异步请求预览图的接口,因此实际上为同步请求,这时 block 中的第二个参数(图片信息)返回的为 nil。
*
* @return 返回请求图片的请求 id
*/
- (NSInteger)requestOriginImageWithCompletion:(void(^)(UIImage *, NSDictionary*))completion withProgressHandler:(PHAssetImageProgressHandler)phProgressHandler;

.m 文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
- (UIImage *)originImage {
if(_originImage) {
return_originImage;
}
__block UIImage *resultImage;
if(_usePhotoKit) {
PHImageRequestOptions *phImageRequestOptions = [[PHImageRequestOptions alloc] init];
phImageRequestOptions.synchronous = YES;
[[[QMUIAssetsManager sharedInstance] phCachingImageManager] requestImageForAsset:_phAsset
targetSize:PHImageManagerMaximumSize
contentMode:PHImageContentModeDefault
options:phImageRequestOptions
resultHandler:^(UIImage *result, NSDictionary*info) {
resultImage = result;
}];
} else{
CGImageRef fullResolutionImageRef = [_alAssetRepresentation fullResolutionImage];
// 通过 fullResolutionImage 获取到的的高清图实际上并不带上在照片应用中使用“编辑”处理的效果,需要额外在 AlAssetRepresentation 中获取这些信息
NSString*adjustment = [[_alAssetRepresentation metadata] objectForKey:@"AdjustmentXMP"];
if(adjustment) {
// 如果有在照片应用中使用“编辑”效果,则需要获取这些编辑后的滤镜,手工叠加到原图中
NSData*xmpData = [adjustment dataUsingEncoding:NSUTF8StringEncoding];
CIImage *tempImage = [CIImage imageWithCGImage:fullResolutionImageRef];
NSError*error;
NSArray*filterArray = [CIFilter filterArrayFromSerializedXMP:xmpData
inputImageExtent:tempImage.extent
error:&error];
CIContext *context = [CIContext contextWithOptions:nil];
if(filterArray && !error) {
for(CIFilter *filter in filterArray) {
[filter setValue:tempImage forKey:kCIInputImageKey];
tempImage = [filter outputImage];
}
fullResolutionImageRef = [context createCGImage:tempImage fromRect:[tempImage extent]];
}
}
// 生成最终返回的 UIImage,同时把图片的 orientation 也补充上去
resultImage = [UIImage imageWithCGImage:fullResolutionImageRef scale:[_alAssetRepresentation scale] orientation:(UIImageOrientation)[_alAssetRepresentation orientation]];
}
_originImage = resultImage;
returnresultImage;
}
- (NSInteger)requestOriginImageWithCompletion:(void(^)(UIImage *, NSDictionary*))completion withProgressHandler:(PHAssetImageProgressHandler)phProgressHandler {
if(_usePhotoKit) {
if(_originImage) {
// 如果已经有缓存的图片则直接拿缓存的图片
if(completion) {
completion(_originImage, nil);
}
return0;
} else{
PHImageRequestOptions *imageRequestOptions = [[PHImageRequestOptions alloc] init];
imageRequestOptions.networkAccessAllowed = YES; // 允许访问网络
imageRequestOptions.progressHandler = phProgressHandler;
return[[[QMUIAssetsManager sharedInstance] phCachingImageManager] requestImageForAsset:_phAsset targetSize:PHImageManagerMaximumSize contentMode:PHImageContentModeDefault options:imageRequestOptions resultHandler:^(UIImage *result, NSDictionary*info) {
// 排除取消,错误,低清图三种情况,即已经获取到了高清图时,把这张高清图缓存到 _originImage 中
BOOLdownloadFinined = ![[info objectForKey:PHImageCancelledKey] boolValue] && ![info objectForKey:PHImageErrorKey] && ![[info objectForKey:PHImageResultIsDegradedKey] boolValue];
if(downloadFinined) {
_originImage = result;
}
if(completion) {
completion(result, info);
}
}];
}
} else{
if(completion) {
completion([selforiginImage], nil);
}
return0;
}
}

2.缩略图

相对于在拉取原图时 ALAssetLibrary 的部分需要手工叠加系统相册的“编辑”效果,拉取缩略图则简单一些,因为系统接口拉取到的缩略图已经带上“编辑”的效果了。

.h 文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/**
* Asset 的缩略图
*
* @param size 指定返回的缩略图的大小,仅在 iOS 8.0 及以上的版本有效,其他版本则调用 ALAsset 的接口由系统返回一个合适当前平台的图片
*
* @return Asset 的缩略图
*/
- (UIImage *)thumbnailWithSize:(CGSize)size;
/**
* 异步请求 Asset 的缩略图,不会产生网络请求
*
* @param size 指定返回的缩略图的大小,仅在 iOS 8.0 及以上的版本有效,其他版本则调用 ALAsset 的接口由系统返回一个合适当前平台的图片
* @param completion 完成请求后调用的 block,参数中包含了请求的缩略图以及图片信息,在 iOS 8.0 或以上版本中,这个 block 会被多次调用,
* 其中第一次调用获取到的尺寸很小的低清图,然后不断调用,直接获取到高清图,获取到高清图后 QMUIAsset 会缓存起这张高清图,
* 这时 block 中的第二个参数(图片信息)返回的为 nil。
*
* @return 返回请求图片的请求 id
*/
- (NSInteger)requestThumbnailImageWithSize:(CGSize)size completion:(void(^)(UIImage *, NSDictionary*))completion;

.m 文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
- (UIImage *)thumbnailWithSize:(CGSize)size {
if(_thumbnailImage) {
return_thumbnailImage;
}
__block UIImage *resultImage;
if(_usePhotoKit) {
PHImageRequestOptions *phImageRequestOptions = [[PHImageRequestOptions alloc] init];
phImageRequestOptions.resizeMode = PHImageRequestOptionsResizeModeExact;
// 在 PHImageManager 中,targetSize 等 size 都是使用 px 作为单位,因此需要对targetSize 中对传入的 Size 进行处理,宽高各自乘以 ScreenScale,从而得到正确的图片
[[[QMUIAssetsManager sharedInstance] phCachingImageManager] requestImageForAsset:_phAsset
targetSize:CGSizeMake(size.width * ScreenScale, size.height * ScreenScale)
contentMode:PHImageContentModeAspectFill options:phImageRequestOptions
resultHandler:^(UIImage *result, NSDictionary*info) {
resultImage = result;
}];
} else{
CGImageRef thumbnailImageRef = [_alAsset thumbnail];
if(thumbnailImageRef) {
resultImage = [UIImage imageWithCGImage:thumbnailImageRef];
}
}
_thumbnailImage = resultImage;
returnresultImage;
}
- (NSInteger)requestThumbnailImageWithSize:(CGSize)size completion:(void(^)(UIImage *, NSDictionary*))completion {
if(_usePhotoKit) {
if(_thumbnailImage) {
if(completion) {
completion(_thumbnailImage, nil);
}
return0;
} else{
PHImageRequestOptions *imageRequestOptions = [[PHImageRequestOptions alloc] init];
imageRequestOptions.resizeMode = PHImageRequestOptionsResizeModeExact;
// 在 PHImageManager 中,targetSize 等 size 都是使用 px 作为单位,因此需要对targetSize 中对传入的 Size 进行处理,宽高各自乘以 ScreenScale,从而得到正确的图片
return[[[QMUIAssetsManager sharedInstance] phCachingImageManager] requestImageForAsset:_phAsset targetSize:CGSizeMake(size.width * ScreenScale, size.height * ScreenScale) contentMode:PHImageContentModeAspectFill options:imageRequestOptions resultHandler:^(UIImage *result, NSDictionary*info) {
// 排除取消,错误,低清图三种情况,即已经获取到了高清图时,把这张高清图缓存到 _thumbnailImage 中
BOOLdownloadFinined = ![[info objectForKey:PHImageCancelledKey] boolValue] && ![info objectForKey:PHImageErrorKey] && ![[info objectForKey:PHImageResultIsDegradedKey] boolValue];
if(downloadFinined) {
_thumbnailImage = result;
}
if(completion) {
completion(result, info);
}
}];
}
} else{
if(completion) {
completion([selfthumbnailWithSize:size], nil);
}
return0;
}
}

3. 预览图

与上面的方法类似,不再展开说明。

.h 文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/**
* Asset 的预览图
*
* @warning 仿照 ALAssetsLibrary 的做法输出与当前设备屏幕大小相同尺寸的图片,如果图片原图小于当前设备屏幕的尺寸,则只输出原图大小的图片
* @return Asset 的全屏图
*/
- (UIImage *)previewImage;
/**
* 异步请求 Asset 的预览图,可能会有网络请求
*
* @param completion 完成请求后调用的 block,参数中包含了请求的预览图以及图片信息,在 iOS 8.0 或以上版本中,
* 这个 block 会被多次调用,其中第一次调用获取到的尺寸很小的低清图,然后不断调用,直接获取到高清图,
* 获取到高清图后 QMUIAsset 会缓存起这张高清图,这时 block 中的第二个参数(图片信息)返回的为 nil。
* @param phProgressHandler 处理请求进度的 handler,不在主线程上执行,在 block 中修改 UI 时注意需要手工放到主线程处理。
*
* @wraning iOS 8.0 以下中并没有异步请求预览图的接口,因此实际上为同步请求,这时 block 中的第二个参数(图片信息)返回的为 nil。
*
* @return 返回请求图片的请求 id
*/
- (NSInteger)requestPreviewImageWithCompletion:(void(^)(UIImage *, NSDictionary*))completion withProgressHandler:(PHAssetImageProgressHandler)phProgressHandler;

.m 文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
- (UIImage *)previewImage {
if(_previewImage) {
return_previewImage;
}
__block UIImage *resultImage;
if(_usePhotoKit) {
PHImageRequestOptions *imageRequestOptions = [[PHImageRequestOptions alloc] init];
imageRequestOptions.synchronous = YES;
[[[QMUIAssetsManager sharedInstance] phCachingImageManager] requestImageForAsset:_phAsset
targetSize:CGSizeMake(SCREEN_WIDTH, SCREEN_HEIGHT)
contentMode:PHImageContentModeAspectFill
options:imageRequestOptions
resultHandler:^(UIImage *result, NSDictionary*info) {
resultImage = result;
}];
} else{
CGImageRef fullScreenImageRef = [_alAssetRepresentation fullScreenImage];
resultImage = [UIImage imageWithCGImage:fullScreenImageRef];
}
_previewImage = resultImage;
returnresultImage;
}
- (NSInteger)requestPreviewImageWithCompletion:(void(^)(UIImage *, NSDictionary*))completion withProgressHandler:(PHAssetImageProgressHandler)phProgressHandler {
if(_usePhotoKit) {
if(_previewImage) {
// 如果已经有缓存的图片则直接拿缓存的图片
if(completion) {
completion(_previewImage, nil);
}
return0;
} else{
PHImageRequestOptions *imageRequestOptions = [[PHImageRequestOptions alloc] init];
imageRequestOptions.networkAccessAllowed = YES; // 允许访问网络
imageRequestOptions.progressHandler = phProgressHandler;
return[[[QMUIAssetsManager sharedInstance] phCachingImageManager] requestImageForAsset:_phAsset targetSize:CGSizeMake(SCREEN_WIDTH, SCREEN_HEIGHT) contentMode:PHImageContentModeAspectFill options:imageRequestOptions resultHandler:^(UIImage *result, NSDictionary*info) {
// 排除取消,错误,低清图三种情况,即已经获取到了高清图时,把这张高清图缓存到 _previewImage 中
BOOLdownloadFinined = ![[info objectForKey:PHImageCancelledKey] boolValue] && ![info objectForKey:PHImageErrorKey] && ![[info objectForKey:PHImageResultIsDegradedKey] boolValue];
if(downloadFinined) {
_previewImage = result;
}
if(completion) {
completion(result, info);
}
}];
}
} else{
if(completion) {
completion([selfpreviewImage], nil);
}
return0;
}
}

4. 方向(imageOrientation)

比较奇怪的是,无论在 PhotoKit 或者是 ALAssetLibrary 中,要想获取到准确的图像方向,只能通过某些 key 检索所得。

.h 文件

1
- (UIImageOrientation)imageOrientation;

.m 文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
- (UIImageOrientation)imageOrientation {
UIImageOrientation orientation;
if(_usePhotoKit) {
if(!_phAssetInfo) {
// PHAsset 的 UIImageOrientation 需要调用过 requestImageDataForAsset 才能获取
[selfrequestPhAssetInfo];
}
// 从 PhAssetInfo 中获取 UIImageOrientation 对应的字段
orientation = (UIImageOrientation)[_phAssetInfo[@"orientation"] integerValue];
} else{
orientation = (UIImageOrientation)[[_alAsset valueForProperty:@"ALAssetPropertyOrientation"] integerValue];
}
returnorientation;
}

系列文章:
iOS 开发之照片框架详解
iOS 开发之照片框架详解之二 —— PhotoKit 详解(上)
iOS 开发之照片框架详解之二 —— PhotoKit 详解(下)

参考资料:
objc中国 - 照片框架
Example app using Photos framework
AssetsLibrary Framework Reference

免责声明:文章转载自《iOS 开发之照片框架详解之二 —— PhotoKit 详解(下)》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇Mac下的UI自动化测试 (二)消息中间件系列三:使用RabbitMq原生Java客户端进行消息通信(消费者(接收方)自动确认模式、消费者(接收方)自行确认模式、生产者(发送方)确认模式)下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

ios开发网络学习二:URL转码以及字典转模型框架MJExtension的使用

一:url转码,当url中涉及到中文的时候,要考虑转码,用UTF8对中文的url进行转码 #import "ViewController.h" @interfaceViewController () @end @implementationViewController #pragma mark ---------------------- #pra...

oracle 嵌套表 老猫

一、嵌套表的定义:     嵌套表是表中之表。一个嵌套表是某些行的集合,它在主表中表示为其中的一列。对主表中的每一条记录,嵌套表可以包含多个行。在某种意义上,它是在一个表中存储一对多关系的一种方法。考查一个包含部门信息的表,在任何时间内每个部门会有很多项目正在实施。在一个严格的关系模型中,将需要建立两个独立的表department和project.   ...

ROS学习笔记(一):自己动手写一个ROS程序

创建一个工作区(workspace) 工作区可以作为一个独立的项目进行编译,存放ROS程序的源文件、编译文件和执行文件。建立工作区的方法如下:   [plain] view plain copy  $ mkdir -p ~/catkin_ws/src   $ cd ~/catkin_ws/src   $ catkin_init_workspace ...

TP5.1 爬虫

环境 PHP >= 7.1 !!!PHP >= 7.1 !!!PHP >= 7.1 !!! 安装 composer require jaeger/querylist 后端 //爬虫 public functioncrawler() { if(request()->post()){...

Sqlserver存储过程及其创建(转)

存储过程可以使得对数据库的管理、以及显示关于数据库及其用户信息的工作容易得多。存储过程是 SQL 语句和可选控制流语句的预编译集合,以一个名称存储并作为一个单元处理。存储过程存储在数据库内,可由应用程序通过一个调用执行,而且允许用户声明变量、有条件执行以及其它强大的编程功能。 存储过程可包含程序流、逻辑以及对数据库的查询。它们可以接受参数、输出参数、返回单...

spark on yarn提交任务时报ClosedChannelException解决方案

spark2.1出来了,想玩玩就搭了个原生的apache集群,但在standalone模式下没有任何问题,基于apache hadoop 2.7.3使用spark on yarn一直报这个错。(Java 8) 报错日志如下: Warning: Master yarn-client is deprecated since 2.0. Please use ma...