版權聲明:本文爲博主原創文章,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文連接。
Photos.framework是iOS8後蘋果推出的一套替代AssetsLibrary.framework獲取相冊資源的原生庫,至於AL庫,歡迎大家給博文iOS開發——簡單實現圖片多選功能(AssetsLibrary.framework篇)提出寶貴的意見。
樓主大部分都是查看官方開發文檔進行探索的(當然,實在不明白了也會請求google 的 0.0 )。這裏就說一下個人的看法吧,相比AL庫,Photos的開發文檔顯然更像是目前我們接觸的ObjC語言(如果不信,可以對比一下AL庫和Photos庫的開發文檔)。初次接觸這個庫的時候可能會感覺比較亂,畢竟類的數量比AL庫多了好多,但在熟悉大體邏輯之後,就會發現它的分工比AL更加明確,並且使用起來要比AL靈活的多。
提醒一下,要使用相冊資源庫的時候,爲了適配一下將來的iOS10,不要忘記在info.plist文件中加入NSPhotoLibraryUsageDescription
這個描述字段啊,更多的權限坑請關注一下博文 iOS開發——iOS
10 由於權限問題導致崩潰的那些坑
Demo全代碼 : https://github.com/RITL/RITLImagePickerDemo
話說的有點多了,下面就談談個人對Photos的理解,這裏只記錄一下Photos.framework中類的使用與理解,真正的實現多選功能請前去上面的鏈接下載demo查看,多謝指正:
類邏輯
研究一個庫或者框架,總體邏輯一定是要縷清的,下面就是個人對photos的理解,有點多,分類一下吧:
資源類
- PHPhotoLibrary 是一個資源庫。能夠獲取相冊權限以及對相冊的操作,與AL不同,它不能獲取資源對象哦.
- PHFetchResult 是一個結果集,一個泛型類。通過方法獲取到的相冊或者資源組就是被封裝成該類返回.
- PHAssetCollection 是一個資源集合對象。其實它就是一個相冊的概念,可通過
類方法
獲得想要的相冊集合,繼承自PHCollection. - PHCollectionList 是一個資源集合列表對象。剛接觸時以爲它是存放PHCollection對象的集合,後來才知道,如果想要通過地點以及時間分組的話,請使用這個類替代PHAssetCollection吧,用法與PHAssetCollection類似,同樣是繼承自PHCollection.
- PHAsset 是一個獨立的資源對象。可以通過
類方法
對PHCollection對象進行遍歷,獲得存放Asset對象的結果集,可以直接獲得資源的規格數據,若想獲得圖片以及原圖等資源,需要配合PHImageManager對象,繼承自PHObject.
工具類
- PHFetchOptions 一個遍歷配置類。一般情況下,當存在遍歷方法的時候就存在這個類型的參數,裏面含有謂詞、遍歷順序等屬性,可以通過設置這些屬性,完成不同的遍歷.
- PHImageManager 是一個負責渲染資源的類。比如獲得PHAsset對象的原圖等操作需要使用該類.
- PHCachingImageManager 繼承自PHImageManager,可以對請求的資源對象進行緩存,這樣再次獲取時就不需要重新渲染,在加快獲取速度的同時也降低了CPU的壓力,這裏最好對緩存的PHImageRequestID進行一下記錄,防止同一資源被無限緩存的尷尬.
- PHImageRequestOptions 是一個資源請求的配置類。通常在使用PHImageManager對某個資源進行請求時都會存在此類型的參數,可以在請求資源時對該對象進行設置,獲得想要的結果,比如原圖..
請求類
- 請求類不能獨立使用,要想發揮作用,需要與PHPhotoLibrary對象配合使用.
- PHAssetCollectionChangeRequest 集合變化請求類,負責對PHAssetCollection對象的操作
- PHCollectionListChangeRequest 集合變化請求類,負責PHCollectionList對象的操作
- PHAssetChangeRequest 資源變化請求類,負責PHAsset對象的操作
類庫中的類及其屬性方法:
這裏提到的都是代碼中用到的屬性和方法,如果只是爲了多圖選擇,那麼以下的方法應該是夠用的,不夠的話可以Command+單擊進入開發文檔查看即可。
PHPhotoLibrary_照片庫
基礎方法
我覺得下面的方法應該都懂,畢竟每個涉及到權限的庫都會存在下面三個方法的.
<span style="font-size:14px;">// 獲得單例對象
+ (PHPhotoLibrary *)sharedPhotoLibrary;
// 獲得相冊權限
+ (PHAuthorizationStatus)authorizationStatus;
//請求權限
+ (void)requestAuthorization:(void (^)(PHAuthorizationStatus status))handler;</span>
操作相冊
之前說請求類不能獨自使用,需要配合PHPhotoLibrary對象,爲什麼這麼說呢,是因爲在使用請求類的時候必須使用下面兩個方法其中之一,下面是開發文檔的一句話:
<span style="font-size:14px;">/*表示請求只能通過下面兩種方法的block進行創建和使用,所有的ChangeRequest類上面都會存在這句話,當然類名肯定不一樣的.*/
PHAssetCollectionChangeRequest can only be created or used within a -[PHPhotoLibrary performChanges:]
or -[PHPhotoLibrary performChangesAndWait:] block.
// 異步執行change的變化請求
- (void)performChanges:(dispatch_block_t)changeBlock completionHandler:(void (^)(BOOL success, NSError *__nullable error))completionHandler;
//同步執行change的請求變化
- (BOOL)performChangesAndWait:(dispatch_block_t)changeBlock error:(NSError *__autoreleasing *)error;</span>
其實我感覺只看上面的兩個方法感覺會比較抽象,那麼就拿出Demo中的兩段小源碼舉個例子,相信這樣就比較好理解了.
新建相冊
<span style="font-size:14px;">//新建一個名字叫做title的相冊
-(void)addCustomGroupWithTitle:(NSString *)title
completionHandler:(void (^)(void))successBlock
failture:(void (^)(NSString * _Nonnull))failtureBlock
{
[self.photoLibaray performChanges:^{
// 創建一個創建相冊的請求
[PHAssetCollectionChangeRequest creationRequestForAssetCollectionWithTitle:title];
}completionHandler:^(BOOL success, NSError * _Nullable error) {
if (success == true) // 成功
{
successBlock();
}
// 失敗
failtureBlock(error.localizedDescription);
}];
}</span>
向相冊裏面添加圖片
<span style="font-size:14px;">// 向collection中添加圖片
-(void)addCustomAsset:(UIImage *)image
collection:(PHAssetCollection *)collection
completionHandler:(void (^)(void))successBlock
failture:(void (^)(NSString * _Nonnull))failtureBlock
{
// 執行變化請求
[self.photoLibaray performChanges:^{
// 如果相冊允許操作
if ([collection canPerformEditOperation:PHCollectionEditOperationAddContent])
{
// 創建資源請求對象
PHAssetChangeRequest * assetChangeRequest = [PHAssetChangeRequest creationRequestForAssetFromImage:image];
// 創建相冊請求對象
PHAssetCollectionChangeRequest * groupChangeRequest = [PHAssetCollectionChangeRequest changeRequestForAssetCollection:collection];
// 向相冊中添加資源
[groupChangeRequest addAssets:@[assetChangeRequest.placeholderForCreatedAsset]];
}
}completionHandler:^(BOOL success, NSError * _Nullable error) {
if (success == true) // 成功
{
successBlock(); return;
}
// 失敗
failtureBlock(error.localizedDescription);
}];
}
// 這裏不止能夠通過圖片對象創建,還存在如下兩種創建方法
+ (nullable instancetype)creationRequestForAssetFromImageAtFileURL:(NSURL *)fileURL; // 通過圖片所在的路徑url進行創建
+ (nullable instancetype)creationRequestForAssetFromVideoAtFileURL:(NSURL *)fileURL; // 通過視頻所在的路徑url進行創建</span>
常用屬性
<span style="font-size:14px;">// 組的標題,比如Camera Roll(膠捲相冊)
@property (nonatomic, strong, readonly, nullable) NSString *localizedTitle;
// 資源組的類型,比如是智能相冊,普通相冊還是外界創建的相冊
PHAssetCollectionType assetCollectionType;
typedef NS_ENUM(NSInteger, PHAssetCollectionType) {
PHAssetCollectionTypeAlbum = 1, // 傳統相冊
PHAssetCollectionTypeSmartAlbum = 2, // 智能相冊
PHAssetCollectionTypeMoment = 3, // 自定義創建的相冊
} NS_ENUM_AVAILABLE_IOS(8_0);
// 具體的子類型,比如是智能相冊的自拍還是喜愛等,這個枚舉類太多,就不進行粘貼了.
PHAssetCollectionSubtype assetCollectionSubtype;
// 資源組中資源的大約數量,不一定準,如果想要確切的,獲得PHFetchResult對象取count即可
NSUInteger estimatedAssetCount;
// 最早的一張圖片存在相冊的時間</span>
NSDate *startDate;
// 最近的一張圖片存在相冊的時間
NSDate *endDate;</span>
獲得具體資源的方法,基本都是通過類方法進行獲取,這樣就降低了PhotosLibrary對象複雜度。
<span style="font-size:14px;">// 判斷是否能夠進行編輯,如果是進行修改請求,最好通過這個方法來判斷是下</span>
- (BOOL)canPerformEditOperation:(PHCollectionEditOperation)anOperation;
// 獲得智能分組,比如膠捲相冊,最近添加,自拍等
PHFetchResult * smartGroups = [PHAssetCollection fetchAssetCollectionsWithType:PHAssetCollectionTypeSmartAlbum
subtype:PHAssetCollectionSubtypeAlbumRegular
options:nil];
// 獲得我們自定義創建的相冊組,比如有QQ的手機應該都會有QQ這個相冊,那麼通過該方法就可以獲取的到
+ (PHFetchResult<PHCollection *> *)fetchTopLevelUserCollectionsWithOptions:(nullable PHFetchOptions *)options;</span>
如果PHFetchResult覺得用起來不是很爽的話,可以將其包裝成數組來進行下一步的操作,Demo中就是將其打包成數組來進行操作的:
<span style="font-size:14px;">@implementation PHFetchResult (NSArray)
// 將PHFetchResult對象轉成NSArray對象
-(void)transToArrayComplete:(void (^)(NSArray<PHAssetCollection *> * _Nonnull, PHFetchResult * _Nonnull))arrayObject
{
__weak typeof(self) weakSelf = self;
NSMutableArray * array = [NSMutableArray arrayWithCapacity:0];
if (self.count == 0)
{
arrayObject([array mutableCopy],weakSelf);
array = nil;
return;
}
[self enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
[array addObject:obj];
// 如果遍歷完畢,進行回調
if (idx == self.count - 1)
{
arrayObject([array mutableCopy],weakSelf);
}
}];
}
@end</span>
PHAsset_資源對象
/<span style="font-size:14px;">/ 資源媒體的類型
PHAssetMediaType mediaType;
typedef NS_ENUM(NSInteger, PHAssetMediaType) {
PHAssetMediaTypeUnknown = 0, // 未知類型
PHAssetMediaTypeImage = 1, // 圖片類型
PHAssetMediaTypeVideo = 2, // 視頻類型
PHAssetMediaTypeAudio = 3, // 音頻類型
} NS_ENUM_AVAILABLE_IOS(8_0);
// 資源美圖的子類型,比如如果資源是圖片,那麼它是全景還是HDR,如果是iOS9,還能知道他是屏幕截圖還是live圖片,枚舉有點多,也不再次粘貼了.
PHAssetMediaSubtype mediaSubtypes;
// 資源的像素寬
NSUInteger pixelWidth;
// 資源的像素高
NSUInteger pixelHeight;
// 資源的創建日期
NSDate *creationDate;
// 資源的最近一次修改的時間
NSDate *modificationDate;
// 資源拍攝的地點
CLLocation *location;
// 如果是音頻或者視頻,它的持續時間
NSTimeInterval duration;
// 它是否被隱藏
BOOL hidden;
// 它是否是喜愛的
BOOL favorite;</span>
常用搭配使用方法
<span style="font-size:14px;">// 請求圖片,將想要獲取Image實體的資源,裁剪的大小以及方式進行獲取圖片
// 如果想要原圖,設置PHImageRequestOptions對象的deliveryMode屬性爲PHImageRequestOptionsDeliveryModeHighQualityFormat即可
[[PHImageManager defaultManager]requestImageForAsset:asset
targetSize:size
contentMode:PHImageContentModeAspectFill
options:option
resultHandler:^(UIImage * _Nullable result, NSDictionary * _Nullable info) {
// 通過block回傳圖片,並將部分信息存在於info字典中,並且該方法的返回值可以在info字典中找到
}];
// 請求數據,獲取資源的Data對象,可以用來計算資源的大小。
// 這樣可以避免UIImage->Data導致內存以及CPU瞬間激增
[[PHImageManager defaultManager]requestImageDataForAsset:asset
options:option
resultHandler:^(NSData * _Nullable imageData, NSString * _Nullable dataUTI, UIImageOrientation orientation, NSDictionary * _Nullable info) {
// 在block裏面獲取圖片的各種信息
}]; // 這裏最好存在一個標位置或者其他方法標誌一下,避免每次都要緩存導致的卡頓以及CPU卡死
[((PHCachingImageManager *)[PHCachingImageManager defaultManager])startCachingImagesForAssets:@[copy_self]
targetSize:newSize
contentMode:PHImageContentModeAspectFill
options:nil];</span>
PHFetchResult_結果集合
表示乍一看,是不是和數組很相似呀.
屬性
<span style="font-size:14px;">// 當前集合的數量
NSUInteger count;
// 獲得index位置的對象
- (ObjectType)objectAtIndex:(NSUInteger)index;
// 是否包含對象
- (BOOL)containsObject:(ObjectType)anObject;
// 對象的索引
- (NSUInteger)indexOfObject:(ObjectType)anObject;
// 第一個對象
ObjectType firstObject;
// 最後一個對象
ObjectType lastObject;</span>
遍歷歷方法,應該也是獲取集合內部數據的唯一方法了.
<span style="font-size:14px;">/*使用Block進行枚舉遍歷,stop控制是否停止,每次獲得數據都會執行一次回調*/
- (void)enumerateObjectsUsingBlock:(void (^)(ObjectType obj, NSUInteger idx, BOOL *stop))block;
/*根據枚舉類型進行枚舉,比如正序還是倒序*/
- (void)enumerateObjectsWithOptions:(NSEnumerationOptions)opts usingBlock:(void (^)(ObjectType obj, NSUInteger idx, BOOL *stop))block;
/*枚舉特定區間並按照響應枚舉類型進行遍歷*/
- (void)enumerateObjectsAtIndexes:(NSIndexSet *)s options:(NSEnumerationOptions)opts usingBlock:(void (^)(ObjectType obj, NSUInteger idx, BOOL *stop))block;</span>
相冊發生變化
使用PHPhotoLibrary對象註冊觀察者,當然,不要在dealloc或者特定的地方註銷觀察者啊,與KVO相同.
<span style="font-size:14px;">// 註冊觀察者
- (void)registerChangeObserver:(id<PHPhotoLibraryChangeObserver>)observer;
// 註銷觀察者
- (void)unregisterChangeObserver:(id<PHPhotoLibraryChangeObserver>)observer;</span>
PHPhotoLibraryChangeObserver
協議方法
<span style="font-size:14px;">// This callback is invoked on an arbitrary serial queue.
// If you need this to be handled on a specific queue,
// you should redispatch appropriately.
// 這個回調是在一個隨意的線程中被喚起,如果需要在一個特定的線程中處理,應該在合適的地方重新喚起
// (翻譯可能不太準確,如果需要更新,一般情況需要在主線程,這個應該都懂得.)
- (void)photoLibraryDidChange:(PHChange *)changeInstance;</span>
實例
具體用法如下:
首先需要查看是否發生了變化,如果沒有變化,那麼就返回nil;如果發生了變化,就會返回響應類型的對象:
<span style="font-size:14px;">// PHChange對象的兩個方法:
// 獲取PHObject對象的變化詳情,其實PHAsset和PHCollection都是繼承自PHObject的
- (nullable PHObjectChangeDetails *)changeDetailsForObject:(PHObject *)object;
-
// 獲取結果集的變化詳情,和上面一樣,如果不存在變化,返回nil
- (nullable PHFetchResultChangeDetails *)changeDetailsForFetchResult:(PHFetchResult *)object;</span>
<span style="font-size:14px;">// PHChange對象的兩個方法: // 獲取PHObject對象的變化詳情,其實PHAsset和PHCollection都是繼承自PHObject的 - (nullable PHObjectChangeDetails *)changeDetailsForObject:(PHObject *)object; - // 獲取結果集的變化詳情,和上面一樣,如果不存在變化,返回nil - (nullable PHFetchResultChangeDetails *)changeDetailsForFetchResult:(PHFetchResult *)object;</span>
下面記錄一下描述詳細變化的類吧:
PHObjectChangeDetails
<span style="font-size:14px;font-weight: normal;">// PHObject對象變化詳情對象
PHObjectChangeDetails.h
// 變化之前的對象
@property (atomic, strong, readonly) __kindof PHObject *objectBeforeChanges;
// 變化之後的對象,
@property (atomic, strong, readonly, nullable) __kindof PHObject *objectAfterChanges;
// 內容是否發生了變化
@property (atomic, readonly) BOOL assetContentChanged;
// 該對象是否已經刪除
@property (atomic, readonly) BOOL objectWasDeleted;
@end
/*
1、判斷一下是否被刪除了,如果被刪除了,那麼請把數據源的該對象也刪除了吧,並重新reload一下當前的視圖.
2、如果沒有被刪除,就可以知道是否發生了變化,那麼,獲取變化後的內容對象並將之前的內容replace一下,刷新視圖即可
*/</span>
<span style="font-size:14px;">// PHObject對象變化詳情對象
PHObjectChangeDetails.h
// 變化之前的對象
@property (atomic, strong, readonly) __kindof PHObject *objectBeforeChanges;
// 變化之後的對象,
@property (atomic, strong, readonly, nullable) __kindof PHObject *objectAfterChanges;
// 內容是否發生了變化
@property (atomic, readonly) BOOL assetContentChanged;
// 該對象是否已經刪除
@property (atomic, readonly) BOOL objectWasDeleted;
@end
/*
1、判斷一下是否被刪除了,如果被刪除了,那麼請把數據源的該對象也刪除了吧,並重新reload一下當前的視圖.
2、如果沒有被刪除,就可以知道是否發生了變化,那麼,獲取變化後的內容對象並將之前的內容replace一下,刷新視圖即可
*/</span>
PHFetchResultChangeDetails
<span style="font-size:14px;">// PHFetchResult對象發生變化的詳情類
PHFetchResultChangeDetails.h
// 變化之前的結果集
@property</span> (atomic, strong, readonly) PHFetchResult *fetchResultBeforeChanges;
// 變化之後的結果集
@property (atomic, strong, readonly) PHFetchResult *fetchResultAfterChanges;
/*
這個變量很有意思:
如果爲true,表示集合發生了增刪改,那麼通過一下面的刪除、新增以及更新操作的響應屬性或方法進行數據的修改即可。
如果爲false,則表示發生了大的改變,不在提供下面那些變化的詳情,只能使用fetchResultAfterChanges屬性對該屬性進行替換即可*/
@property (atomic, assign, readonly) BOOL hasIncrementalChanges;
// 如果是刪除操作,返回刪除的位置以及刪除的對象
@property (atomic, strong, readonly, nullable) NSIndexSet *removedIndexes;
@property (atomic, strong, readonly) NSArray<__kindof PHObject *> *removedObjects;
// 如果是新增操作,返回新增的位置以及新增的對象
@property (atomic, strong, readonly, nullable) NSIndexSet *insertedIndexes;
@property (atomic, strong, readonly) NSArray<__kindof PHObject *> *insertedObjects;
// 如果是更新操作,返回更新的位置以及更新的對象
@property (atomic, strong, readonly, nullable) NSIndexSet *changedIndexes;
@property (atomic, strong, readonly) NSArray<__kindof PHObject *> *changedObjects;
/*
1、判斷一下hasIncrementalChanges值,如果爲false,直接取fetchResultAfterChanges屬性進行值的替換
2、如果爲true,根據下面的詳情數據進行相應操作即可,當然,使用全體替換的方法也是可以的,但是單個操作可以使用動畫哦
*/</span>
吐槽
我要開始吐槽啦!其實畢業回學校的時候無聊,想練習一下Swift,研究過這個庫,大體的功能與博文Demo的功能差不多,但由於起名太隨便,隨手刪了,沒錯..刪了!(還真是感謝我隨手清理垃圾簍的習慣,呵呵….所以大家一定要不忘記備份呀,= = 再就是起名不要太隨便啊),這個Demo是趁工作的業餘時間寫的,目的是加深對Photos庫理解的同時不辜負學校的那段時間,不太建議直接拿本文的Demo直接放入工程中使用哦,Demo的目的只是爲了學習一下Photos庫,儘管對Demo進行了一些內存泄露的處理,但每次還是會有大約1MB多的內存多餘佔用,這個問題會在之後樓主不斷進步的過程中進行修復的
記錄完畢3Q
原文出自 : http://blog.csdn.net/runintolove/article/details/52024806