关于Photos库的简单应用,筛选、获取、GIF、livePhoto、video、GIF存储到相册等


写在最开始

由于本项目中对UI还原度要求较高,交互要求完全还原,用别人封装的改起来总归是有些别扭;为了后续方便自己实现定制UI交互等,决定自己从系统API开始封装一套相册资源选择器。

而AL用起来则到处报被弃用的⚠️,想逼死我这个强迫症啊~
然后就选择了PH,总的来说和AL比较类似,但是很多东西实现起来却是缺这少那的。虽说磨了有段时间,但目前用起来效果&性能尚可;下边分享点小坑和核心代码。

  • 一、简单实现拉取所有相册资源(照片视频等),并保持创建时间排序
  • 二、筛选大小,剔除不符合规则视频
  • 三、gif区分、处理以及一些猜测(希望有朋友能帮忙验证最后的猜测)
  • 四、网络GIF图存储到相册

一、 其实一开始就遇到了个问题,不能同时获取照片和视频。。。

最后为分别拉出加到同一个数组之后,进行按时间排序🤡。

NSMutableArray<PHAsset *> *assets = [NSMutableArray array];
    PHFetchOptions *option = [[PHFetchOptions alloc] init];
    //ascending 为YES时,按照照片的创建时间升序排列;为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为自己包装的对象,方便本地做标记和取用。

@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;
/** 当前元素 被选中之后的标号 */
@property (nonatomic, copy) NSString *currentItemSelectedFlage;
@property (nonatomic, assign) long long videoTimeLength;

+ (YTMediaSelectorItemModel *)initWithAssetType:(NSInteger)assetType itemAsset:(PHAsset *)asset withAssetLocalIdentifier:(NSString *)localIdentifier;
@end

二、 这么搞完一套,就取出了所有的资源,但是若想在构造YTMediaSelectorItemModel时再做些筛选的话,就会碰到各种问题了😄。


  • 比如想筛选视频大小,限制一个范围的时候
    此时我们肯定会去看下Asset里有无fileSize之类的属性,我看完伤心了,没有。。。
    PHAsset头文件

    也就是说无法直接取其属性进行计算比对了,多番查找发现有个PHAssetResource类,ta有fileSize的私有属性:
/*
         PHAssetResource:
                         type:
                         uti: 
                         filename: 
                         asset: 
                         locallyAvailable: 
                         fileURL: 
                         width: 
                         height: 
                         fileSize: 1924730
                         analysisType: never-download
                         cplResourceType: Original
                         isCurrent: YES
                         isInCloud: NO
         */

继续查找怎么获取这个asset的Resource有如此一行代码:

[[PHAssetResource assetResourcesForAsset:asset] firstObject]

此时我们 valueForKey一下就拿到了想要的数据,暂时实现了既定需求。
如此就结束了吗???没这么简单。。。
打印log发现,每执行100次左右的assetResourcesForAsset:就会花大概一秒的时间!!!😅尝试有无别的方式获取fileSize无果,我妥协了、向现实低了头,我做了当需要筛选fileSize的时候进行分批回调。。。

__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时,分批回调,先回调第%d批\n",count / 50);
            if (ws.ytSelectorTakedAllMediaBlock) {
                ws.ytSelectorTakedAllMediaBlock(ws.allAssetItemModelArr);
            }
        }
}];

如此,筛选fileSize时也能有类似秒开秒显的感觉了,勉强算是实现需求吧~~~


以上是19年写的代码,感觉没啥东西好说;而今(2021.03.17)需要对GIF做些处理,查找GIF相关知识点时发现都是东拉西扯,基本没有关于PHAsset拿到之后构造显示数据源时就做好标记进行区分的文章,尝试自己搞搞~~~并分享下吧,希望能帮助到一些后来者.

三、 下面开始正经的说下怎么通过PHAsset区分GIF、livePhoto等

先说livePhoto、HDR、截图之类,有对应的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.下面捋捋对应关系:

None                                =  0,
全景                                 = (1UL << 0)       1,
HDR                                 = (1UL << 1)        2,
截图                                 = (1UL << 2)        4,
实况照片(livePhoto)                   = (1UL << 3)        8,
景深效果(DepthEffect)                 =  (1UL << 4)      16,
****                                 =                  32,
gif                                   =                 64,

视频的 1UL << 16 开始,我不得不猜测可以用这个mediaSubtypes == 64来判断是否为gif。当然,这个办法只经过我一百来张三个渠道的GIF测试,可能会有遗漏之类(期待大家的验证,欢迎验证通过的朋友在评论区留下一笔)。

  1. 判断PHAsset是否GIF第二种:
[[asset valueForKey:@"filename"] hasSuffix:@"GIF"]

判断后缀是否为GIF / gif。也通过了我那一百多张GIF的测试。

  1. 取PHAsset私有属性uniformTypeIdentifier,和第二种类似,但似乎更靠谱一点
[[asset valueForKey:@"uniformTypeIdentifier"] isEqual:@"com.compuserve.gif"]
  1. 判断PHAsset是否GIF第四种:
[[PHAssetResource assetResourcesForAsset:asset].firstObject.uniformTypeIdentifier isEqualToString:@"com.compuserve.gif"]

也是我认为最正经的一种办法,但是又面临一个执行耗时的问题。也是100次耗时1秒左右!!!

四种方式:1 2 3都不影响速度,4看着最正经但是费时。。。最后我选择了第三种方式😄


期待用了第一种方式的朋友和我互通有无问题

四、 关于存储GIF动图到相册

最常见的把image存入相册的操作莫过于 UIImageWriteToSavedPhotosAlbum
这个API了吧?然鹅、GIF图通过SDWebImage的loadImageWithURL获取到的为image对象,直接writeToSavedPhotosAlbum将保存一张静态图。怎么办呢?去找了下PHotos里的API,找到了如下代码:

NSError *err = nil;
[[PHPhotoLibrary sharedPhotoLibrary] performChangesAndWait:^{
    PHAssetResourceCreationOptions *options = [[PHAssetResourceCreationOptions alloc] init];
    [[PHAssetCreationRequest creationRequestForAsset] addResourceWithType:PHAssetResourceTypePhoto data:imgData options:options];
} error:&error];

以为这个问题到这就解决了,实际还有个小问题:SDWebImage的loadImageWithURL加载图片结束的回调里并不会返回data,其值为nil;而PHPhotoLibrary里的API需要data,尝试调整SDWebImageOptions字段,发现并不能实现直接回调image对应的data。
*一个好的程序员当以解决问题为第一要务,想办法取SD缓存到本地的data!有了如下一行:

  NSData *imgData = [[SDImageCache sharedImageCache] diskImageDataForKey:imageUrl];

此处我的SDWebImage版本为5.0以后版本,之前版本取本地data略有不同,自行解决。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 160,026评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,655评论 1 296
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,726评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,204评论 0 213
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,558评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,731评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,944评论 2 314
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,698评论 0 203
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,438评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,633评论 2 247
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,125评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,444评论 3 255
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,137评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,103评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,888评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,772评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,669评论 2 271

推荐阅读更多精彩内容