關於iOS選取相冊中iCloud雲上圖片和視頻
工作原因,需要處理接入一個視頻模塊,在視頻選擇的時候遇到了一個不太容易發現的bug,產生的原因是由於手機內存小,而用戶又打開了相冊同步iCloud,

加載中的圖片

在這時,如果本地可用內存過小,會導致
將本地相冊中的圖片或視頻刪除只留縮略圖,如果App調用的時候想要選取這種圖片就需要從iCloud雲中進行下載,
才能獲取原圖或原視頻。
下面po下解決方案:
如果你之前處理過相冊問題,那么對如下的代碼肯定不陌生,就是很普通的兩個系統級別的請求回調,獲取對應的圖片,視頻。
// get Image [[PHImageManager defaultManager] requestImageDataForAsset:asset options:nil resultHandler:^(NSData * _Nullable imageData, NSString * _Nullable dataUTI, UIImageOrientation orientation, NSDictionary * _Nullable info) { }]; // get Video [[PHImageManager defaultManager] requestPlayerItemForVideo:asset options:nil resultHandler:^(AVPlayerItem * _Nullable playerItem, NSDictionary * _Nullable info) { if (completion) completion(playerItem,info); }];
但是往往之前沒有注意到第二個輸入 options
是用來干嘛的,
其實解決方案就來自於這個 PHImageRequestOptions
,PHVideoRequestOptions
。
這這兩個 options
都有一個共同的參數就是
@property (nonatomic, assign, getter=isNetworkAccessAllowed) BOOL networkAccessAllowed; // if necessary will download the image from iCloud (client can monitor or cancel using progressHandler). Defaults to NO (see start/stopCachingImagesForAssets)
系統的解釋也很詳細,如果賦值 YES
,那么允許從 iCloud
中獲取圖片和視頻,默認是 NO
。
雖然這個問題解決不是很難,但是往往容易被忽略,所以記錄一下。
這里非常感謝@半遲塵大大的TZImagePickerController的源碼,這個是一個非常靠譜的相冊選擇圖片視頻的庫,並且處於仍在維護中。感興趣的可以鏈接過去看一看源碼,寫的很好。
博主博客@HarwordLiu
下面po一下完整的這個問題的解決代碼:
/// Get Video - (void)getVideoOutputPathWithAsset:(PHAsset *)asset completion:(void (^)(NSString *outputPath))completion { PHVideoRequestOptions* options = [[PHVideoRequestOptions alloc] init]; options.version = PHVideoRequestOptionsVersionOriginal; options.deliveryMode = PHVideoRequestOptionsDeliveryModeAutomatic; options.networkAccessAllowed = YES; [[PHImageManager defaultManager] requestAVAssetForVideo:asset options:options resultHandler:^(AVAsset* avasset, AVAudioMix* audioMix, NSDictionary* info){ // NSLog(@"Info:\n%@",info); AVURLAsset *videoAsset = (AVURLAsset*)avasset; // NSLog(@"AVAsset URL: %@",myAsset.URL); [self startExportVideoWithVideoAsset:videoAsset completion:completion]; }]; } /// Get Image - (PHImageRequestID)getPhotoWithAsset:(PHAsset *)asset photoWidth:(CGFloat)photoWidth completion:(void (^)(UIImage *, NSDictionary *, BOOL isDegraded))completion { PHAsset *phAsset = (PHAsset *)asset; CGFloat aspectRatio = phAsset.pixelWidth / (CGFloat)phAsset.pixelHeight; CGFloat pixelWidth = photoWidth * 2.0; CGFloat pixelHeight = pixelWidth / aspectRatio; CGSize imageSize = CGSizeMake(pixelWidth, pixelHeight); // 修復獲取圖片時出現的瞬間內存過高問題 PHImageRequestOptions *option = [[PHImageRequestOptions alloc] init]; option.resizeMode = PHImageRequestOptionsResizeModeFast; PHImageRequestID imageRequestID = [[PHImageManager defaultManager] requestImageForAsset:asset targetSize:imageSize contentMode:PHImageContentModeAspectFill options:option resultHandler:^(UIImage * _Nullable result, NSDictionary * _Nullable info) { BOOL downloadFinined = (![[info objectForKey:PHImageCancelledKey] boolValue] && ![info objectForKey:PHImageErrorKey]); if (downloadFinined && result) { if (completion) completion(result,info,[[info objectForKey:PHImageResultIsDegradedKey] boolValue]); } // Download image from iCloud / 從iCloud下載圖片 if ([info objectForKey:PHImageResultIsInCloudKey] && !result) { PHImageRequestOptions *option = [[PHImageRequestOptions alloc]init]; option.networkAccessAllowed = YES; option.resizeMode = PHImageRequestOptionsResizeModeFast; [[PHImageManager defaultManager] requestImageDataForAsset:asset options:option resultHandler:^(NSData * _Nullable imageData, NSString * _Nullable dataUTI, UIImageOrientation orientation, NSDictionary * _Nullable info) { UIImage *resultImage = [UIImage imageWithData:imageData scale:0.1]; if (completion) completion(resultImage,info,[[info objectForKey:PHImageResultIsDegradedKey] boolValue]); }]; } }]; return imageRequestID; }
判斷是否是iCloud視頻或圖片的方法
https://stackoverflow.com/questions/31966571/check-given-phasset-is-icloud-asset
第一種方法
[cloudAlbums enumerateObjectsUsingBlock:^(PHAssetCollection *collection, NSUInteger idx, BOOL *stop) { PHFetchResult *result = [PHAsset fetchAssetsInAssetCollection:collection options:fetchOptions]; [result enumerateObjectsUsingBlock:^(PHAsset *asset, NSUInteger idx, BOOL *stop) { PHImageRequestOptions *options = [[PHImageRequestOptions alloc] init]; options.resizeMode = PHImageRequestOptionsResizeModeFast; options.synchronous = YES;
__block BOOL isICloudAsset = NO; [[PHImageManager defaultManager] requestImageForAsset:asset targetSize:imageSize contentMode:PHImageContentModeAspectFit options:options resultHandler:^(UIImage *result, NSDictionary *info) { //根據請求會調中的參數重 NSDictionary *info 是否有cloudKey 來判斷是否是 iCloud if ([info objectForKey: PHImageResultIsInCloudKey].boolValue) { isICloudAsset = YES; } }]; }]; }];
第二種方法(其實是同一種)
__block BOOL isPhotoInICloud = NO; PHImageRequestOptions *options = [[PHImageRequestOptions alloc] init]; options.networkAccessAllowed = YES; options.deliveryMode = PHImageRequestOptionsDeliveryModeHighQualityFormat; // 下載iCloud 圖片的進度回調 只要圖片是在icloud中 然后去請求圖片就會走這個回調 如果圖片沒有在iCloud中不回走這個回調 //里面的會調中的參數重 NSDictionary *info 是否有cloudKey 來判斷是否是 iCloud 處理UI放到主線程 options.progressHandler = ^(double progress, NSError *error, BOOL *stop, NSDictionary *info){ isPhotoInICloud = YES;
}); //上面的進度下載完了之后才會掉用resultHandler 回調 [[PHImageManager defaultManager] requestImageForAsset:asset targetSize:PHImageManagerMaximumSize contentMode:PHImageContentModeAspectFit options:options resultHandler:^(UIImage * _Nullable result, NSDictionary * _Nullable info) { if (isPhotoInICloud) { // Photo is in iCloud. } });