iOS 開發之照片框架詳解之二 —— PhotoKit 詳解(下)


本文鏈接: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

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM