iOS 圖片選擇打造專屬於自己的 ImagePicker(1)

前文

從iOS8以後,Apple 就不再使用 AssetsLibrary 作爲獲取系統相冊圖片的方法了,轉而在iOS8中推出了Photokit作爲訪問系統相冊的庫。官方對Photokit的概念解釋爲:

在iOS和macOS中,PhotoKit提供了支持爲Photos應用構建照片編輯擴展的類。 在iOS和tvOS中,PhotoKit還可以直接訪問由照片應用管理的照片和視頻。
使用PhotoKit,您可以獲取和緩存assets以進行顯示和回放,編輯圖像和視頻內容,或管理assets集合,例如專輯,時刻和共享相冊。

詳細請見蘋果開發者文檔

開發

我們目前對Photokit還比較陌生,唯一能夠讓我們揭開它神祕面紗的方式就是自己動手去模仿一下系統相冊,如何去獲取系統內的所有照片資源,如何去獲取所有的相冊,以及如何將獲取到的數據直觀的展現給用戶看將是本章內容我要展示給大家的。

第一步:環境配置

  • 在Xcode項目中加入頭文件
#import <Photos/Photos.h>
  • 在Xcode中修改info.plist

    在info.plist中找到 Privacy - Photo Library Usage Description 選項,添加隱私請求權限說明,例如:淘寶想要訪問您的相冊。

第二步:認識 PhotoKit 對象

可能剛開始的時候,大家也都跟我一樣常常分不清楚 PHAsset,PHFetchOptions,PHAssetCollection,PHFetchResult,PHImageManager,PHImageRequestOptions 這些的區別,在這裏就跟大家一一簡單的做一個說明,爲下面更進一步開發做好鋪墊。

PHAsset:照片庫中圖像,視頻或 live 照片。

PHFetchOptions:一組選項控制選項包括過濾,排序和管理,用於影響在獲取PHAsset或collection對象時照片返回的結果。

PHCollection:Photos asset collections 和 collection lists 的抽象超類。

PHAssetCollection:PHCollection 的子類,表示一個相冊或者一個時刻,例如片刻,用戶創建的相冊或智能相冊。

PHFetchResult:表示一系列的資源結果集合,也可以是相冊的集合,從 PHCollection 的類方法中獲得;

PHImageManager:提供用於檢索或生成與PHAsset相關聯的圖像或視頻數據的方法。

PHCachingImageManager:PHImageManager的子類,爲了處理大量的PHAsset數據時提升性能,如果要使用照片或視頻資源的縮略圖填充UICollectionViewController 類似的UI,請使用PHCachingImageManager。

PHImageRequestOptions:控制圖片加載時的一些參數,例如同步加載or異步加載,圖片尺寸等。

PHVideoRequestOptions:控制視頻加載時的一些參數,例如同步加載or異步加載,圖片尺寸等。

PHLivePhotoRequestOptions:控制live圖片加載時的一些參數,例如同步加載or異步加載,圖片尺寸等。

第三步:PhotoKit 機制

PhotoKit是通過"Fetch"的方式去獲取系統的相冊資源,這些獲取的方式都是通過一系列的API去調用完成的,具體使用哪個類方法,則需要了解獲取的是相冊、時刻還是資源,這類方法中的 option 充當了過濾器的作用,可以過濾相冊的類型,日期,名稱等,從而直接獲取對應的資源。

獲取相冊

獲取系統智能相冊

    PHFetchResult *fetchResult = [PHAssetCollection fetchAssetCollectionsWithType:PHAssetCollectionTypeSmartAlbum subtype:PHAssetCollectionSubtypeAny options:nil];

獲取用戶創建的相冊

    PHFetchResult *topLevelUserCollections = [PHCollectionList fetchTopLevelUserCollectionsWithOptions:nil];

遍歷相冊列表

for(int i = 0; i < fetchResult.count; i++){
    PHCollection *collection = fetchResult[i];
    if([collection isKindOfClass:[PHAssetCollection class]]){
        PHAssetCollection *assetCollection = (PHAssetCollection *)collection;
        
        ....
        NSString *title = assetCollection.localizedTitle;
    }
}

獲取系統智能相冊API中的subtype是由多個不同選擇組成的枚舉,根據你自己想要獲取的數據需求來決定:

typedef NS_ENUM(NSInteger, 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,
    PHAssetCollectionSubtypeSmartAlbumDepthEffect PHOTOS_AVAILABLE_IOS_TVOS(10_2, 10_1) = 212,
    PHAssetCollectionSubtypeSmartAlbumLivePhotos PHOTOS_AVAILABLE_IOS_TVOS(10_3, 10_2) = 213,
    PHAssetCollectionSubtypeSmartAlbumAnimated PHOTOS_AVAILABLE_IOS_TVOS(11_0, 11_0) = 214,
    PHAssetCollectionSubtypeSmartAlbumLongExposures PHOTOS_AVAILABLE_IOS_TVOS(11_0, 11_0) = 215,
    // Used for fetching, if you don't care about the exact subtype
    PHAssetCollectionSubtypeAny = NSIntegerMax
} PHOTOS_ENUM_AVAILABLE_IOS_TVOS(8_0, 10_0);

獲取相冊內照片

獲取到我們的相冊之後,我們接下來的工作就是要將相冊內的照片,視頻等數據顯示在我們的網格視圖中,但是如果直接用原圖來做顯示就顯得極不恰當,Apple提供的PhotoKit框架爲我們提供瞭解決方案。

獲取相冊內所有照片的縮略圖
- (void)requestThumbnailImageWithSize:(PHAsset *)asset size:(CGSize)size
           completion:(void(^)(UIImage *result, NSDictionary *info))completion{
    PHCachingImageManager *phCachingImageManager = [[PHCachingImageManager alloc] init];
    PHImageRequestOptions *imageRequestOptions = [[PHImageRequestOptions alloc] init];
    imageRequestOptions.networkAccessAllowed = YES; // 允許訪問網絡
    imageRequestOptions.deliveryMode = PHImageRequestOptionsDeliveryModeOpportunistic;
    imageRequestOptions.synchronous = true;
    imageRequestOptions.resizeMode = PHImageRequestOptionsResizeModeFast;
    
    [phCachingImageManager requestImageForAsset:asset targetSize:size contentMode:PHImageContentModeAspectFill options:imageRequestOptions resultHandler:^(UIImage * _Nullable result, NSDictionary * _Nullable info) {
        if(completion){
            completion(result, info);
        }
    }];
}
獲取預覽圖
- (void)requestPreviewImageWithCompletion:(PHAsset *)asset completion:(void(^)(UIImage *result, NSDictionary *info))completion{
    
    PHCachingImageManager *phCachingImageManager = [[PHCachingImageManager alloc] init];
    
    PHImageRequestOptions *imageRequestOptions = [[PHImageRequestOptions alloc] init];
    imageRequestOptions.networkAccessAllowed = YES; 
    imageRequestOptions.deliveryMode = PHImageRequestOptionsDeliveryModeOpportunistic;
    imageRequestOptions.synchronous = true;
    imageRequestOptions.resizeMode = PHImageRequestOptionsResizeModeFast;
    
    [phCachingImageManager requestImageForAsset:asset targetSize:CGSizeMake(kScreenWidth, kScreenHeight) contentMode:PHImageContentModeAspectFill options:imageRequestOptions resultHandler:^(UIImage * _Nullable result, NSDictionary * _Nullable info) {
        if(completion){
            completion(result, info);
        }
    }];
}
獲取原圖
- (void)requestOriginImageWithCompletion:(PHAsset *)asset completion:(void (^)(UIImage *result, NSDictionary<NSString *, id> *info))completion withProgressHandler:(PHAssetImageProgressHandler)phProgressHandler{
    
    PHCachingImageManager *phCachingImageManager = [[PHCachingImageManager alloc] init];
    
    PHImageRequestOptions *imageRequestOptions = [[PHImageRequestOptions alloc] init];
    imageRequestOptions.networkAccessAllowed = YES; // 允許訪問網絡
    imageRequestOptions.synchronous = true;
    imageRequestOptions.progressHandler = phProgressHandler;
    
    [phCachingImageManager requestImageForAsset:asset targetSize:PHImageManagerMaximumSize contentMode:PHImageContentModeAspectFill options:imageRequestOptions resultHandler:^(UIImage * _Nullable result, NSDictionary * _Nullable info) {
        if(completion){
            completion(result, info);
        }
    }];
}

上面的三個方法都調用了requestImageForAsset這個方法來請求圖片,該方法的參數有多個,下面依次來講解一下它們的作用:

  • asset:圖片資源
  • targetSize:需要獲取的圖片尺寸,如果給定的尺寸與原圖的尺寸比例不匹配,則下面要講的參數contentMode將確定如何調整圖像大小,如果需要返回原圖尺寸,可以傳入系統預先定義好的常量 PHImageManagerMaximumSize,表示返回原圖尺寸
  • contentMode (PHImageContentMode):裁剪方式,可傳入PHImageContentModeAspectFit,PHImageContentModeAspectFill和PHImageContentModeDefault,如果 targetSize 傳入 PHImageManagerMaximumSize,則 contentMode 無論傳入什麼值都會被視爲 PHImageContentModeDefault
  • options(PHImageRequestOptions):PHImageRequestOptions 中包含了一系列控制請求圖像的屬性分別如下:
  1. isNetworkAccessAllowed:參數控制是否允許網絡請求;
  2. deliveryMode:用於控制請求的圖片質量;PHImageRequestOptionsDeliveryModeOpportunistic: 如果是異步狀態,圖片會多次返回並且質量越來越高;PHImageRequestOptionsDeliveryModeHighQualityFormat:高清格式; PHImageRequestOptionsDeliveryModeFastFormat 加載反應速度最快的格式;
  3. synchronous:控制是否爲同步請求,如果是同步則照片只返回一次;
  4. resizeMode:屬性控制圖像的剪裁;PHImageRequestOptionsResizeModeNone: 無裁剪;PHImageRequestOptionsResizeModeFast: 加載方式更快; PHImageRequestOptionsResizeModeExact: 返回與targetSize 保持一致的圖片尺寸(如果normalizedCropRect被賦值則必須要設置);
  5. normalizedCropRect:裁剪區域設置;
  6. progressHandler:這是一個回調block,當圖像需要從 iCloud 下載時,這個 block 會被自動調用,block 中會返回圖像下載的進度、圖像的信息、出錯信息.如果需要更新UI則需要將progressHandler放到主線程上執行;
  • resultHandler((UIImage *__nullable result, NSDictionary *__nullable info)):求結束後被調用的 block,返回一個包含資源對於圖像的 UIImage 和包含圖像信息的一個 Dictionary;

當然,還有請求 livephoto 和請求video的方法,這裏就不做篇幅去細說了,在接下來的文章中我們會講到,感興趣的可以先去了解一下它們的接口。

livephoto
- (PHImageRequestID)requestLivePhotoForAsset:(PHAsset *)asset targetSize:(CGSize)targetSize contentMode:(PHImageContentMode)contentMode options:(nullable PHLivePhotoRequestOptions *)options resultHandler:(void (^)(PHLivePhoto *__nullable livePhoto, NSDictionary *__nullable info))resultHandler PHOTOS_AVAILABLE_IOS_TVOS(9_1, 10_0);
video
- (PHImageRequestID)requestPlayerItemForVideo:(PHAsset *)asset options:(nullable PHVideoRequestOptions *)options resultHandler:(void (^)(AVPlayerItem *__nullable playerItem, NSDictionary *__nullable info))resultHandler;

小結

本篇文章就跟大家簡單的介紹了一下PhotoKit幾個常用對象的概念以及API的調用,在下篇文章中,我會繼續給大家帶來利用PhotoKit打造專屬自己的Imagepicker的內容,最後跟大家總結一下開發中需要注意的地方:

  1. 拉取相冊之前,要先請求用戶權限;
[PHPhotoLibrary requestAuthorization:^(PHAuthorizationStatus status) {
        if(status == PHAuthorizationStatusAuthorized){
            NSLog(@"User has authorized this application to access photos data.");
        }else if(status == PHAuthorizationStatusDenied){
            NSLog(@"User has explicitly denied this application access to photos data.");
        }else if(status == PHAuthorizationStatusRestricted){
            NSLog(@"This application is not authorized to access photo data.");
        }else if(status == PHAuthorizationStatusNotDetermined){
            NSLog(@"User has not yet made a choice with regards to this application");
        }
    }];
  1. 請使用PHCachingImageManager對象來替換PHImageManager對象來拉取資源;由於需要經常使用PHImageCachingManager來獲取圖片,所以需要將PHImageCachingManager封裝成一個單例來調用,避免開銷過大;
  2. 如果PHImageRequestOptions設置爲異步,requestImageForAsset 對象調用 requestImageForAsset函數後,回調的 block返回一個包含資源對於圖像的 UIImage 和包含圖像信息的一個 Dictionary,在整個請求的週期中,這個 block 可能會被多次調用;
  3. 獲取圖片時儘量獲取預覽圖,不要直接顯示原件,建議獲取與設備屏幕同樣大小的圖像;

如果你覺得我寫的不錯,請關注我的微信公衆號:HelloWorld沈傑,您的支持是我創作的動力:

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