由于在iOS8及以后苹果将原有的操作相册的ALAssetsLibrary framework替换为Photos framework,所以,如果在应用中使用到的相册需要支持iOS8以下的系统版本的话,就需要了解Photos framework以做不同的版本适配。
ALAsset(iOS8及以后使用PHAsset)
一个ALAsset
实例对象代表一个资源实体,比如一张图片、一个视频。共有三种类型:
ALAssetTypePhoto // 图片
ALAssetTypeVideo // 视频
ALAssetTypeUnknown // 未知
通过这个实例,你可以获取到这个资源的创建时间、资源类型、缩略图、二进制数据等信息。
ALAssetsGroup(iOS8及以后使用PHAssetCollection)
一个ALAssetsGroup
实例对象代表一组资源的集合,也就是一个相册,可以是系统默认存在的相册(相机胶卷),也可以是开发者给用户创建的自定义相册(QQ)。
通过它,你可以获取到这个相册的名称、封面缩略图等。
ALAssetsLibrary(iOS8及以后使用PHPhotoLibrary)
一个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
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
获取所有相册,并过滤相册中的视频
+ (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);
}
}
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
是个基类,它有两个子类。分别是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
PHPhotoLibrary
PHPhotoLibrary
是个单例对象,与ALAssetsLibrary
相比发生了较大变化,只能申请获取PHOtos权限以及授权状态获取等。
使用这个单例你也可以注册相册改动的监听者,在相册发生变化(比如添加或导入了新图片)时,做一些刷新或其他处理。
在iOS8及以上的相册资源获取中,都是使用fetch
相关API的形式,过程类似Core Data
。匹配资源的过程自然需要匹配规则,最终匹配结果也需要一个集合来记录。
PHFetchOptions
PHFetchOptions
的实例对象代表获取资源时的匹配规则。
可以指定资源排序规则(NSArray<NSSortDescriptor *> *sortDescriptors;
)、资源类型规则(NSPredicate *predicate
)、最大数量(NSUInteger fetchLimit
)等。
PHFetchResult
PHFetchResult
的实例对象代表相册资源的匹配结果集合。它包含零个或多个符合匹配规则的资源,这些资源可以是PHAssetCollection
对象,也可以是PHAsset
对象。
PHImageManager
PHImageManager
是一个单例对象,不需要你手动创建,这个单例对象可以让你获取到一个PHAsset资源的实际二进制数据——如一张图片数据。
在iOS8及以上系统中,获取相册不需要创建实例,直接使用PHAssetCollection
的类方法进行操作即可。
获取指定类型的相册
// Fetch asset collections of a single type and subtype provided (use PHAssetCollectionSubtypeAny to match all subtypes)
+ (PHFetchResult<PHAssetCollection *> *)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<NSString *> *localizedLocationNames; // 位置名称
注意:
estimatedAssetCount
并不是一个准确的值,当PHAssetCollection
对象不能马上计算出当前相册中的资源数量时,会返回NSNotFound
,如果想要获取资源的具体数量,需要使用PHAsset
来fetch所有资源,计算资源数量。
获取某相册中的PHAsset资源
+ (PHFetchResult<PHAsset *> *)fetchAssetsInAssetCollection:(PHAssetCollection *)assetCollection options:(nullable PHFetchOptions *)options;
获取某相册的封面资源
+ (nullable PHFetchResult<PHAsset *> *)fetchKeyAssetsInAssetCollection:(PHAssetCollection *)assetCollection options:(nullable PHFetchOptions *)options;
注意:
这个API可以获取到系统默认的一些封面图资源,这个结果由零个或多个PHAsset组成,在相册是smartAlbum类型的情况下,可能会有零个结果的情况。
获取中Photos中的所有PHAsset资源
+ (PHFetchResult<PHAsset *> *)fetchAssetsWithOptions:(nullable PHFetchOptions *)options;
注意:
这里并不是获取某个相册中的PHAsset资源,而是手机中所有的PHAsset资源。
比如手机中有10个相册,每个相册中10个PHAsset资源,如果不指定PHFetchOptions,使用该方法会获取到所有的100个PHAsset资源。
获取Photos中指定资源类型的所有PHAsset资源
+ (PHFetchResult<PHAsset *> *)fetchAssetsWithMediaType:(PHAssetMediaType)mediaType options:(nullable PHFetchOptions *)options;
参数中的mediaType可选:
PHAssetMediaTypeUnknown = 0,
PHAssetMediaTypeImage = 1,
PHAssetMediaTypeVideo = 2,
PHAssetMediaTypeAudio = 3,
获取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,也就是异步获取。
获取指定类型的相册
+ (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;
}
我在不同系统的相册适配中采用适配器的方式进行的适配,仅供参考。
相册适配主要是为了解决在iOS8以上的系统中,使用ALLibrary相关API可能存在一些不兼容的问题,导致资源获取不一致情况的出现。
在业务展现上并不期望因系统不同而给用户差别较大的体验,所以可以只针对相册资源数据获取的数据测适配即可,而无需改动UI。
这种场景可以在数据层对ALAssetsGroup
和PHAssetCollection
以及ALAsset
和PHAsset
做抽象,抽取适配器即可。
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