优雅的创建一个相册管理类(兼容AssetsLibrary和PhotoKit)
在这篇文章的基础上,我github开源了一个自定义相册,进行了大量优化以及封装,配合着demo去看,会更容易理解。地址:https://github.com/lovisty/YFPhotoAlbum
越来越多的app选择图片的时候,放弃iOS默认的界面,使用自定义相册去展示,并供用户选择。做到这些,肯定少不了一个获取相册分组和每个分组里的图片的类,这里要展示一下一个Mange应该做的工作。
主要有两个工作,第一,获取相册分组。第二获取每个分组里的照片信息。这里针对iOS8之前的ALAssetsLibrary 和之后的PhotoKit进行阐述。
ALAssetsLibrary
先陈述一下几个类的意义
ALAssetsLibrary:
可以实现查看相册列表,增加相册分组,保存图片到相册等功能。
ALAssetsGroup:
包含了相册分组的信息,分组名称,分组类型,封面图片,分组内资源的数量等信息。
ALAsset:
包含了某个分组里的资源文件,以及图片缩略图,资源类型等其他资源信息。
ALAssetRepresentation:
是通过ALAsset的 defaultRepresentation 方法获取的一个类。
包含了资源文件的更加详细的信息,比如,大小,尺寸,高清图等。
如果在 ALAsset 里找不到,那就到 ALAssetRepresentation 里去逛逛吧,总有新的发现。
看看效(mei)果(nv):
show代码
.h 文件
#import <Foundation/Foundation.h>
#import <AssetsLibrary/AssetsLibrary.h>
typedef void(^AssetsLibraryInfoBlock)(NSArray *groupArray);
typedef void(^ALAssetsBlock)(NSArray *photoArray);
@interface PXPhotoAlbumManger : NSObject
@property (nonatomic, strong) ALAssetsLibrary *assetsLibrary;
@property (nonatomic, copy) AssetsLibraryInfoBlock assetsLibraryInfo; //获取分组列表信息
@property (nonatomic, copy) ALAssetsBlock photosInfoBlock; //获取分组里面的资源信息
- (void)allPhotoGroup:( ALAssetsGroupType )groupType assetsLibraryInfo:(AssetsLibraryInfoBlock)assetsLibraryInfo ALAssetsoInfo:(ALAssetsBlock)ALAssetsoInfo;
- (void)allPhotoInALAssetsGroup:(ALAssetsGroup *)assetsGroup ALAssetsoInfo:(ALAssetsBlock)ALAssetsoInfo;
.m 文件
获取所有分组信息,并获取其中一个指定的分组里的资源
- (void)allPhotoGroup:( ALAssetsGroupType )groupType assetsLibraryInfo:(AssetsLibraryInfoBlock)assetsLibraryInfo ALAssetsoInfo:(ALAssetsBlock)ALAssetsoInfo{
NSMutableArray *groupArray = [NSMutableArray array];
self.assetsLibrary = [[ALAssetsLibrary alloc] init];
@weakify(self)
[self.assetsLibrary enumerateGroupsWithTypes:groupType usingBlock:^(ALAssetsGroup *group, BOOL *stop) {
@strongify(self)
if (group) {
if ([group numberOfAssets] > 0) {
[groupArray addObject:group];
}
}else{
NSArray *resultArray = [[groupArray reverseObjectEnumerator] allObjects];
assetsLibraryInfo(resultArray);
//第一次默认遍历第一个相册分组 用于默认展示
[self allPhotoInALAssetsGroup:[resultArray firstObject] ALAssetsoInfo:^(NSArray *photosInfoArray) {
ALAssetsoInfo(photosInfoArray);
}];
}
} failureBlock:^(NSError *error) {
}];
}
获取指定分组里的资源信息
- (void)allPhotoInALAssetsGroup:(ALAssetsGroup *)assetsGroup ALAssetsoInfo:(ALAssetsBlock)ALAssetsoInfo{
NSMutableArray *assetsArray = [NSMutableArray array];
[assetsGroup enumerateAssetsWithOptions:NSEnumerationReverse usingBlock:^(ALAsset *result, NSUInteger index, BOOL *stop) {
if (result != nil) {
[assetsArray addObject:result];
}else{
ALAssetsoInfo(assetsArray);
}
}];
}
调用:
第一次调用,默认展示一个相册分组里的内容(遍历分组获取图片是一个耗时操作,所以不要一次去获取所有分组里的图片。如果项目有需求,可以考虑放到子线程进行。)
@weakify(self)
[self.manger allPhotoGroup:ALAssetsGroupAll assetsLibraryInfo:^(NSArray *groupArray) {
@strongify(self)
self.groups = groupArray;
[self.view addSubview:self.photoAlbumView];
} ALAssetsoInfo:^(NSArray *photoArray) {
@strongify(self)
[self.assetsImages addObjectsFromArray:photoArray];
[self.collectionView reloadData];
}];
切换相册分组的时候调用
@weakify(self)
[self.manger allPhotoInALAssetsGroup:group ALAssetsoInfo:^(NSArray *photoArray) {
@strongify(self)
[self.assetsImages addObjectsFromArray:photoArray];
[self.collectionView reloadData];
}];
在看看效果
当然这些只是基本的相册展示,实际项目里还需要有一些其他东西,比如选择(单选和多选)。你肯定也许需要这些方法:
AssetsGroup
[group valueForProperty:ALAssetsGroupPropertyName];//分组名称(一般用于显示)
[group valueForProperty:ALAssetsGroupPropertyType];//分组类型(用于获取分组里的资源)
[group valueForProperty:ALAssetsGroupPropertyPersistentID];//分组ID(用于区分不同的分组)
ALAsset
[asset valueForProperty:ALAssetPropertyType];//获取资源类型(ALAssetTypePhoto,ALAssetTypeVideo,ALAssetTypeUnknown)
[asset valueForProperty:ALAssetPropertyAssetURL];//获取资源的URL(可用于区分是否为同一张图片【因为不同分组里的图片可能相同】)
[asset valueForProperty:ALAssetPropertyDuration];//如果是视频文件,返回视频的长度
[asset valueForProperty:ALAssetPropertyLocation];//返回资源的地理位置
[asset valueForProperty:ALAssetPropertyOrientation];//返回拍摄方向 ALAssetPropertyOrientation为枚举类型
[asset valueForProperty:ALAssetPropertyRepresentations];//获取资源的描述信息
ALAssetRepresentation
ALAssetRepresentation *representation = [asset defaultRepresentation];
[representation dimensions];//返回图片的尺寸
[representation size]; //返回图片的大小,即所占用物理空间
[representation filename]; //文件的名字
如果你的app已经不再支持iOS8一下的操作系统,那么你就可以任性的不再使用ALAssetsLibrary,使用iOS8新的PhotoKit框架。使用起来更方便,获取的信息更完整,也更高效。下面更新将对PhotoKit进行一些解析。
PhotoKit
所有的 PhotoKit
对象都是继承自 PHObject 抽象基类
同样,在展示代码前,先熟悉一下下面几个类
PHCollection:
这是一个抽象类,我们不会对其进行实例化,当然这样做也没什么意义。它的两个子类 PHAssetCollection or PHCollectionList 才是我们想要的。
PHAssetCollection:
表示一个相册或者一个时刻,或者是一个由系统提供的一系列智能相册(SmartAlbum),
如:最近添加,最近删除,屏幕截图,收藏等。
PHCollectionList:
表示一组 PHCollections 。它可能同时包含 PHAssetCollection 和 PHCollectionList ,所以它本身也是一个PHCollection。
这里有些不容易理解的,首先PHCollectionList会包含PHAssetCollection信息,比如用户自己创建的相册。
同时也会包含具有继承关系的一系列相册(PHCollectionList),比如系统相册里的时刻-年度。
PHFetchResult:
表示一系列的资源结果集合。
当你使用PHAsset,PHCollection,PHAssetCollection 和 PHCollectionList类检索对象,都会返回一个PHFetchResult对象,你所需要的信息,就在这个对象里。
另外,虽然这是同步操作,但官方告诉我们,不用去担心性能问题,即使是获取大量数据,依然能够保持很好的性能。
PHAsset:
代表照片库中的一个资源或图片,或视频,另外它会包含iCloud里的内容。这点和 ALAsset 有些类似。
PHImageManager:
与ALAsset 的不同之处在乎,通过 PHAsset 可直接获取资源的信息。但是PHAsset不行,它要通过 PHImageManager 进行获取。
PHImageManager 用于处理图片或者视频资源的加载,可以获取到图片的缩略图,原图,以及用来播放的视频文件。另外一个亮点就是资源的加载过程带有缓存处理,下一次加载的时候就会变得很快。对于我们想要的资源,可以通过传入一个 PHImageRequestOptions 控制输出,得到我们想要的样子。
PHImageRequestOptions:
当然就是控制加载资源的一系列参数了。其中有 synchronous(是否为同步请求,默认NO), PHImageRequestOptionsVersion , PHImageRequestOptionsDeliveryMode , PHImageRequestOptionsResizeMode , CGRect , networkAccessAllowed , PHAssetImageProgressHandler 等一系列条件限制。
PhotoKit最直观的展示,是不是比上面的强大多了
熟悉了上面的这些类(如果不熟悉,可以结合下面的代码理解一下),我们看一下具体的代码。
Show Code
.h
#import <Foundation/Foundation.h>
typedef void(^PhotoKitAllGrougsBlock)(NSArray *groupArray);
typedef void(^PHImagesBlock)(NSArray *photoArray);
@interface PXPhotoKitManger : NSObject
@property (nonatomic, strong) NSMutableArray *photosArray;
@property (nonatomic, strong) NSMutableArray *assetCollections;
//如果所有group(包含智能相册和用户自己创建的相册)
- (void)allGroupInfo:(PhotoKitAllGrougsBlock)allGrougsInfo PHImagesInfo:(PHImagesBlock)PHImagesInfo;
- (void)allPhotoInPHAssetGroup:(PHAssetCollection *)assetCollection PHImagesInfo:(PHImagesBlock)PHImagesInfo;
//获取相册的封面【这里不像AssetsLibrary那样简单,需要我们遍历相册取第一张图片】
- (void)theCoverPhotoInPHAssetGroup:(PHAssetCollection *)assetCollection PHImagesInfo:(PHImagesBlock)PHImagesInfo;
.m
获取相册分组 并默认获取一个指定相册里的内容
#pragma mark - 获取相册分组
- (void)allGroupInfo:(PhotoKitAllGrougsBlock)allGrougsInfo PHImagesInfo:(PHImagesBlock)PHImagesInfo{
self.assetCollections = [NSMutableArray array];
self.photosArray = [NSMutableArray array];
PHFetchOptions *fetchOptions = [[PHFetchOptions alloc] init];
fetchOptions.sortDescriptors = @[[NSSortDescriptor sortDescriptorWithKey:@"localizedTitle" ascending:YES]];
PHFetchResult *smartAlbums = [PHAssetCollection fetchAssetCollectionsWithType:PHAssetCollectionTypeSmartAlbum subtype:PHAssetCollectionSubtypeAlbumRegular options:nil];
PHFetchResult *userAlbums = [PHCollectionList fetchTopLevelUserCollectionsWithOptions:nil];
[self obtainAllAssetCollectionWithPHFetchResult:@[smartAlbums,userAlbums] allGrougsInfo:allGrougsInfo PHImagesInfo:PHImagesInfo];
}
- (void)obtainAllAssetCollectionWithPHFetchResult:(NSArray *)resultAlbums allGrougsInfo:(PhotoKitAllGrougsBlock)allGrougsInfo PHImagesInfo:(PHImagesBlock)PHImagesInfo{
PHFetchResult *smartAlbums = resultAlbums[0];
PHFetchResult *userAlbums = resultAlbums[1];
for (int i = 0; i < userAlbums.count; i++) {
PHCollection *collection = userAlbums[i];
if ([collection isKindOfClass:[PHAssetCollection class]]) {
PHAssetCollection *assetCollection = (PHAssetCollection *)collection;
[self.assetCollections addObject:assetCollection];
}
}
for (NSInteger i = 0; i < smartAlbums.count; i++) {
PHCollection *collection = smartAlbums[i];
if ([collection isKindOfClass:[PHAssetCollection class]]) {
PHAssetCollection *assetCollection = (PHAssetCollection *)collection;
[self.assetCollections addObject:assetCollection];
}
}
allGrougsInfo(self.assetCollections);
[self allPhotoInPHAssetGroup:self.assetCollections[0] PHImagesInfo:^(NSArray *photos) {
[self.photosArray addObjectsFromArray:photos];
PHImagesInfo(photos);
}];
}
#pragma mark - 获取一个指定相册里的内容
- (void)allPhotoInPHAssetGroup:(PHAssetCollection *)assetCollection PHImagesInfo:(PHImagesBlock)PHImagesInfo{
PHFetchResult *fetchResult = [PHAsset fetchAssetsInAssetCollection:assetCollection options:nil];
//如果相册里没有照片,直接返回nil
if (fetchResult.count <=0) {
PHImagesInfo(nil);
return;
}
PHImageManager *manger = [[PHImageManager alloc]init];
NSMutableArray *photos = [NSMutableArray array];
PHImageRequestOptions *options = [[PHImageRequestOptions alloc] init];
options.deliveryMode = PHImageRequestOptionsDeliveryModeHighQualityFormat;
__block NSInteger tempMark = 0;
for (NSInteger i = 0; i < fetchResult.count; i++) {
// 获取一个资源(PHAsset)
PHAsset *asset = fetchResult[i];
[manger requestImageForAsset:asset targetSize:PHImageManagerMaximumSize contentMode:PHImageContentModeDefault options:options resultHandler:^(UIImage * _Nullable result, NSDictionary * _Nullable info) {
[photos addObject:result];
if (tempMark == fetchResult.count-1) {
PHImagesInfo(photos);
}
tempMark++;
}];
}
}
#pragma mark - 获取相册封面
- (void)theCoverPhotoInPHAssetGroup:(PHAssetCollection *)assetCollection PHImagesInfo:(PHImagesBlock)PHImagesInfo{
NSLog(@"%@ %ld ",assetCollection.localizedTitle,(long)assetCollection.assetCollectionSubtype);
PHFetchResult *fetchResult = [PHAsset fetchAssetsInAssetCollection:assetCollection options:nil];
if (fetchResult.count<=0) {
UIImage *image = [UIImage imageNamed:@"picture"];
PHImagesInfo(@[image]);
return;
}
PHImageManager *manger = [[PHImageManager alloc]init];
// 获取一个资源(PHAsset)
PHAsset *asset = fetchResult[0];
NSMutableArray *photos = [NSMutableArray array];
PHImageRequestOptions *options = [[PHImageRequestOptions alloc] init];
options.synchronous = YES;//为了效果,我这里选择了同步 因为只获取一张照片,不会对界面产生很大的影响
[manger requestImageForAsset:asset targetSize:CGSizeMake(50, 50) contentMode:PHImageContentModeDefault options:options resultHandler:^(UIImage * _Nullable result, NSDictionary * _Nullable info) {
[photos addObject:result];
PHImagesInfo(photos);
}];
}
使用的时候
[self.manger allGroupInfo:^(NSArray *groupArray) {
__strong __typeof(weakSelf)self = weakSelf;
self.groups = groupArray;
} PHImagesInfo:^(NSArray *photoArray) {
//第一张代表拍照的图片
UIImage *camera = [UIImage imageNamed:@"camera"];
if (camera) {
[self.photos addObject:camera];
}
[self.photos addObjectsFromArray:photoArray];
[self.collectionView reloadData];
}];
切换的时候
- (void)switchAssetCollectionGroup:(PHAssetCollection *)assetCollection{
[self.manger allPhotoInPHAssetGroup:assetCollection PHImagesInfo:^(NSArray *photoArray) {
UIImage *camera = [UIImage imageNamed:@"camera"];
if (camera) {
[self.photos addObject:camera];
}
[self.photos addObjectsFromArray:photoArray];
[self.collectionView reloadData];
}];
}
另外,如何在一个项目里同时使用AssetsLibrary和PhotoKit。这里就需要一个具有包容精神的Model,可以接收来自不同地方的数据。
我这里给model分别提供了两个方法
声明:
+ (NSArray *)transformALAssetsGroupToModelFrom:(NSArray *)groups;
+ (NSArray *)transformPHAssetCollectionToModelFrom:(NSArray *)collections;
实现
//AssetsLibrary 赋值
+ (NSArray *)transformALAssetsGroupToModelFrom:(NSArray *)groups{
NSMutableArray *groupArray = [NSMutableArray array];
for (ALAssetsGroup *group in groups) {
PXPhotoAlbumModel *model = [[PXPhotoAlbumModel alloc]init];
model.albumName = [group valueForProperty:ALAssetsGroupPropertyName];
UIImage* image = [UIImage imageWithCGImage:group.posterImage];
model.coverImage = image;
model.photoCount = group.numberOfAssets;
model.groupType = [[group valueForProperty:ALAssetsGroupPropertyType] integerValue];
model.group = group;
[groupArray addObject:model];
}
return [groupArray copy];
}
//PhotoKit 赋值
+ (NSArray *)transformPHAssetCollectionToModelFrom:(NSArray *)collections{
NSMutableArray *groupArray = [NSMutableArray array];
for (PHAssetCollection *assetCollection in collections) {
PXPhotoAlbumModel *model = [[PXPhotoAlbumModel alloc]init];
model.albumName = assetCollection.localizedTitle;
PXPhotoKitManger *manger = [[PXPhotoKitManger alloc]init];
//获取封面
[manger theCoverPhotoInPHAssetGroup:assetCollection PHImagesInfo:^(NSArray *photoArray) {
model.coverImage = photoArray[0];
}];
PHFetchResult *assetsFetchResult = [PHAsset fetchAssetsInAssetCollection:assetCollection options:nil];
//获取照片个数
model.photoCount = assetsFetchResult.count;
model.assetCollection = assetCollection;
[groupArray addObject:model];
}
return groupArray;
}
这样,我们获取想要的值,都可以通过这个model直接获取。
以上就是AssetsLibrary和PhotoKit的基本概念,使用,以及简单封装。更多更深入的东西还有待我们发掘。有相关疑问欢迎联系我,大家一起讨论,相互学习。
QQ:617267337