iOS7、8、9相册适配
前言
由于在ios8及以后苹果将原有的操作相册的alassetslibrary framework替换为photos framework,所以,如果在应用中使用到的相册需要支持ios8以下的版本的话,就需要了解photos framework以做不同的版本适配。
一、ios8以下
1. 几个重要的实体概念
alasset(ios8及以后使用phasset)
一个alasset
实例对象代表一个资源实体,比如一张图片、一个视频。共有三种类型:
alassettypephoto // 图片 alassettypevideo // 视频 alassettypeunknown // 未知
通过这个实例,你可以获取到这个资源的创建时间、资源类型、缩略图、二进制数据等信息。
alassetsgroup(ios8及以后使用phassetcollection)
一个alassetsgroup
实例对象代表一组资源的集合,也就是一个相册,可以是系统默认存在的相册(相机胶卷),也可以是开发者给用户创建的自定义相册(qq)。
通过它,你可以获取到这个相册的名称、封面缩略图等。
alassetslibrary(ios8及以后使用phphotolibrary)
一个alassetslibrary
实例对象对所有的相册资源进行索引。
使用它,你可以获取指定类型的相册、单张图片等对象,也可以添加新的自定义相册或者图片到相薄。如果你想要知道有没有获取到系统相薄的访问权限,同样需要用到它。
2. 实体相关api
alassetslibrary
在ios7及以下系统中,如果你想要获取相册中的资源,或者对相册进行操作,那么你先要创建一个alassetslibrary实例对象。
获取指定类型的相册
- (void)enumerategroupswithtypes:(alassetsgrouptype)types usingblock:(alassetslibrarygroupsenumerationresultsblock)enumerationblock failureblock:(alassetslibraryaccessfailureblock)failureblock
注意:
由于这里的遍历都是异步的操作,所以alassetslibrary的实例对象需要被一个静态变量或者成员变量引用来保证不被销毁,回调的block才能保证被成功调用。下面同此处。
根据一个相册url获取这个相册
- (void)groupforurl:(nsurl *)groupurl resultblock:(alassetslibrarygroupresultblock)resultblock failureblock:(alassetslibraryaccessfailureblock)failureblock
创建一个相册
- (void)addassetsgroupalbumwithname:(nsstring *)name resultblock:(alassetslibrarygroupresultblock)resultblock failureblock:(alassetslibraryaccessfailureblock)failureblock
相册授权状态
+ (alauthorizationstatus)authorizationstatus
alassetsgroup
ios7及以下系统的中每个相册都是一个alassetsgroup实例,你可以使用这个实例获取这个相册的相关信息。
获取相册信息
- (id)valueforproperty:(nsstring *)property
参数property包含以下类型:
alassetsgrouppropertyname
alassetsgrouppropertytype
alassetsgrouppropertypersistentid
alassetsgrouppropertyurl
封面图
- (cgimageref)posterimage
相册包含实体(照片、视频)的数量
- (nsinteger)numberofassets
过滤规则
- (void)setassetsfilter:(alassetsfilter *)filter
注意:
参数中的filter是一个alassetsfilter实例,这个实例只有三种类型:
+ (alassetsfilter *)allphotos; // 所有图片 + (alassetsfilter *)allvideos; // 所有视频 + (alassetsfilter *)allassets; // 所有视频及图片
在使用- (nsinteger)numberofassets
获取相册实体数量时,会依赖该过滤规则。比如一个相薄中存在5个视频和5张图片,如果指定allphotos,则numberofassets返回值为5。同样使用- (void)enumerateassetsusingblock:(alassetsgroupenumerationresultsblock)enumerationblock
获取相册中的所有资源时,也只能获取到指定过滤规则下的资源。
使用相应遍历规则获取相册中的所有alasset资源
- (void)enumerateassetswithoptions:(nsenumerationoptions)options usingblock:(alassetsgroupenumerationresultsblock)enumerationblock
3. 使用示例
获取所有相册,并过滤相册中的视频
+ (void)getassetsgroupsforios8belowsuccess:(void (^)(nsmutablearray *))success failure:(void (^)(nserror *error))failure { nsmutablearray *assetsgroups = [nsmutablearray array]; [[self library] enumerategroupswithtypes:alassetsgroupall usingblock:^(alassetsgroup *group, bool *stop) { if (group != nil) { [group setassetsfilter:[alassetsfilter allphotos]]; if (group.numberofassets > 0) { [assetsgroups addobject:group]; } } else { if (success) { success(assetsgroups); } } } failureblock:^(nserror *error) { if (failure) { failure(error); } }]; } + (alassetslibrary *)library { static alassetslibrary *library; static dispatch_once_t oncetoken; dispatch_once(&oncetoken, ^{ library = [[alassetslibrary alloc] init]; }); return library; }
获取一个相册中的所有资源
+ (void)gettimelinesectionmodelsforios8belowwithgroup:(mralbumgroupmodel *)group success:(void (^)(nsmutablearray *))success failure:(void (^)(nserror *))failure { nsmutablearray *sectionmodels = [nsmutablearray array]; [group.assetgroup enumerateassetswithoptions:nsenumerationreverse usingblock:^(alasset *result, nsuinteger index, bool *stop) { if(result) { [sectionmodels addobject:result]; } }]; if (success != nil && sectionmodels.count > 0) { success(sectionmodels); } if (failure != nil && sectionmodels.count == 0) { failure(nil); } }
二、ios8及以上
1. 几个重要的实体概念
与alassetslibrary framework对应的几个实体
phasset
一个phasset
实例对象与alasset
类似,代表一个资源实体,比如一张图片、一个视频。与alasset
不同之处在于,多了一种实体类型,共四种类型:
phassetmediatypeunknown = 0, phassetmediatypeimage = 1, phassetmediatypevideo = 2, phassetmediatypeaudio = 3,
同时,还多了更具体的子类型phassetmediasubtype:
phassetmediasubtypenone = 0, // photo subtypes phassetmediasubtypephotopanorama = (1ul << 0), phassetmediasubtypephotohdr = (1ul << 1), phassetmediasubtypephotoscreenshot photos_available_ios_tvos(9_0, 10_0) = (1ul << 2), phassetmediasubtypephotolive photos_available_ios_tvos(9_1, 10_0) = (1ul << 3), // video subtypes phassetmediasubtypevideostreamed = (1ul << 16), phassetmediasubtypevideohighframerate = (1ul << 17), phassetmediasubtypevideotimelapse = (1ul << 18),
通过这个实例,除了可以获取到这个资源的创建时间、资源类型、缩略图、二进制数据等信息,还可以获取到location等位置信息。
phcollectionphcollection
是个基类,它有两个子类。分别是phassetcollection
和phcollectionlist
,phassetcollection
代表 photos 中的相册,phcollectionlist
代表 photos 中的文件夹。phcollectionlist
里可嵌套phassetcollection
,也可以嵌套自身类型,同时支持多重嵌套。
一个phassetcollection
实例对象与alassetsgroup
类似,代表一组资源的集合,也就是一个相册。
通过它,你可以获取到这个相册的名称、封面缩略图。
以及相册类型phassetcollectiontype
:
phassetcollectiontypealbum = 1, // 自定义相册,如qq phassetcollectiontypesmartalbum = 2, // 相机胶卷、我的照片流、屏幕截图、全景照片等 phassetcollectiontypemoment = 3, // 时刻
相册子类型phassetcollectionsubtype
:
// phassetcollectiontypealbum regular subtypes phassetcollectionsubtypealbumregular = 2, phassetcollectionsubtypealbumsyncedevent = 3, phassetcollectionsubtypealbumsyncedfaces = 4, phassetcollectionsubtypealbumsyncedalbum = 5, phassetcollectionsubtypealbumimported = 6, // phassetcollectiontypealbum shared subtypes phassetcollectionsubtypealbummyphotostream = 100, phassetcollectionsubtypealbumcloudshared = 101, // phassetcollectiontypesmartalbum subtypes phassetcollectionsubtypesmartalbumgeneric = 200, phassetcollectionsubtypesmartalbumpanoramas = 201, phassetcollectionsubtypesmartalbumvideos = 202, phassetcollectionsubtypesmartalbumfavorites = 203, phassetcollectionsubtypesmartalbumtimelapses = 204, phassetcollectionsubtypesmartalbumallhidden = 205, phassetcollectionsubtypesmartalbumrecentlyadded = 206, phassetcollectionsubtypesmartalbumbursts = 207, phassetcollectionsubtypesmartalbumslomovideos = 208, phassetcollectionsubtypesmartalbumuserlibrary = 209, phassetcollectionsubtypesmartalbumselfportraits photos_available_ios_tvos(9_0, 10_0) = 210, phassetcollectionsubtypesmartalbumscreenshots photos_available_ios_tvos(9_0, 10_0) = 211, // used for fetching, if you don't care about the exact subtype phassetcollectionsubtypeany = nsintegermax
phphotolibraryphphotolibrary
是个单例对象,与alassetslibrary
相比发生了较大变化,只能申请获取photos权限以及授权状态获取等。
使用这个单例你也可以注册相册改动的监听者,在相册发生变化(比如添加或导入了新图片)时,做一些刷新或其他处理。
新的实体
在ios8及以上的相册资源获取中,都是使用fetch
相关api的形式,过程类似core data
。匹配资源的过程自然需要匹配规则,最终匹配结果也需要一个集合来记录。
phfetchoptionsphfetchoptions
的实例对象代表获取资源时的匹配规则。
可以指定资源排序规则(nsarray
)、资源类型规则(nspredicate *predicate
)、最大数量(nsuinteger fetchlimit
)等。
phfetchresultphfetchresult
的实例对象代表相册资源的匹配结果集合。它包含零个或多个符合匹配规则的资源,这些资源可以是phassetcollection
对象,也可以是phasset
对象。
phimagemanagerphimagemanager
是一个单例对象,不需要你手动创建,这个单例对象可以让你获取到一个phasset资源的实际二进制数据——如一张图片数据。
2. 实体相关api
phassetcollection
在ios8及以上系统中,获取相册不需要创建实例,直接使用phassetcollection
的类方法进行操作即可。
获取指定类型的相册
// fetch asset collections of a single type and subtype provided (use phassetcollectionsubtypeany to match all subtypes) + (phfetchresult*)fetchassetcollectionswithtype:(phassetcollectiontype)type subtype:(phassetcollectionsubtype)subtype options:(nullable phfetchoptions *)options;
基本属性
@property (nonatomic, strong, readonly, nullable) nsstring *localizedtitle; // 相册标题 @property (nonatomic, assign, readonly) phassetcollectiontype assetcollectiontype; // 相册类型 @property (nonatomic, assign, readonly) phassetcollectionsubtype assetcollectionsubtype; // 子类型 @property (nonatomic, assign, readonly) nsuinteger estimatedassetcount; // 预估资源数量 @property (nonatomic, strong, readonly, nullable) nsdate *startdate; // 开始日期 @property (nonatomic, strong, readonly, nullable) nsdate *enddate; // 结束日期 @property (nonatomic, strong, readonly, nullable) cllocation *approximatelocation; // 位置信息 @property (nonatomic, strong, readonly) nsarray *localizedlocationnames; // 位置名称
注意:estimatedassetcount
并不是一个准确的值,当phassetcollection
对象不能马上计算出当前相册中的资源数量时,会返回nsnotfound
,如果想要获取资源的具体数量,需要使用phasset
来fetch所有资源,计算资源数量。
phasset
获取某相册中的phasset资源
+ (phfetchresult*)fetchassetsinassetcollection:(phassetcollection *)assetcollection options:(nullable phfetchoptions *)options;
获取某相册的封面资源
+ (nullable phfetchresult*)fetchkeyassetsinassetcollection:(phassetcollection *)assetcollection options:(nullable phfetchoptions *)options;
注意:
这个api可以获取到系统默认的一些封面图资源,这个结果由零个或多个phasset组成,在相册是smartalbum类型的情况下,可能会有零个结果的情况。
获取中photos中的所有phasset资源
+ (phfetchresult*)fetchassetswithoptions:(nullable phfetchoptions *)options;
注意:
这里并不是获取某个相册中的phasset资源,而是手机中所有的phasset资源。
比如手机中有10个相册,每个相册中10个phasset资源,如果不指定phfetchoptions,使用该方法会获取到所有的100个phasset资源。
获取photos中指定资源类型的所有phasset资源
+ (phfetchresult*)fetchassetswithmediatype:(phassetmediatype)mediatype options:(nullable phfetchoptions *)options;
参数中的mediatype可选:
phassetmediatypeunknown = 0, phassetmediatypeimage = 1, phassetmediatypevideo = 2, phassetmediatypeaudio = 3,
phimagemanager
获取phasset图片资源的图片数据
- (phimagerequestid)requestimageforasset:(phasset *)asset targetsize:(cgsize)targetsize contentmode:(phimagecontentmode)contentmode options:(nullable phimagerequestoptions *)options resulthandler:(void (^)(uiimage *__nullable result, nsdictionary *__nullable info))resulthandler;
注意:
参数中的phimagerequestoptions
对象可以设置目标图片的一些属性,其中synchronous表示是否同步获取,默认是no,也就是异步获取。
3. 使用示例
获取指定类型的相册
+ (nsmutablearray *)getcollecionswithsmartalbumsubtype:(phassetcollectionsubtype)subtype { phfetchoptions *useralbumsoptions = [phfetchoptions new]; phfetchresult *useralbumsresult = [phassetcollection fetchassetcollectionswithtype:phassetcollectiontypealbum subtype:phassetcollectionsubtypeany options:useralbumsoptions]; phfetchresult *usersmartalbumsresult = [phassetcollection fetchassetcollectionswithtype:phassetcollectiontypesmartalbum subtype:subtype options:useralbumsoptions]; nsmutablearray *collections = [nsmutablearray array]; void (^albumenumerateobjectsusingblock)(phassetcollection *, nsuinteger idx, bool *) = ^(phassetcollection * _nonnull collection, nsuinteger idx, bool * _nonnull stop) { if (collection.estimatedassetcount == 0) { return ; } nsuinteger numberofassets = 0; phfetchresult *assetsresult = [phasset fetchassetsinassetcollection:collection options:nil]; numberofassets = [assetsresult countofassetswithmediatype:phassetmediatypeimage]; if (numberofassets == 0) { return; } [collections addobject:collection]; }; [usersmartalbumsresult enumerateobjectsusingblock:albumenumerateobjectsusingblock]; [useralbumsresult enumerateobjectsusingblock:albumenumerateobjectsusingblock]; return collections; }
获取一个相册中的所有图片资源,按创建时间降序排序
+ (void)gettimelinesectionmodelsforios8abovewithgroup:(mralbumgroupmodel *)group success:(void (^)(nsmutablearray *))success failure:(void (^)(nserror *))failure { phfetchresult *assetsresult = [phasset fetchassetsinassetcollection:group.collection options:[self fetchoptions]]; nsmutablearray *sectionmodels = [nsmutablearray array]; [assetsresult enumerateobjectsusingblock:^(phasset * _nonnull asset, nsuinteger idx, bool * _nonnull stop) { [sectionmodels addobject:asset]; }]; if (success != nil && sectionmodels.count > 0) { success(sectionmodels); } if (failure != nil && sectionmodels.count == 0) { failure(nil); } } + (phfetchoptions *)fetchoptions { phfetchoptions *fetchoptions = [phfetchoptions new]; fetchoptions.predicate = [nspredicate predicatewithformat:@"mediatype = %@", @(phassetmediatypeimage)]; fetchoptions.sortdescriptors = @[[nssortdescriptor sortdescriptorwithkey:@"creationdate" ascending:no]]; return fetchoptions; }
三、适配参考
我在不同系统的相册适配中采用适配器的方式进行的适配,仅供参考。
1. 适配器
相册适配主要是为了解决在ios8以上的系统中,使用allibrary相关api可能存在一些不兼容的问题,导致资源获取不一致情况的出现。
在业务展现上并不期望因系统不同而给用户差别较大的体验,所以可以只针对相册资源数据获取的数据测适配即可,而无需改动ui。
这种场景可以在数据层对alassetsgroup
和phassetcollection
以及alasset
和phasset
做抽象,抽取适配器即可。
2. 简单示例
assetgroupmodel
@interface albumgroupmodel : nsobject @property (nonatomic, strong) uiimage *posterimage; @property (nonatomic, assign) nsuinteger numberofassets; @property (nonatomic, copy) nsstring *assetsgrouppropertyname; @property (nonatomic, strong) phassetcollection *collection; @property (nonatomic, strong) alassetsgroup *assetgroup; @end @implementation albumgroupmodel - (uiimage *)posterimage { if (_posterimage == nil) { if ([xslappinfotool systemversion] < 8.0) { _posterimage = [uiimage imagewithcgimage:self.assetgroup.posterimage]; } else { phfetchresult *groupresult = [phasset fetchassetsinassetcollection:self.collection options:self.class.fetchoptions]; phasset *asset = groupresult.firstobject; phimagerequestoptions *requestoptions = [[phimagerequestoptions alloc] init]; requestoptions.resizemode = phimagerequestoptionsresizemodeexact; cgfloat scale = [uiscreen mainscreen].scale; cgfloat dimension = 80.0f; cgsize size = cgsizemake(dimension * scale, dimension * scale); __block uiimage *resultimage = nil; [[phimagemanager defaultmanager] requestimageforasset:asset targetsize:size contentmode:phimagecontentmodeaspectfill options:requestoptions resulthandler:^(uiimage *result, nsdictionary *info) { resultimage = result; }]; _posterimage = resultimage; } } return _posterimage; } @end
assetmodel
@interface albumassetmodel : nsobject @property (nonatomic, strong, nullable) phasset *assetph; @property (nonatomic, strong, nullable) alasset *assetal; - (void)thumbnail:(nullable void(^)(uiimage * _nullable thumbnail))resultcallback; @end @implementation albumassetmodel - (void)thumbnail:(void (^)(uiimage *))resultcallback { if (self.assetph) { phimagemanager *manager = [phimagemanager defaultmanager]; [manager requestimageforasset:self.assetph targetsize:cgsizemake(100, 100) contentmode:phimagecontentmodedefault options:nil resulthandler:^(uiimage * _nullable result, nsdictionary * _nullable info) { if (resultcallback) { resultcallback(result); } }]; } else if (self.assetal) { if (resultcallback) { resultcallback([uiimage imagewithcgimage:[self.assetal thumbnail]]); } } } @end
推荐阅读