1. 程式人生 > >iOS 圖片選擇打造專屬於自己的 ImagePicker(1)

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沈傑,您的支援是我創作的動力: