iOS開發 圖片選擇器、圖片多選功能的實現

版權聲明:本文爲博主原創文章,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文連接。

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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章