本文鏈接: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;
return
resultImage;
}
- (
NSInteger
)requestOriginImageWithCompletion:(
void
(^)(UIImage *,
NSDictionary
*))completion withProgressHandler:(PHAssetImageProgressHandler)phProgressHandler {
if
(_usePhotoKit) {
if
(_originImage) {
// 如果已經有緩存的圖片則直接拿緩存的圖片
if
(completion) {
completion(_originImage,
nil
);
}
return
0;
}
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 中
BOOL
downloadFinined = ![[info objectForKey:PHImageCancelledKey] boolValue] && ![info objectForKey:PHImageErrorKey] && ![[info objectForKey:PHImageResultIsDegradedKey] boolValue];
if
(downloadFinined) {
_originImage = result;
}
if
(completion) {
completion(result, info);
}
}];
}
}
else
{
if
(completion) {
completion([
self
originImage],
nil
);
}
return
0;
}
}
|
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;
return
resultImage;
}
- (
NSInteger
)requestThumbnailImageWithSize:(CGSize)size completion:(
void
(^)(UIImage *,
NSDictionary
*))completion {
if
(_usePhotoKit) {
if
(_thumbnailImage) {
if
(completion) {
completion(_thumbnailImage,
nil
);
}
return
0;
}
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 中
BOOL
downloadFinined = ![[info objectForKey:PHImageCancelledKey] boolValue] && ![info objectForKey:PHImageErrorKey] && ![[info objectForKey:PHImageResultIsDegradedKey] boolValue];
if
(downloadFinined) {
_thumbnailImage = result;
}
if
(completion) {
completion(result, info);
}
}];
}
}
else
{
if
(completion) {
completion([
self
thumbnailWithSize:size],
nil
);
}
return
0;
}
}
|
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;
return
resultImage;
}
- (
NSInteger
)requestPreviewImageWithCompletion:(
void
(^)(UIImage *,
NSDictionary
*))completion withProgressHandler:(PHAssetImageProgressHandler)phProgressHandler {
if
(_usePhotoKit) {
if
(_previewImage) {
// 如果已經有緩存的圖片則直接拿緩存的圖片
if
(completion) {
completion(_previewImage,
nil
);
}
return
0;
}
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 中
BOOL
downloadFinined = ![[info objectForKey:PHImageCancelledKey] boolValue] && ![info objectForKey:PHImageErrorKey] && ![[info objectForKey:PHImageResultIsDegradedKey] boolValue];
if
(downloadFinined) {
_previewImage = result;
}
if
(completion) {
completion(result, info);
}
}];
}
}
else
{
if
(completion) {
completion([
self
previewImage],
nil
);
}
return
0;
}
}
|
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 才能獲取
[
self
requestPhAssetInfo];
}
// 從 PhAssetInfo 中獲取 UIImageOrientation 對應的字段
orientation = (UIImageOrientation)[_phAssetInfo[
@"orientation"
] integerValue];
}
else
{
orientation = (UIImageOrientation)[[_alAsset valueForProperty:
@"ALAssetPropertyOrientation"
] integerValue];
}
return
orientation;
}
|
系列文章:
iOS 開發之照片框架詳解
iOS 開發之照片框架詳解之二 —— PhotoKit 詳解(上)
iOS 開發之照片框架詳解之二 —— PhotoKit 詳解(下)
參考資料:
objc中國 - 照片框架
Example app using Photos framework
AssetsLibrary Framework Reference