寫在最開始:
由于本項(xiàng)目中對UI還原度要求較高翅娶,交互要求完全還原文留,用別人封裝的改起來總歸是有些別扭;為了后續(xù)方便自己實(shí)現(xiàn)定制UI交互等竭沫,決定自己從系統(tǒng)API開始封裝一套相冊資源選擇器燥翅。
而AL用起來則到處報被棄用的??,想逼死我這個強(qiáng)迫癥啊~
然后就選擇了PH蜕提,總的來說和AL比較類似森书,但是很多東西實(shí)現(xiàn)起來卻是缺這少那的。雖說磨了有段時間贯溅,但目前用起來效果&性能尚可拄氯;下邊分享點(diǎn)小坑和核心代碼。
-
一它浅、簡單實(shí)現(xiàn)拉取所有相冊資源(照片視頻等),并保持創(chuàng)建時間排序
-
二镣煮、篩選大小姐霍,剔除不符合規(guī)則視頻
-
三、gif區(qū)分典唇、處理以及一些猜測(希望有朋友能幫忙驗(yàn)證最后的猜測)
-
四镊折、網(wǎng)絡(luò)GIF圖存儲到相冊
一、 其實(shí)一開始就遇到了個問題介衔,不能同時獲取照片和視頻恨胚。。炎咖。
最后為分別拉出加到同一個數(shù)組之后赃泡,進(jìn)行按時間排序??。
NSMutableArray<PHAsset *> *assets = [NSMutableArray array];
PHFetchOptions *option = [[PHFetchOptions alloc] init];
//ascending 為YES時乘盼,按照照片的創(chuàng)建時間升序排列;為NO時升熊,則降序排列
option.sortDescriptors = @[[NSSortDescriptor sortDescriptorWithKey:@"creationDate" ascending:YES]];
PHFetchResult *result = [PHAsset fetchAssetsWithMediaType:PHAssetMediaTypeImage options:option];
WS(ws);
[result enumerateObjectsWithOptions:NSEnumerationReverse usingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
PHAsset *asset = (PHAsset *)obj;
[ws.allAssetItemModelArr addObject:[YTMediaSelectorItemModel initWithAssetType:1 itemAsset:asset withAssetLocalIdentifier:asset.localIdentifier]];
[assets addObject:asset];
}];
PHFetchOptions *option = [[PHFetchOptions alloc] init];
option.sortDescriptors = @[[NSSortDescriptor sortDescriptorWithKey:@"creationDate" ascending:YES]];
PHFetchResult *result = [PHAsset fetchAssetsWithMediaType:PHAssetMediaTypeVideo options:option];
NSLog(@"拉取到 %ld 個視頻資源\n", result.count);
WS(ws);
[result enumerateObjectsWithOptions:NSEnumerationReverse usingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
PHAsset *asset = (PHAsset *)obj;
[ws.allAssetItemModelArr addObject:[YTMediaSelectorItemModel initWithAssetType:2 itemAsset:asset withAssetLocalIdentifier:asset.localIdentifier]];
}];
- (NSArray *)comparaAssetsModelArr:(NSMutableArray <YTMediaSelectorItemModel *>*)assetModels{
NSComparator cmptr = ^(YTMediaSelectorItemModel *obj1, YTMediaSelectorItemModel *obj2){
if ([obj1.itemAsset.creationDate timeIntervalSince1970] < [obj2.itemAsset.creationDate timeIntervalSince1970]) {
return (NSComparisonResult)NSOrderedDescending;
}
if ([obj1.itemAsset.creationDate timeIntervalSince1970] > [obj2.itemAsset.creationDate timeIntervalSince1970]) {
return (NSComparisonResult)NSOrderedAscending;
}
return (NSComparisonResult)NSOrderedSame;
};
self.allAssetItemModelArr = [[assetModels sortedArrayUsingComparator:cmptr] mutableCopy];
return self.allAssetItemModelArr;
}
YTMediaSelectorItemModel為自己包裝的對象,方便本地做標(biāo)記和取用绸栅。
@interface YTMediaSelectorItemModel : NSObject
/// 1 image 2 video 3 gif
@property (nonatomic, assign) NSInteger assetType;
@property (nonatomic, strong) PHAsset *itemAsset;
@property (nonatomic, copy) NSString *astLocalIdentifier;
@property (nonatomic, strong) UIImage *thumbnailImg;
/** 是否被用戶勾選 */
@property (nonatomic, assign) BOOL isSelected;
/** 當(dāng)前元素 被選中之后的標(biāo)號 */
@property (nonatomic, copy) NSString *currentItemSelectedFlage;
@property (nonatomic, assign) long long videoTimeLength;
+ (YTMediaSelectorItemModel *)initWithAssetType:(NSInteger)assetType itemAsset:(PHAsset *)asset withAssetLocalIdentifier:(NSString *)localIdentifier;
@end
二级野、 這么搞完一套,就取出了所有的資源粹胯,但是若想在構(gòu)造YTMediaSelectorItemModel時再做些篩選的話蓖柔,就會碰到各種問題了??辰企。
- 比如想篩選視頻大小,限制一個范圍的時候
此時我們肯定會去看下Asset里有無fileSize之類的屬性况鸣,我看完傷心了蟆豫,沒有。懒闷。十减。
PHAsset頭文件
也就是說無法直接取其屬性進(jìn)行計算比對了,多番查找發(fā)現(xiàn)有個PHAssetResource類愤估,ta有fileSize的私有屬性:
/*
PHAssetResource:
type:
uti:
filename:
asset:
locallyAvailable:
fileURL:
width:
height:
fileSize: 1924730
analysisType: never-download
cplResourceType: Original
isCurrent: YES
isInCloud: NO
*/
繼續(xù)查找怎么獲取這個asset的Resource有如此一行代碼:
[[PHAssetResource assetResourcesForAsset:asset] firstObject]
此時我們 valueForKey一下就拿到了想要的數(shù)據(jù)帮辟,暫時實(shí)現(xiàn)了既定需求。
如此就結(jié)束了嗎玩焰?由驹??沒這么簡單昔园。蔓榄。。
打印log發(fā)現(xiàn)默刚,每執(zhí)行100次左右的assetResourcesForAsset:就會花大概一秒的時間I!!荤西!??嘗試有無別的方式獲取fileSize無果澜搅,我妥協(xié)了、向現(xiàn)實(shí)低了頭邪锌,我做了當(dāng)需要篩選fileSize的時候進(jìn)行分批回調(diào)勉躺。。觅丰。
__block int count = 0;
[result enumerateObjectsWithOptions:NSEnumerationReverse usingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
count ++;
if (count > 0 && count % 50 == 0) {
NSLog(@"在篩選fileSize時饵溅,分批回調(diào),先回調(diào)第%d批\n",count / 50);
if (ws.ytSelectorTakedAllMediaBlock) {
ws.ytSelectorTakedAllMediaBlock(ws.allAssetItemModelArr);
}
}
}];
如此妇萄,篩選fileSize時也能有類似秒開秒顯的感覺了蜕企,勉強(qiáng)算是實(shí)現(xiàn)需求吧~~~
以上是19年寫的代碼,感覺沒啥東西好說嚣伐;而今(2021.03.17)需要對GIF做些處理糖赔,查找GIF相關(guān)知識點(diǎn)時發(fā)現(xiàn)都是東拉西扯,基本沒有關(guān)于PHAsset拿到之后構(gòu)造顯示數(shù)據(jù)源時就做好標(biāo)記進(jìn)行區(qū)分的文章轩端,嘗試自己搞搞~~~并分享下吧放典,希望能幫助到一些后來者.
三、 下面開始正經(jīng)的說下怎么通過PHAsset區(qū)分GIF、livePhoto等
先說livePhoto奋构、HDR壳影、截圖之類,有對應(yīng)的mediaSubtypes字段返回一個枚舉值:
typedef NS_OPTIONS(NSUInteger, PHAssetMediaSubtype) {
PHAssetMediaSubtypeNone = 0,
// Photo subtypes
PHAssetMediaSubtypePhotoPanorama = (1UL << 0),
PHAssetMediaSubtypePhotoHDR = (1UL << 1),
PHAssetMediaSubtypePhotoScreenshot API_AVAILABLE(ios(9)) = (1UL << 2),
PHAssetMediaSubtypePhotoLive API_AVAILABLE(ios(9.1)) = (1UL << 3),
PHAssetMediaSubtypePhotoDepthEffect API_AVAILABLE(macos(10.12.2), ios(10.2), tvos(10.1)) = (1UL << 4),
// Video subtypes
PHAssetMediaSubtypeVideoStreamed = (1UL << 16),
PHAssetMediaSubtypeVideoHighFrameRate = (1UL << 17),
PHAssetMediaSubtypeVideoTimelapse = (1UL << 18),
};
UL ? 無符號long類型
1.下面捋捋對應(yīng)關(guān)系:
None = 0,
全景 = (1UL << 0) 1弥臼,
HDR = (1UL << 1) 2,
截圖 = (1UL << 2) 4宴咧,
實(shí)況照片(livePhoto) = (1UL << 3) 8,
景深效果(DepthEffect) = (1UL << 4) 16径缅,
**** = 32掺栅,
gif = 64,
視頻的 1UL << 16 開始纳猪,我不得不猜測可以用這個mediaSubtypes == 64來判斷是否為gif氧卧。當(dāng)然,這個辦法只經(jīng)過我一百來張三個渠道的GIF測試氏堤,可能會有遺漏之類(期待大家的驗(yàn)證沙绝,歡迎驗(yàn)證通過的朋友在評論區(qū)留下一筆)。
- 判斷PHAsset是否GIF第二種:
[[asset valueForKey:@"filename"] hasSuffix:@"GIF"]
判斷后綴是否為GIF / gif鼠锈。也通過了我那一百多張GIF的測試闪檬。
- 取PHAsset私有屬性uniformTypeIdentifier,和第二種類似购笆,但似乎更靠譜一點(diǎn)
[[asset valueForKey:@"uniformTypeIdentifier"] isEqual:@"com.compuserve.gif"]
- 判斷PHAsset是否GIF第四種:
[[PHAssetResource assetResourcesForAsset:asset].firstObject.uniformTypeIdentifier isEqualToString:@"com.compuserve.gif"]
也是我認(rèn)為最正經(jīng)的一種辦法粗悯,但是又面臨一個執(zhí)行耗時的問題。也是100次耗時1秒左右S勺馈N琛!
四種方式:1 2 3都不影響速度行您,4看著最正經(jīng)但是費(fèi)時。剪廉。娃循。最后我選擇了第三種方式??
期待用了第一種方式的朋友和我互通有無問題
四、 關(guān)于存儲GIF動圖到相冊
最常見的把image存入相冊的操作莫過于 UIImageWriteToSavedPhotosAlbum
這個API了吧斗蒋?然鵝捌斧、GIF圖通過SDWebImage的loadImageWithURL獲取到的為image對象,直接writeToSavedPhotosAlbum將保存一張靜態(tài)圖泉沾。怎么辦呢捞蚂?去找了下PHotos里的API,找到了如下代碼:
NSError *err = nil;
[[PHPhotoLibrary sharedPhotoLibrary] performChangesAndWait:^{
PHAssetResourceCreationOptions *options = [[PHAssetResourceCreationOptions alloc] init];
[[PHAssetCreationRequest creationRequestForAsset] addResourceWithType:PHAssetResourceTypePhoto data:imgData options:options];
} error:&error];
以為這個問題到這就解決了跷究,實(shí)際還有個小問題:SDWebImage的loadImageWithURL加載圖片結(jié)束的回調(diào)里并不會返回data姓迅,其值為nil;而PHPhotoLibrary里的API需要data,嘗試調(diào)整SDWebImageOptions字段丁存,發(fā)現(xiàn)并不能實(shí)現(xiàn)直接回調(diào)image對應(yīng)的data肩杈。
*一個好的程序員當(dāng)以解決問題為第一要務(wù),想辦法取SD緩存到本地的data解寝!有了如下一行:
NSData *imgData = [[SDImageCache sharedImageCache] diskImageDataForKey:imageUrl];
此處我的SDWebImage版本為5.0以后版本扩然,之前版本取本地data略有不同,自行解決聋伦。