欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

iOS7、8、9相册适配

程序员文章站 2023-12-02 10:27:40
前言 由于在ios8及以后苹果将原有的操作相册的alassetslibrary framework替换为photos framework,所以,如果在应用中使用到的相册需要支持ios8以下的版本的话...

前言

由于在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等位置信息。

phcollection
phcollection是个基类,它有两个子类。分别是phassetcollectionphcollectionlistphassetcollection代表 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

phphotolibrary
phphotolibrary是个单例对象,与alassetslibrary相比发生了较大变化,只能申请获取photos权限以及授权状态获取等。
使用这个单例你也可以注册相册改动的监听者,在相册发生变化(比如添加或导入了新图片)时,做一些刷新或其他处理。

新的实体

在ios8及以上的相册资源获取中,都是使用fetch相关api的形式,过程类似core data。匹配资源的过程自然需要匹配规则,最终匹配结果也需要一个集合来记录。

phfetchoptions
phfetchoptions的实例对象代表获取资源时的匹配规则。
可以指定资源排序规则(nsarray *sortdescriptors;)、资源类型规则(nspredicate *predicate)、最大数量(nsuinteger fetchlimit)等。

phfetchresult
phfetchresult的实例对象代表相册资源的匹配结果集合。它包含零个或多个符合匹配规则的资源,这些资源可以是phassetcollection对象,也可以是phasset对象。

phimagemanager
phimagemanager是一个单例对象,不需要你手动创建,这个单例对象可以让你获取到一个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。

这种场景可以在数据层对alassetsgroupphassetcollection以及alassetphasset做抽象,抽取适配器即可。

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