AssetsLibrary的详细使用
AssetsLibrary的组成
AssetsLibrary的组成和iPhone中相册的实际组成十分的类似,AssetsLibrary库中的类结构对应相册中的相册应用、相册、相片或视频,具体的类组成如下:
1.AssetsLibrary:代表iPhone中的资源库(包含所有的photo、video),可以这么认为,AssetsLibrary就带包iPhone中的相册应用。可以通过AssetsLibrary获取所有的AssetsGroup。同时可以向资源库中添加相册;
2.AssetsGroup:映射照片库(AssetsLibrary)中的一个相册,同过AssetsGroup可以获取相册相应的信息,同时获取可以通过相册(AssetsGroup)获取相册下的资源,同时也在当前相册下保存资源;
3.ALAsset:对应相册中的一张照片或者视频,ALAsset包含了照片或视频的详细信息,可以通过ALAsset获取缩略图。另一方面可以使用ALAsset的实例方法保存照片或视频;
4.ALAssetRepresentation:ALAssetRepresentation 可以理解成是对ALAsset的封装(但不是其子类),可以更方便地获取 ALAsset 中的资源信息。通过ALAssetRepresentation可以获取原图、全屏图。每个 ALAsset 都有至少有一个 ALAssetRepresentation 对象,可以通过 defaultRepresentation 获取。而例如使用系统相机应用拍摄的 RAW + JPEG 照片,则会有两个 ALAssetRepresentation,一个封装了照片的 RAW 信息,另一个则封装了照片的 JPEG 信息。
AssetsLibrary一般用于定制一个图片选择器,图片选择器相应的可以实现多选、自定义界面。相应的思路应该是:
1.实例化照片库,列出所有相册
2.展示相册中的所有图片
3.对选图片或则点击图片预览大图。
因此AssetsLibrary的讲解大体通过上面的过程来详细的讲解。
1. 实例化AssetsLibrary && AssetsLibrary类的详解
1>实例化AssetsLibrary
实例化很简单,就普通的alloc init方法,如果你还想精简那就用new吧
ALAssetsLibrary * library = [[ALAssetsLibrary alloc] init];
2>遍历照片库(ALAssetsLibrary)中的所有相册(AssetsGroup)
self.groups = [NSMutableArray array];
// 遍历相册
[library enumerateGroupsWithTypes:ALAssetsGroupSavedPhotos usingBlock:^(ALAssetsGroup *group, BOOL *stop) {
if (group) { // 遍历相册还未结束
// 设置过滤器
[group setAssetsFilter:[ALAssetsFilter allPhotos]];
if (group.numberOfAssets) {
[self.groups addObject:group];
}
} else { // 遍历结束(当group为空的时候就意味着结束)
if (self.groups.count) {
// 如果相册个数不为零,则可以在此处开始遍历相册了
//[self enumerateAssets];
} else {
NSLog(@"没有相册列表");
}
}
} failureBlock:^(NSError *error) {
NSLog(@"遍历失败");
}];
上面的代码遍历出了所有的相册,并把相册中资源数量不为空的相册对象(AssetsGroup)的应用存储到了一个数组中。
需要注意的是:
1.ALAssetsLibrary需要相册为空,即相册中没有任何资源。如果你不想把为空的相册保存到数组中,可以通过AssetsGroup的numberOfAssets属性判断相册是否为空,为空则不保存。
2.ALAssetsGroup有一个过滤器的实例方法:setAssetsFilter,通过此方法传入的枚举值,可以设置只获取相册中的photo或者video。同时设置后,或更新AssetsGroup对应的numberOfAssets。
3.在AssetsLibrary框架之下,所有的遍历操作都是异步(Asynchronous)执行的。因为用户的资源库、相册可能很大,这种条件之下,异步遍历不至于堵塞主线程。另外,当遍历对应的block中的输出结果参数为空(nil),则意味遍历结束。当然,通过给定的stop参数,你也可以在满足某种条件下手动的停止遍历。
2. 遍历相册(AssetsGroup)中的所有相片或视频(Asset)
AssetsGroup提供了三种遍历方式:
1.- (void)enumerateAssetsUsingBlock:
2.- (void)enumerateAssetsWithOptions:usingBlock:
3.- (void)enumerateAssetsAtIndexes:options:usingBlock:
遍历相册:
- (void)enumerateAssets {
NSMutableArray * assets = [NSMutableArray new];
for (ALAssetsGroup * group in self.groups) {
/*
// 遍历所有的相片
[group enumerateAssetsWithOptions:NSEnumerationReverse usingBlock:^(ALAsset *result, NSUInteger index, BOOL *stop) {
if (result) { // 遍历未结束
[assets addObject:result];
} else { // result 为nil,遍历结束
}
}];
*/
// 遍历指定的相片
NSInteger fromIndex = 0; // 重指定的index开始遍历
NSInteger toIndex =5; // 指定最后一张遍历的index
[group enumerateAssetsAtIndexes:[NSIndexSet indexSetWithIndex:toIndex] options:NSEnumerationReverse usingBlock:^(ALAsset *result, NSUInteger index, BOOL *stop) {
if (index > toIndex) { // 已经遍历到指定的最后一张照片
*stop = YES; // 停止遍历
} else {
if (result) {
// 存储相片
[assets addObject:result];
} else { // 遍历结束
// 展示图片
//[self showPhotoWith:result];
}
}
}];
}
}
遍历相册(ALAssetsGroup)中的照片(ALAsset)和遍历资源库(ALAssetsLibrary)中的相册过程十分的类似,不过遍历相册中的照片更加的灵活,可以通过enumerateAssetsUsingBlock: 遍历所有的照片,同时可以通过enumerateAssetsAtIndexes:options:usingBlock:方法遍历指定index的照片。这正如上面代码中段注释类是遍历所有的照片,段注释下面遍历指定index一样。
ALAssetsGroup的详解
ALAssetsGroup代表的是一个相册,这个概念在上面已经说得够详细了,就不介绍了。在这里主要说说ALAssetsGroup类中的方法与属性的含义和用法。
- (void)showALAssetsGroupInfo:(ALAssetsGroup *)assetsGroup {
// 是否可编辑,即相册是否可以通过代码添加相片
BOOL editable = assetsGroup.editable;
// 添加一个ALAsset到当前相册,前提editable = YES,
[assetsGroup addAsset:nil];
/**
+ (ALAssetsFilter *)allPhotos; // 获取Photo
+ (ALAssetsFilter *)allVideos; // 获取Video
+ (ALAssetsFilter *)allAssets; // 获取Photo还有video
*/
// 设置过滤器
[assetsGroup setAssetsFilter:[ALAssetsFilter allPhotos]];
// 当前过滤器下的ALAsset数量
NSInteger number = assetsGroup.numberOfAssets;
/**
NSString *const ALAssetsGroupPropertyName; // Group的名称
NSString *const ALAssetsGroupPropertyType; // Group类型(ALAssetsGroupType)
NSString *const ALAssetsGroupPropertyPersistentID;
NSString *const ALAssetsGroupPropertyURL; // 唯一表示这个Group的URL,可以通过URL在资源库中获取对应的Group,用于唯一标识这个group
*/
// 通过Property获取ALAssetsGroup对应的信息
NSLog(@"%@", [assetsGroup valueForProperty:ALAssetsGroupPropertyName]);
NSLog(@"%@", [assetsGroup valueForProperty:ALAssetsGroupPropertyType]);
NSLog(@"%@", [assetsGroup valueForProperty:ALAssetsGroupPropertyPersistentID]);
NSLog(@"%@", [assetsGroup valueForProperty:ALAssetsGroupPropertyURL]);
// 获取相册封面
[assetsGroup posterImage];
}
3 获取相片(ALAsset)中的详细内容
在这里需要详细的说明一下,所有的ALAsset所对应的照片信息存在与ALAsset中的ALAssetRepresentation对象中,通过ALAssetRepresentation可以获取这个ALAsset对应的原图、修改过后的图。而通过ALAsset可以获取ALAsset对应的缩略图。另外需要说明的是,一个ALAsset可能对应多个ALAssetRepresentation,原因在与相册取图的时候可能抓取多种不同格式的图片,例如:RAW和JPEG格式的图片,此时ALAsset就存在两个ALAssetRepresentation,此时就可以通过不同的ALAssetRepresentation获取不同格式的原图、修改后的全屏图。
获取原图、全屏图:
- (void)showPhotoWith:(ALAsset *)asset {
// 获取ALAsset对应的ALAssetRepresentation
ALAssetRepresentation * representation = [asset defaultRepresentation];
NSLog(@"%@", representation.url); // 图片URL
NSLog(@"%@", NSStringFromCGSize(representation.dimensions)); // 图片尺寸
NSLog(@"%lld", representation.size); // 数据字节
NSLog(@"%@", representation.UTI); // Uniform Type Identifier : 统一类型标识符(表示图片或视频的类型)
NSLog(@"%@", representation.filename); // 在相册中的文件名
NSLog(@"%@", representation.metadata); // 元数据(一些设备相关的信息,比如使用的相机)
NSLog(@"%lf", representation.scale); // 缩放比例
NSLog(@"%ld", representation.orientation); // 方向
/**
fullScreenImage : 返回当前设备尺寸大小的图片,编辑后的图片
fullResolutionImage : 原图,没有编辑的图片
*/
// 获取原图
UIImage * image = [UIImage imageWithCGImage:[representation fullScreenImage] scale:1.0 orientation:UIImageOrientationDownMirrored];
self.imageView.image = image;
}
fullResolutionImage 是图片的原图,通过 fullResolutionImage 获取的图片没有任何处理,包括通过系统相册中“编辑”功能处理后的信息也没有被包含其中,因此需要展示“编辑”功能处理后的信息,使用 fullResolutionImage 就比较不方便,另外 fullResolutionImage 的拉取也会比较慢,在多张 fullResolutionImage 中切换时能明显感觉到图片的加载过程。因此这里建议获取图片的 fullScreenImage,它是图片的全屏图版本,这个版本包含了通过系统相册中“编辑”功能处理后的信息,同时也是一张缩略图,但图片的失真很少,缺点是图片的尺寸是一个适应屏幕大小的版本,因此展示图片时需要作出额外处理,但考虑到加载速度非常快的原因(在多张图片之间切换感受不到图片加载耗时),仍建议使用 fullScreenImage。
ALAsset详解:
- (void)showALAssetInfoWith:(ALAsset *)asset {
/**
NSString *const ALAssetPropertyType;
NSString *const ALAssetPropertyLocation;
NSString *const ALAssetPropertyDuration;
NSString *const ALAssetPropertyOrientation;
NSString *const ALAssetPropertyDate;
NSString *const ALAssetPropertyRepresentations;
NSString *const ALAssetPropertyURLs;
NSString *const ALAssetPropertyAssetURL;
*/
NSLog(@"%@", [asset valueForProperty:ALAssetPropertyType]); // 这个type表示这个是photo还是video
NSLog(@"%@", [asset valueForProperty:ALAssetPropertyLocation]); // 拍摄的地点
NSLog(@"%@", [asset valueForProperty:ALAssetPropertyDuration]); // 视频的时长
NSLog(@"%@", [asset valueForProperty:ALAssetPropertyOrientation]); // 照片的方向
NSLog(@"%@", [asset valueForProperty:ALAssetPropertyDate]); // 照片的拍摄时间
NSLog(@"%@", [asset valueForProperty:ALAssetPropertyRepresentations]);
NSLog(@"%@", [asset valueForProperty:ALAssetPropertyURLs]); //
NSLog(@"%@", [asset valueForProperty:ALAssetPropertyAssetURL]);
[asset thumbnail]; // 返回缩略图
[asset aspectRatioThumbnail]; // 等比例缩略图
[asset representationForUTI:@"public.jpeg"]; // 通过统一类型标识获取ALAssetRepresentation
// ALAsset 还具有更改ALAsset中的元数据能力
// ALAsset 的实例方法能保存Photo、video到相册
}
4. AssetsLibrary 的坑点
- AssetsLibrary 实例需要强引用
实例一个 AssetsLibrary 后,如上面所示,我们可以通过一系列枚举方法获取到需要的相册和资源,并把其储存到数组中,方便用于展示。但是,当我们把这些获取到的相册和资源储存到数组时,实际上只是在数组中储存了这些相册和资源在 AssetsLibrary 中的引用(指针),因而无论把相册和资源储存数组后如何利用这些数据,都首先需要确保 AssetsLibrary 没有被 ARC 释放,否则把数据从数组中取出来时,会发现对应的引用数据已经丢失(参见下图)。这一点较为容易被忽略,因此建议在使用 AssetsLibrary 的 viewController 中,把 AssetsLibrary 作为一个强持有的 property 或私有变量,避免在枚举出 AssetsLibrary 中所需要的数据后,AssetsLibrary 就被 ARC 释放了。 - AssetsLibrary 遵循写入优先原则
写入优先也就是說,在利用 AssetsLibrary 读取资源的过程中,有任何其它的进程(不一定是同一个 App)在保存资源时,就会收到 ALAssetsLibraryChangedNotification,让用户自行中断读取操作。最常见的就是读取 fullResolutionImage 时,用进程在写入,由于读取 fullResolutionImage 耗时较长,很容易就会 exception。 - 开启 Photo Stream 容易导致 exception
本质上,这跟上面的 AssetsLibrary 遵循写入优先原则是同一个问题。如果用户开启了共享照片流(Photo Stream),共享照片流会以 mstreamd 的方式“偷偷”执行,当有人把相片写入 Camera Roll 时,它就会自动保存到 Photo Stream Album 中,如果用户刚好在读取,那就跟上面说的一样产生 exception 了。由于共享照片流是用户决定是否要开启的,所以开发者无法改变,但是可以通过下面的接口在需要保护的时刻关闭监听共享照片流产生的频繁通知信息。
[ALAssetsLibrary disableSharedPhotoStreamsSupport];