相冊適配
前言
由于在iOS8及以后蘋果將原有的操作相冊的ALAssetsLibrary framework替換為Photos framework,所以,如果在應用中使用到的相冊需要支持iOS8以下的系統(tǒng)版本的話,就需要了解Photos framework以做不同的版本適配。
一、iOS8以下
1. 幾個重要的實體概念
-
ALAsset(iOS8及以后使用PHAsset)
一個ALAsset
實例對象代表一個資源實體,比如一張圖片、一個視頻。共有三種類型:ALAssetTypePhoto // 圖片 ALAssetTypeVideo // 視頻 ALAssetTypeUnknown // 未知
通過這個實例,你可以獲取到這個資源的創(chuàng)建時間、資源類型、縮略圖、二進制數(shù)據(jù)等信息。
ALAssetsGroup(iOS8及以后使用PHAssetCollection)
一個ALAssetsGroup
實例對象代表一組資源的集合,也就是一個相冊,可以是系統(tǒng)默認存在的相冊(相機膠卷),也可以是開發(fā)者給用戶創(chuàng)建的自定義相冊(QQ)。
通過它,你可以獲取到這個相冊的名稱、封面縮略圖等。ALAssetsLibrary(iOS8及以后使用PHPhotoLibrary)
一個ALAssetsLibrary
實例對象對所有的相冊資源進行索引。
使用它,你可以獲取指定類型的相冊、單張圖片等對象,也可以添加新的自定義相冊或者圖片到相薄。如果你想要知道有沒有獲取到系統(tǒng)相薄的訪問權(quán)限,同樣需要用到它。
2. 實體相關(guān)API
ALAssetsLibrary
在iOS7及以下系統(tǒng)中,如果你想要獲取相冊中的資源,或者對相冊進行操作,那么你先要創(chuàng)建一個ALAssetsLibrary實例對象。
-
獲取指定類型的相冊
- (void)enumerateGroupsWithTypes:(ALAssetsGroupType)types usingBlock:(ALAssetsLibraryGroupsEnumerationResultsBlock)enumerationBlock failureBlock:(ALAssetsLibraryAccessFailureBlock)failureBlock
注意:
由于這里的遍歷都是異步的操作,所以ALAssetsLibrary的實例對象需要被一個靜態(tài)變量或者成員變量引用來保證不被銷毀,回調(diào)的Block才能保證被成功調(diào)用。下面同此處。
-
根據(jù)一個相冊URL獲取這個相冊
- (void)groupForURL:(NSURL *)groupURL resultBlock:(ALAssetsLibraryGroupResultBlock)resultBlock failureBlock:(ALAssetsLibraryAccessFailureBlock)failureBlock
-
創(chuàng)建一個相冊
- (void)addAssetsGroupAlbumWithName:(NSString *)name resultBlock:(ALAssetsLibraryGroupResultBlock)resultBlock failureBlock:(ALAssetsLibraryAccessFailureBlock)failureBlock
-
相冊授權(quán)狀態(tài)
+ (ALAuthorizationStatus)authorizationStatus
ALAssetsGroup
iOS7及以下系統(tǒng)的中每個相冊都是一個ALAssetsGroup實例,你可以使用這個實例獲取這個相冊的相關(guān)信息。
-
獲取相冊信息
- (id)valueForProperty:(NSString *)property
參數(shù)property包含以下類型:
ALAssetsGroupPropertyName
ALAssetsGroupPropertyType
ALAssetsGroupPropertyPersistentID
ALAssetsGroupPropertyURL
-
封面圖
- (CGImageRef)posterImage
-
相冊包含實體(照片、視頻)的數(shù)量
- (NSInteger)numberOfAssets
-
過濾規(guī)則
- (void)setAssetsFilter:(ALAssetsFilter *)filter
注意:
參數(shù)中的filter是一個ALAssetsFilter實例,這個實例只有三種類型:
+ (ALAssetsFilter *)allPhotos; // 所有圖片
+ (ALAssetsFilter *)allVideos; // 所有視頻
+ (ALAssetsFilter *)allAssets; // 所有視頻及圖片
在使用- (NSInteger)numberOfAssets
獲取相冊實體數(shù)量時,會依賴該過濾規(guī)則。比如一個相薄中存在5個視頻和5張圖片,如果指定allPhotos,則numberOfAssets返回值為5。同樣使用- (void)enumerateAssetsUsingBlock:(ALAssetsGroupEnumerationResultsBlock)enumerationBlock
獲取相冊中的所有資源時,也只能獲取到指定過濾規(guī)則下的資源。
-
使用相應遍歷規(guī)則獲取相冊中的所有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),
```
通過這個實例,除了可以獲取到這個資源的創(chuàng)建時間、資源類型、縮略圖、二進制數(shù)據(jù)等信息,還可以獲取到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
相比發(fā)生了較大變化,只能申請獲取PHOtos權(quán)限以及授權(quán)狀態(tài)獲取等。
使用這個單例你也可以注冊相冊改動的監(jiān)聽者,在相冊發(fā)生變化(比如添加或?qū)肓诵聢D片)時,做一些刷新或其他處理。
新的實體
在iOS8及以上的相冊資源獲取中,都是使用fetch
相關(guān)API的形式,過程類似Core Data
。匹配資源的過程自然需要匹配規(guī)則,最終匹配結(jié)果也需要一個集合來記錄。
PHFetchOptions
PHFetchOptions
的實例對象代表獲取資源時的匹配規(guī)則。
可以指定資源排序規(guī)則(NSArray<NSSortDescriptor *> *sortDescriptors;
)、資源類型規(guī)則(NSPredicate *predicate
)、最大數(shù)量(NSUInteger fetchLimit
)等。PHFetchResult
PHFetchResult
的實例對象代表相冊資源的匹配結(jié)果集合。它包含零個或多個符合匹配規(guī)則的資源,這些資源可以是PHAssetCollection
對象,也可以是PHAsset
對象。PHImageManager
PHImageManager
是一個單例對象,不需要你手動創(chuàng)建,這個單例對象可以讓你獲取到一個PHAsset資源的實際二進制數(shù)據(jù)——如一張圖片數(shù)據(jù)。
2. 實體相關(guān)API
PHAssetCollection
在iOS8及以上系統(tǒng)中,獲取相冊不需要創(chuàng)建實例,直接使用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; // 預估資源數(shù)量 @property (nonatomic, strong, readonly, nullable) NSDate *startDate; // 開始日期 @property (nonatomic, strong, readonly, nullable) NSDate *endDate; // 結(jié)束日期 @property (nonatomic, strong, readonly, nullable) CLLocation *approximateLocation; // 位置信息 @property (nonatomic, strong, readonly) NSArray<NSString *> *localizedLocationNames; // 位置名稱
注意:
estimatedAssetCount
并不是一個準確的值,當PHAssetCollection
對象不能馬上計算出當前相冊中的資源數(shù)量時,會返回NSNotFound
,如果想要獲取資源的具體數(shù)量,需要使用PHAsset
來fetch所有資源,計算資源數(shù)量。
PHAsset
-
獲取某相冊中的PHAsset資源
+ (PHFetchResult<PHAsset *> *)fetchAssetsInAssetCollection:(PHAssetCollection *)assetCollection options:(nullable PHFetchOptions *)options;
-
獲取某相冊的封面資源
+ (nullable PHFetchResult<PHAsset *> *)fetchKeyAssetsInAssetCollection:(PHAssetCollection *)assetCollection options:(nullable PHFetchOptions *)options;
注意:
這個API可以獲取到系統(tǒng)默認的一些封面圖資源,這個結(jié)果由零個或多個PHAsset組成,在相冊是smartAlbum類型的情況下,可能會有零個結(jié)果的情況。
-
獲取中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;
參數(shù)中的mediaType可選:
```
PHAssetMediaTypeUnknown = 0,
PHAssetMediaTypeImage = 1,
PHAssetMediaTypeVideo = 2,
PHAssetMediaTypeAudio = 3,
```
PHImageManager
-
獲取PHAsset圖片資源的圖片數(shù)據(jù)
- (PHImageRequestID)requestImageForAsset:(PHAsset *)asset targetSize:(CGSize)targetSize contentMode:(PHImageContentMode)contentMode options:(nullable PHImageRequestOptions *)options resultHandler:(void (^)(UIImage *__nullable result, NSDictionary *__nullable info))resultHandler;
注意:
參數(shù)中的PHImageRequestOptions
對象可以設(shè)置目標圖片的一些屬性,其中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;
}
```
-
獲取一個相冊中的所有圖片資源,按創(chuàng)建時間降序排序
+ (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;
}
三、適配參考
我在不同系統(tǒng)的相冊適配中采用適配器的方式進行的適配,僅供參考。
1. 適配器
相冊適配主要是為了解決在iOS8以上的系統(tǒng)中,使用ALLibrary相關(guān)API可能存在一些不兼容的問題,導致資源獲取不一致情況的出現(xiàn)。
在業(yè)務展現(xiàn)上并不期望因系統(tǒng)不同而給用戶差別較大的體驗,所以可以只針對相冊資源數(shù)據(jù)獲取的數(shù)據(jù)測適配即可,而無需改動UI。
這種場景可以在數(shù)據(jù)層對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