首先几乎每个App都用到了自定义相册的展示,系统在iOS8以后提供了photos.framework来供我们与相册交互。既然要自定义相册,最先要做的就是封装获取相册相关的API。现在已经有很多开源的相册项目,为什么我们还要自己造轮子呢,其实大部分的开源项目都可以实现大部分的功能,但确实无法满足所有的需求,所以该踩的坑还是要踩,以下就结合我们项目中遇到的一些问题,分享一些tips。

1.相册权限的判断

  当用户点击相册时,其实有三种情况。情况1,已经授权过了;情况2.之前拒绝授权;情况3,之前没有决定过。针对于情况1,我们直接打开就可以了。针对于情况2,我们可以弹框提示,然后跳转到系统设置里面,引导用户打开。针对于情况3,我们需要弹框,然后再用户做出选择之后,在根据是情况1、还是情况2来进行不同的操作。

+ (void)requestAlbumAuthorization:(void (^)(BOOL isAuthorized))block ifAlert:(BOOL)ifAlert
{PHAuthorizationStatus photoAuthorStatus = [PHPhotoLibrary authorizationStatus];if (photoAuthorStatus == PHAuthorizationStatusAuthorized)block ? block(YES) : nil;else if (photoAuthorStatus == PHAuthorizationStatusDenied){if (ifAlert)[AlertManager showAlertControllerWithType:AlertManagerTypeOpenAlbumFail];block ? block(NO) : nil;}else[PHPhotoLibrary requestAuthorization:^(PHAuthorizationStatus status) {dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{if (status == PHAuthorizationStatusAuthorized)block ? block(YES) : nil;else{if (ifAlert)[AlertManager showAlertControllerWithType:AlertManagerTypeOpenAlbumFail];block ? block(NO) : nil;}});}];
}

2.如何判断相册资源的类型

  首先我们知道每一个相册资源都是PHAsset对象,而系统提供了mediaType这个属性来告诉我们判断该资源是图片、视频还是音频。但是针对于图片来说,又可能是静态图、GIF或者LIVE Photo。虽然又可以通过mediaSubtypes是否等于 PHAssetMediaSubtypePhotoLive 来判断图片是否是LIVE Photo。但如何判断图片是否是GIF通过系统的API就无能为力了。

那么如何判断PHAsset对象是不是GIF呢??

其实我们可以通过PHAsset得到对应的资源对象PHAssetSource,该对象的uniformTypeIdentifier属性(即UTI)来判断是否是GIF(com.compuserve.gif)

- (BOOL)isImageAssetGIF:(PHAsset *)asset
{PHAssetResource * resource = [[PHAssetResource assetResourcesForAsset:asset] firstObject];return [resource.uniformTypeIdentifier isEqualToString:@"com.compuserve.gif"];
}

原理如此,但是获取PHAssetResource太慢,如果放在主线程同步获取类型的话,会造成卡顿。这里可以通过KVC来获取uniformTypeIdentifier。完整的获取类型代码如下:

+ (AssetMediaType)getTypeForAsset:(PHAsset *)asset
{switch (asset.mediaType){case PHAssetMediaTypeVideo:return AssetMediaTypeVideo;case PHAssetMediaTypeImage:{if (asset.mediaSubtypes == PHAssetMediaSubtypePhotoLive)return AssetMediaTypeLivePhoto;if ([[asset valueForKey:@"uniformTypeIdentifier"] isEqualToString:@"com.compuserve.gif"])return AssetMediaTypeGIF;return AssetMediaTypeStaticGraph;}default:return AssetMediaTypeUnknown;}
}

3.获取资源的缩略图

  一般情况下,在某个相册的列表页,会展示大量的图片,而相册中的照片的分辨率几乎都是几千*几千的,而我们在列表页只需要展示宽度为100~200像素的图片即可,那么就需要我们去获取缩略图。获取缩略图的方法如下,具体注意事项可参考代码中的注释。

+ (PHImageRequestID)requestImageForAsset:(PHAsset *)asset size:(CGSize)size resizeMode:(PHImageRequestOptionsResizeMode)resizeMode needsDegrade:(BOOL)needsDegrade completion:(void (^)(UIImage * image, NSDictionary * info))completion
{PHImageRequestOptions * option = [[PHImageRequestOptions alloc] init];option.resizeMode = resizeMode; //  resizeMode:对请求的图像怎样缩放。有三种选择:None,默认加载方式;Fast,尽快地提供接近或稍微大于要求的尺寸;Exact,精准提供要求的尺寸。针对于缩略图,我们使用Fast即可。option.networkAccessAllowed = YES;//处理图片存在iCloud中的情况,将网络请求设置为打开,默认为NO/*info字典提供请求状态信息:PHImageResultIsInCloudKey:图像是否必须从iCloud请求PHImageResultIsDegradedKey:当前UIImage是否是低质量的,这个可以实现给用户先显示一个预览图PHImageResultRequestIDKey和PHImageCancelledKey:请求ID以及请求是否已经被取消PHImageErrorKey:如果没有图像,字典内的错误信息*/PHImageRequestID requestID = [[PHCachingImageManager defaultManager] requestImageForAsset:asset targetSize:size contentMode:PHImageContentModeAspectFit options:option resultHandler:^(UIImage * _Nullable image, NSDictionary * _Nullable info) {BOOL downloadFinined = ![[info objectForKey:PHImageCancelledKey] boolValue] && ![info objectForKey:PHImageErrorKey];if (needsDegrade == NO)downloadFinined = downloadFinined && ![[info objectForKey:PHImageResultIsDegradedKey] boolValue];//  如果不判断PHImageResultIsDegradedKey,即如果该图片在iCloud上时候,会先显示一张模糊的预览图,待加载完毕后会显示高清图//  && ![[info objectForKey:PHImageResultIsDegradedKey] boolValue];if (downloadFinined && completion){//回调在主线程,所以直接返回即可completion(image, info);}}];return requestID;
}

4.获取预览页的资源

  • 获取静态图的预览资源**
      针对获取静态图的预览资源,我们可以通过上一条说的获取缩略图的方法来获取。++为保证快速滑动的流畅性,在快速滑动的时候获取图片宽度最大不要超过500++。
  • 获取动态图的预览资源
      针对于动态图,通过上一条方法获取的图片的方法可以获取成功,但是获取到的是静态图。如果希望图片可以播放,那么就需要获取图片的data,然后再解析data,播放GIF。++这里也有一个坑,如果GIF存在iCloud上,即便我们设置了networkAccessAllowedYES,也可能会存在返回的图片是静态图的情况++。百思不得解,最后也是没有办法的办法,通过PHAssetResourceManagerwriteDataForAssetResource:toFile:options:completionHandler:方法来获取图片的data。
+ (void)requestOriginalImageDataForAsset:(PHAsset *)asset completion:(void (^)(NSData *, NSDictionary *))completion
{if (!completion)return ;PHImageRequestOptions * option = [[PHImageRequestOptions alloc] init];option.networkAccessAllowed = YES;option.resizeMode = PHImageRequestOptionsResizeModeFast;AssetMediaType type = [self getTypeForAsset:asset];[[PHImageManager defaultManager] requestImageDataForAsset:asset options:option resultHandler:^(NSData * _Nullable imageData, NSString * _Nullable dataUTI, UIImageOrientation orientation, NSDictionary * _Nullable info) {BOOL downloadFinined = ![[info objectForKey:PHImageCancelledKey] boolValue] && ![info objectForKey:PHImageErrorKey] && ![[info objectForKey:PHImageResultIsDegradedKey] boolValue];if (downloadFinined){if (type == AssetMediaTypeGif){NSArray <PHAssetResource *> * resources = [PHAssetResource assetResourcesForAsset:asset];PHAssetResource * resource = [resources objectAtIndex:0];if (![resource.uniformTypeIdentifier isEqualToString:dataUTI]){//  如果请求的是GIF,返回的不是GIF,那么执行导出的操作[self requestDataForAssetSource:resource completion:completion];return ;}}completion(imageData, info);}}];
}+ (void)requestDataForAssetSource:(PHAssetResource *)resource completion:(void (^)(NSData *, NSDictionary *))completion
{if (completion == nil)return ;PHAssetResourceRequestOptions * option = [[PHAssetResourceRequestOptions alloc] init];option.networkAccessAllowed = YES;NSString * exportFilePath = [self getExportFilePathForType:AssetMediaTypeGif];[[PHAssetResourceManager defaultManager] writeDataForAssetResource:resource toFile:[NSURL fileURLWithPath:exportFilePath] options:option completionHandler:^(NSError * _Nullable error) {NSData * data = [NSData dataWithContentsOfFile:exportFilePath];if (error)data = nil;if ([NSThread isMainThread])completion(data, nil);elsedispatch_async(dispatch_get_main_queue(), ^{completion(data, nil);});//  得到data之后删除文件...[[NSFileManager defaultManager] removeItemAtPath:exportFilePath error:nil];}];
}
  • 获取LIVE Photo的预览资源
      获取LIVE Photo资源,感觉系统还是很友好,得到PHLivePhoto对象之后,加载到对应的view上,3D Touch就能播放了。==其实这里也有坑!!!== 我来描述一下现象,通过iOS 11系统的iPhone X拍摄LIVE Photo,通过iCloud导入到iOS 10系统的iPhone 6s上,获取PHLivePhoto对象的时候竟然崩溃了…原因貌似是系统在调用stringByAppendingPathExtension:方法时参数传递了nil,好吧,通过Method swizzle来解决。
/**为了解决LivePhoto获取时,stringByAppendingPathExtension参数为nil的情况,猜测原因可能是iOS11 iPhone X 拍的LivePhoto在iOS 10.3.3 iPhone 6s Plus上产生系统兼容的问题*/
@implementation NSString (LivePhoto)+ (void)load
{[NSString swizzleInstanceMethod:@selector(stringByAppendingPathExtension:) withMethod:@selector(nil_stringByAppendingPathExtension:)];
}- (NSString *)nil_stringByAppendingPathExtension:(NSString *)str
{if (str == nil || [str isEqual:[NSNull null]])return nil;return [self nil_stringByAppendingPathExtension:str];
}@end@implementation AlbumTool+ (void)requestLivePhotoForAsset:(PHAsset *)asset completion:(void (^)(PHLivePhoto *, NSDictionary *))completion
{if (!completion)return ;PHLivePhotoRequestOptions * option = [[PHLivePhotoRequestOptions alloc] init];if ([option respondsToSelector:@selector(setVersion:)])option.version = PHImageRequestOptionsVersionCurrent;//  这样保证很快,在目前480在我们项目上已经够用,并且下方resultHandler不会调用多次,如果有其他需求可以修改参数option.deliveryMode = PHImageRequestOptionsDeliveryModeFastFormat;option.networkAccessAllowed = YES;[[PHCachingImageManager defaultManager] requestLivePhotoForAsset:asset targetSize:PHImageManagerMaximumSize contentMode:PHImageContentModeAspectFit options:option resultHandler:^(PHLivePhoto * _Nullable livePhoto, NSDictionary * _Nullable info) {//  该回调貌似不一定在主线程if ([NSThread isMainThread])completion(livePhoto, info);elsedispatch_async(dispatch_get_main_queue(), ^{completion(livePhoto, info);});}];
}@end

    终于搞定了,实属不易,宝宝心里苦…

  • 获取视频的预览资源
      视频资源直接获取AVAsset即可,即便视频在iCloud上,没有关系,边播边缓冲,没什么好说的(终于遇到一个不是很坑的API了…)。而针对于requestPlayerItemForVideo这个方法,如果视频在iCloud上,那么返回的AVPlayItem为nil。
+ (void)requestAVAssetForAsset:(PHAsset *)asset completion:(void (^)(AVAsset * asset, AVAudioMix * audioMix, NSDictionary * info))completion
{if (!completion)return ;PHVideoRequestOptions * option = [[PHVideoRequestOptions alloc] init];option.version = PHVideoRequestOptionsVersionOriginal;option.deliveryMode = PHVideoRequestOptionsDeliveryModeAutomatic;option.networkAccessAllowed = YES;[[PHImageManager defaultManager] requestAVAssetForVideo:asset options:option resultHandler:^(AVAsset * _Nullable asset, AVAudioMix * _Nullable audioMix, NSDictionary * _Nullable info) {if ([NSThread isMainThread])completion(asset, audioMix, info);elsedispatch_async(dispatch_get_main_queue(), ^{completion(asset, audioMix, info);});}];
}

    到这里获取各种资源类型的预览资源都搞定了,很不易。

5.判断资源是否在本地

  其实针对于图片资源来说,我们通过设置`为NO,并且通过requestImageDataForAsset:方法来获取imageData,如果imageData存在,那么说明在本地,如果imageDatanil说明在iCloud上。这个方法针对静态图、动态图和LIVE Photo都试用,虽然LIVE Photo本质上是image + MOV,但是可以想象图片和视频一体的,如果图片有的话,那么视频也就有,所以判断LIVE Phot也可以通过这个方法判断。++判断是否在本地的方法较为耗时,建议异步处理。++
&emsp;&emsp;针对于视频,就像上一条说的,我们获取视频的
AVPlayItem即可,如果为nil`那么表示视频在iCloud上。

+ (void)judgeAssetIsInLocalAlbum:(PHAsset *)asset completion:(void (^)(BOOL isInLocalAlbum))completion
{if (!completion)return ;if (asset.mediaType == PHAssetMediaTypeImage){//  针对于LivePhoto来说,不用单独的去判断别的,因为如果有的话,image和mov都有,如果没有的话,都没有,如果下载的话,会把两个都下载下来PHImageRequestOptions * option = [[PHImageRequestOptions alloc] init];option.networkAccessAllowed = NO;//  修改为异步,但不是全部都是异步,也有可能是同步,但是优化了很多option.synchronous = NO;[[PHCachingImageManager defaultManager] requestImageDataForAsset:asset options:option resultHandler:^(NSData * _Nullable imageData, NSString * _Nullable dataUTI, UIImageOrientation orientation, NSDictionary * _Nullable info) {BOOL isInLocalAlbum = imageData ? YES : NO;if ([NSThread isMainThread])completion(isInLocalAlbum);elsedispatch_async(dispatch_get_main_queue(), ^{completion(isInLocalAlbum);});}];}else if (asset.mediaType == PHAssetMediaTypeVideo){PHVideoRequestOptions * option = [[PHVideoRequestOptions alloc] init];option.networkAccessAllowed = NO;[[PHCachingImageManager defaultManager] requestPlayerItemForVideo:asset options:option resultHandler:^(AVPlayerItem * _Nullable playerItem, NSDictionary * _Nullable info) {BOOL isInLocalAlbum = playerItem ? YES : NO;if ([NSThread isMainThread])completion(isInLocalAlbum);elsedispatch_async(dispatch_get_main_queue(), ^{completion(isInLocalAlbum);});}];}
}

6.LIVE Photo导出

  上面提到了LIVE Photo本质上是image + MOV,所以如果导出的话,我们直接拿到视频资源和图片资源就好了。那么如果拿到视频资源呢?想想获取GIF的data,我们用到了PHAssetResourceManagerwriteDataForAssetResource:toFile:options:completionHandler:方法,这里也同样试用,我们拿到资源后导出即可。

+ (void)exportLivePhotoForAsset:(PHAsset *)asset completion:(void (^)(NSString * exportFilePath, NSError * error))completion;
{[self requestLivePhotoForAsset:asset completion:^(PHLivePhoto *livePhoto, NSDictionary *info) {NSArray <PHAssetResource *> * assetResources = [PHAssetResource assetResourcesForLivePhoto:livePhoto];BOOL find = NO;for (PHAssetResource * assetResource in assetResources){//  如果要拿图片资源,这个修改为PHAssetResourceTypePhoto即可if (assetResource.type == PHAssetResourceTypeVideo || assetResource.type == PHAssetResourceTypePairedVideo){NSString * exportFilePath = [self getExportFilePathForType:AssetMediaTypeLivePhoto];PHAssetResourceRequestOptions * option = [[PHAssetResourceRequestOptions alloc] init];option.networkAccessAllowed = YES;[[PHAssetResourceManager defaultManager] writeDataForAssetResource:assetResource toFile:[NSURL fileURLWithPath:exportFilePath] options:option completionHandler:^(NSError * _Nullable error) {if ([NSThread isMainThread])completion(exportFilePath, error);elsedispatch_async(dispatch_get_main_queue(), ^{completion(exportFilePath, error);});}];find = YES;break;}}if (find == NO){if ([NSThread isMainThread])completion(nil, [NSError errorWithDomain:@"com.kingsword.TuGeLe" code:-1 userInfo:nil]);elsedispatch_async(dispatch_get_main_queue(), ^{completion(nil, [NSError errorWithDomain:@"com.kingsword.TuGeLe" code:-1 userInfo:nil]);});}}];
}

7.相册中资源类型的筛选

  我们之前还有一个需求,就是在一个相册里面按照资源的类型筛选(是不是很变态)。开始没有思路,后来做的时候就比较清楚了,我们自己定义了资源类型:

typedef enum : NSUInteger {AssetMediaTypeUnknown       = 0,//  单独静图AssetMediaTypeStaticGraph   = 1 << 0,//  单独gifAssetMediaTypeGif           = 1 << 1,//  照片(包含GIF,但不处理LIVE)AssetMediaTypePhoto         = AssetMediaTypeStaticGraph | AssetMediaTypeGif,//  单独LIVEAssetMediaTypeLivePhoto     = 1 << 2,//  照片以及LIVE照片AssetMediaTypePhotoAndLive  = AssetMediaTypePhoto | AssetMediaTypeLivePhoto,//  视频AssetMediaTypeVideo         = 1 << 3,//  照片和视频AssetMediaTypePhotoAndVideo = AssetMediaTypePhoto | AssetMediaTypeVideo,//  照片、视频、LIVEAssetMediaTypeAll           = AssetMediaTypePhoto | AssetMediaTypeVideo | AssetMediaTypeLivePhoto
} AssetMediaType;

那么如果我们要筛选什么样类型的图,我们通过这个枚举类型去获取就好了,无论是单种类型,还是多种类型,都不在话下。下面以所有照片这个相册为例。

+ (NSMutableArray <AlbumItem *> *)getItemsInAlbumList:(AlbumList *)albumList withType:(AssetMediaType)mediaType limit:(NSInteger)limit
{NSMutableArray <AlbumItem *> * albumItems = [NSMutableArray array];//  这个fetchResult里面的是这个相册下所有PHAsset的结果集[albumList.fetchResult enumerateObjectsUsingBlock:^(PHAsset * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {if (albumItems.count == limit){*stop = YES;return ;}AssetMediaType type = [self getTypeForAsset:obj];//  产品需求,如果LIVE Photo存在,那么他对应的静态图中也要存在与静态图筛选器中if (type == AssetMediaTypeLivePhoto && (mediaType & AssetMediaTypeLivePhoto) == 0){if (mediaType & AssetMediaTypeStaticGraph)type = AssetMediaTypeStaticGraph;elsereturn ;}else if ((mediaType & type) == 0)return ;NSString * duration = [self getDuration:obj];[albumItems addObject:[AlbumItem itemWithAsset:obj type:type duration:duration]];}];return albumItems;
}

总结

  自定义相册看起来容易,其实里面要挖掘,还是有很多坑的,这些只是针对于我们项目中的需求开发遇到的一些坑,有更深层次的自定义可能还会有很多其他坑。其实遇到坑也不要慌,搜搜资料,看看官方文档,都可以解决。最后原大家踏平所有坑,早日成为大牛…

iOS封装相册API的tips相关推荐

  1. 极光推送之java后台封装REST API

    1 什么是推送? 这个看图效果最好请直接看下图: 我们手机经常会收到如上图弹框消息,我们今天说的就是上面的弹窗信息如何推送的.一般情况我们可以通过第三的服务来给自己的app发送推送消息例如:极光推送. ...

  2. 【Ios】 Unity for iOS 打开相册、相机及保存图片到相册

    偶然在网上看到一位博主写的 unity 和ios 交互 个人感觉 功能很全 转载地址:http://blog.csdn.net/AnYuanLzh/article/details/50748928 在 ...

  3. 转 : Squareup刷卡器,音频读卡识别android/iOS源码API

    转  :   Squareup刷卡器,音频读卡识别android/iOS源码API 相信很多人已经见过squareup的读卡器了,银行磁卡在该读卡器上一刷,则能从手机中获取银行卡磁道的信息. Squa ...

  4. axios get 某个参数是数组怎么传_Vue 中 Axios 的封装和 API 接口的管理

    我们所要的说的axios的封装和api接口的统一管理,其实主要目的就是在帮助我们简化代码和利于后期的更新维护. 一.axios的封装 在vue项目中,和后台交互获取数据这块,我们通常使用的是axios ...

  5. $.post请求的参数在后台代码中得到为null_vue中Axios的封装和API接口的管理

    来源:愣锤 https://juejin.im/post/684490365288107214 如图,面对一团糟代码的你~~~真的想说,What F~U~C~K!!! 回归正题,我们所要的说的axio ...

  6. [zz]Maya C++ API Programming Tips

    Maya C++ API Programming Tips source : http://wanochoi.com/?page_id=1588 How to handle the multiple ...

  7. iOS 封装跑马灯和轮播效果

    代码地址如下: http://www.demodashi.com/demo/14075.html 功能概述和预览 功能描述:WSL_RollView 是基于UICollectionView实现的支持水 ...

  8. jq封装接口ajax,jquery ajax方法封装及api文件设计的代码示例

    本篇文章给大家带来的内容是关于jquery ajax方法封装及api文件设计的代码示例,有一定的参考价值,有需要的朋友可以参考一下,希望对你有所帮助. 封装 jquery ajax 文件/** * 封 ...

  9. router中获取vuex_Vue 中 Axios 的封装和 API 接口的管理

    (给JavaScript加星标,提升前端技能) 作者:愣锤 https://juejin.im/post/684490365288107214 如图,面对一团糟代码的你~~~真的想说,What F~U ...

最新文章

  1. TensorRT PoolingLayer
  2. java方块排序_[代码全屏查看]-NxN方块排序,可自动运行
  3. jdk安装教程_在JDK 12精简数字格式中使用最小分数数字
  4. Python 常用排序Demo|冒、插、快、希等入门算法
  5. < Android数据存储> 任务二 应用程序数据文件夹里的文件读写
  6. Python3 闭包函数及nonlocal
  7. QT实现RSS新闻阅读器
  8. Java黑皮书课后题第5章:**5.32(游戏:彩票)修改程序清单3-8,产生一个两位数的彩票。这两位数是不同的
  9. centos 安装pm2
  10. Linux学习笔记5
  11. CCCC-GPLT L3-013. 非常弹的球 团体程序设计天梯赛
  12. 将pandas DataFrame写入CSV文件
  13. Linux上配置jupyter的步骤及与本地映射
  14. solr6.6 solrJ索引富文本(word/pdf)文件
  15. 锐捷网络linux如何认证上网,如何在Linux里面进行锐捷认证上网
  16. 旅行商问题(TSP) 中国34个城市 经纬度平面坐标
  17. 1037u处理器搭载文件服务器,悦升IVB 赛扬1037U工控主板 满足多行业需求
  18. 第二章 @Entity实例里常用的注解详解
  19. 系统更新荣耀play服务器,华为宣布:荣耀Play推送EMUI 9.1正式版更新!
  20. php 图片上传($_FILES)

热门文章

  1. 算法 树7 二叉搜索树的操作集
  2. 山经·南山经:堂庭山
  3. 4070ti和3080性能差多少 rtx4070ti和rtx3080区别对比
  4. Channel 通道详解
  5. 学习Photoshop的一些网站以及找素材的网站
  6. 【软件测试】测试员vs测试工程师,你是测试员还是测试工程师?
  7. 华米科技Amazfit GTR2不负众望,获得创新智能手表奖
  8. 隐马尔科夫模型(HMM)等文章记录
  9. postgresql-9.5.5数据库安装教程
  10. uboot do_bootm函数详解