前言
蘋果在iOS14繼續(xù)加強(qiáng)了對(duì)用戶隱私的保護(hù),有時(shí)需求只是想選擇一張相冊(cè)中的圖片,但是需要對(duì)App開發(fā)整個(gè)照片庫(kù)的權(quán)限,一些私密照片也可以被App讀取到,這樣很不合理!因此iOS14中對(duì)相冊(cè)權(quán)限新增了 "Limited Photo Library Access" 模式,這樣用戶可以控制App允許訪問(wèn)的照片。下面簡(jiǎn)單介紹下如何適配iOS14相冊(cè)新增的功能,以及如何使用Photos框架自定義相冊(cè)頁(yè)面。文中的API使用可以參考Demo
PHPhotoLibrary新增API
PHPhotoLibrary用于獲取查看相冊(cè)權(quán)限,處理相冊(cè)變化,注冊(cè)監(jiān)聽相冊(cè)變化,監(jiān)聽用戶添加/刪除了哪些照片。
使用相冊(cè)權(quán)限,必須在工程的info.plist文件中添加NSPhotoLibraryUsageDescription,否則會(huì)啟動(dòng)崩潰。
- 權(quán)限枚舉
typedef NS_ENUM(NSInteger, PHAuthorizationStatus) {
PHAuthorizationStatusNotDetermined = 0, // 用戶未作出選擇
PHAuthorizationStatusRestricted, // 此App無(wú)權(quán)限訪問(wèn)照片數(shù)據(jù)
PHAuthorizationStatusDenied, // 用戶已明確拒絕此應(yīng)用程序訪問(wèn)照片數(shù)據(jù)
PHAuthorizationStatusAuthorized, // 用戶已授權(quán)此應(yīng)用程序訪問(wèn)照片數(shù)據(jù)
PHAuthorizationStatusLimited API_AVAILABLE(ios(14)), // 用戶已授權(quán)此應(yīng)用程序進(jìn)行有限照片庫(kù)訪問(wèn)
};
- 權(quán)限等級(jí)枚舉
typedef NS_ENUM(NSInteger, PHAccessLevel) {
PHAccessLevelAddOnly = 1, // 僅允許添加
PHAccessLevelReadWrite = 2, // 讀寫
} API_AVAILABLE(macos(11.0), ios(14), tvos(14));
- 權(quán)限獲取
新權(quán)限獲取:增加了權(quán)限等級(jí)
// 獲取指定等級(jí)的權(quán)限
+ (PHAuthorizationStatus)authorizationStatusForAccessLevel:(PHAccessLevel)accessLevel API_AVAILABLE(macosx(11.0), ios(14), tvos(14));
// 請(qǐng)求指定等級(jí)的權(quán)限
+ (void)requestAuthorizationForAccessLevel:(PHAccessLevel)accessLevel handler:(void(^)(PHAuthorizationStatus status))handler API_AVAILABLE(macosx(11.0), ios(14), tvos(14)) NS_SWIFT_ASYNC(2);
舊權(quán)限獲取:在iOS14中已經(jīng)廢棄,建議使用上面的新API
+ (PHAuthorizationStatus)authorizationStatus API_DEPRECATED_WITH_REPLACEMENT("+authorizationStatusForAccessLevel:", ios(8, API_TO_BE_DEPRECATED), macos(10.13, API_TO_BE_DEPRECATED), tvos(10, API_TO_BE_DEPRECATED));
+ (void)requestAuthorization:(void(^)(PHAuthorizationStatus status))handler API_DEPRECATED_WITH_REPLACEMENT("+requestAuthorizationForAccessLevel:handler:", ios(8, API_TO_BE_DEPRECATED), macos(10.13, API_TO_BE_DEPRECATED), tvos(10, API_TO_BE_DEPRECATED));
注意:如果仍使用舊的API未適配iOS14新特性,這時(shí)獲取相冊(cè)權(quán)限狀態(tài),就算在Limited 模式下也會(huì)返回Authorized
新增 PHPickerController
iOS 14 中系統(tǒng)新增了一個(gè)圖片選擇器PHPicker ( iOS14 以上使用),官方建議使用 PHPicker 來(lái)替代原有的 UIImagePickerController ( iOS14 以下使用)進(jìn)行圖片選擇 。UIImagePickerController 只能選中一張圖片已經(jīng)不符合需求了,將逐漸被廢棄替換。
- 使用PHPickerController

- 使用 PHPickerConfiguration 配置 PHPicker,鍵 selectionLimit 設(shè)置為 n 表示最多可選中 n 張圖片,0 為 maximum,默認(rèn)值為1;使用 filter 設(shè)置想要的相冊(cè)資源類型,包括 imagesFilter、videosFilter、livePhotosFilter,亦可以設(shè)置為數(shù)組@[ videoFilter , livePhotosFilter ]顯示多種類型.
- 設(shè)置 PHPickerViewControllerDelegate 代理,接收選中照片后的回調(diào);
- 在代理回調(diào) piscker:didFinishPicking: 中處理返回結(jié)果 PHPickerResult;
- PHPickerController的優(yōu)點(diǎn)
PHPicker 使用的獨(dú)立的進(jìn)程獲取相冊(cè)數(shù)據(jù),所以不會(huì)影響App的性能,解決了之前自定義相冊(cè),圖片吃內(nèi)存的問(wèn)題。
因?yàn)?PHPicker 使用的系統(tǒng)獨(dú)立進(jìn)程,當(dāng)用戶選中所選照片時(shí),也只會(huì)回調(diào)相應(yīng)照片的圖片信息,所以不需要App申請(qǐng)權(quán)限,也不受新增的限制模式的影響。換句話說(shuō),如果只使用了 PHPicker ,則完成不需要在代碼里面申請(qǐng)權(quán)限,info.plist 文件里面也不需要申請(qǐng)權(quán)限,直接使用即可。
支持多選。
支持按照 image,video,livePhotos 類型進(jìn)行選擇。
支持搜索,頁(yè)面上有搜索框。
- PHPickerController的缺點(diǎn)
- 不支持選中圖片的編輯,例如選中后裁剪成正方形,需要自定義實(shí)現(xiàn)了;
- 不能直接獲取到 PHAsset ,如果想要獲取,則需要獲取用戶權(quán)限,使用戶授權(quán)。
- PHPickerController 代碼 Demo 演示
#import <PhotosUI/PhotosUI.h> //頭文件
- (IBAction)openPhotoLibrary:(id)sender {
PHPickerConfiguration *config = [[PHPickerConfiguration alloc] init];
config.selectionLimit = 1;
config.filter = [PHPickerFilter imagesFilter];
PHPickerViewController *pickVC = [[PHPickerViewController alloc] initWithConfiguration:config];
pickVC.delegate = self;
[self presentViewController:pickVC animated:YES completion:nil];
}
- (void)picker:(PHPickerViewController *)picker didFinishPicking:(NSArray<PHPickerResult *> *)results {
if (results == nil || results.count <= 0) {
NSLog(@"選擇照片失敗");
} else {
PHPickerResult *result = [results firstObject];
if ([result.itemProvider canLoadObjectOfClass:[UIImage class]]) {
__weak __typeof(self)weakSelf = self;
[result.itemProvider loadObjectOfClass:[UIImage class] completionHandler:^(__kindof id<NSItemProviderReading> _Nullable object, NSError * _Nullable error) {
if (!error) {
UIImage *tmpImage = (UIImage *)object;
NSLog(@"選擇照片成功");
} else {
NSLog(@"選擇照片失敗");
}
}];
} else {
NSLog(@"選擇照片失敗");
}
}
[picker dismissViewControllerAnimated:YES completion:nil];
}
更多詳細(xì)內(nèi)如可以參考:WWDC視頻: Meet the new Photos picker
Photos 框架使用
Photos 框架是iOS 8之后用于替代AssetsLibrary的一個(gè)現(xiàn)代化框架,幾年以來(lái),相機(jī)應(yīng)用與照片應(yīng)用發(fā)生了顯著的變化,增加了許多新特性,Photos可以獲取相冊(cè)中的所有圖片和視頻資源,包括iCloud Photo library 和 Live Photos,并且能夠異步獲取并緩存縮略圖和原圖。主要用來(lái)開發(fā)自定義相冊(cè)、對(duì)資源進(jìn)行處理等能力。
資源
@interface PHObject : NSObject <NSCopying>
// Returns an identifier which persistently identifies the object on a given device
@property (nonatomic, copy, readonly) NSString *localIdentifier; //標(biāo)識(shí)資源的唯一標(biāo)識(shí)符
@end
相冊(cè)中有兩種資源,分別是PHAsset 和 PHCollection,都繼承自PHObject。PHAsset代表一個(gè)相冊(cè)中的文件,這個(gè)文件可以是視頻、音頻或照片,我們可以通過(guò)PHAsset來(lái)獲取這個(gè)文件相應(yīng)的元信息以及標(biāo)識(shí)符localIdentifier,包括定位、創(chuàng)建時(shí)間、名稱等等。而PHCollection則是一個(gè)集合,PHCollection有兩個(gè)子類分別是PHAssetCollection和PHCollectionList,其中PHAssetCollection代表一個(gè)相冊(cè),也就是PHAsset的集合。PHCollectionList代表一個(gè)自身的集合,也就是PHCollection的集合,通常用來(lái)獲取相冊(cè)列表。

PHAsset
PHAsset可以獲取文件的元數(shù)據(jù)以及描述符,不包含資源本身的內(nèi)容,所以對(duì)內(nèi)存的占用較小。如果需要獲取內(nèi)容本身,請(qǐng)參考[獲取資源內(nèi)容章節(jié)]。它有如下幾個(gè)重要屬性:
//資源類型
typedef NS_ENUM(NSInteger, PHAssetMediaType) {
PHAssetMediaTypeUnknown = 0,
PHAssetMediaTypeImage = 1, //圖片
PHAssetMediaTypeVideo = 2, //視頻
PHAssetMediaTypeAudio = 3, //音頻
};
//資源子類型
typedef NS_OPTIONS(NSUInteger, PHAssetMediaSubtype) {
PHAssetMediaSubtypeNone = 0,
// Photo subtypes
PHAssetMediaSubtypePhotoPanorama = (1UL << 0), //全景圖(Panorama)
PHAssetMediaSubtypePhotoHDR = (1UL << 1), //HDR圖片
PHAssetMediaSubtypePhotoScreenshot API_AVAILABLE(ios(9)) = (1UL << 2), //屏幕截圖
PHAssetMediaSubtypePhotoLive API_AVAILABLE(ios(9.1)) = (1UL << 3), //livePhoto
PHAssetMediaSubtypePhotoDepthEffect API_AVAILABLE(macos(10.12.2), ios(10.2), tvos(10.1)) = (1UL << 4), //人像景深模式
// Video subtypes
PHAssetMediaSubtypeVideoStreamed = (1UL << 16),
PHAssetMediaSubtypeVideoHighFrameRate = (1UL << 17),
PHAssetMediaSubtypeVideoTimelapse = (1UL << 18),
PHAssetMediaSubtypeVideoCinematic API_AVAILABLE(macos(12), ios(15), tvos(15)) = (1UL << 21),
};
//資源來(lái)源類型
typedef NS_OPTIONS(NSUInteger, PHAssetSourceType) {
PHAssetSourceTypeNone = 0,
PHAssetSourceTypeUserLibrary = (1UL << 0), //用戶相冊(cè)
PHAssetSourceTypeCloudShared = (1UL << 1), //iCloud
PHAssetSourceTypeiTunesSynced = (1UL << 2), //iTunes同步
} API_AVAILABLE(ios(9));
@property (nonatomic, assign, readonly) PHAssetMediaType mediaType; //資源類型,圖片或者音頻或視頻
@property (nonatomic, assign, readonly) PHAssetMediaSubtype mediaSubtypes; //資源子類型
@property (nonatomic, assign, readonly) NSUInteger pixelWidth; //像素寬
@property (nonatomic, assign, readonly) NSUInteger pixelHeight; //像素高
@property (nonatomic, strong, readonly, nullable) NSDate *creationDate; //創(chuàng)建時(shí)間
@property (nonatomic, strong, readonly, nullable) NSDate *modificationDate; //修改時(shí)間
@property (nonatomic, strong, readonly, nullable) CLLocation *location; //圖片位置
@property (nonatomic, assign, readonly) NSTimeInterval duration; //視頻時(shí)長(zhǎng)
@property (nonatomic, assign, readonly, getter=isHidden) BOOL hidden; //資源是否被用戶標(biāo)記為"隱藏"
@property (nonatomic, assign, readonly, getter=isFavorite) BOOL favorite; //資源是否被用戶標(biāo)記資源為"收藏"
@property (nonatomic, assign, readonly) PHAssetSourceType sourceType; //資源來(lái)源類型,可以來(lái)源自用戶相冊(cè)、iCloud、iTunes同步
//連拍模式相關(guān),
@property (nonatomic, strong, readonly, nullable) NSString *burstIdentifier;//在連拍模式下拍攝的一組照片具有同一個(gè)burstIdentifier作為標(biāo)識(shí),如果想要獲取連拍照片中的剩余的其他照片,可以通過(guò)將這個(gè)值傳入 fetchAssetsWithBurstIdentifier(...) 方法來(lái)獲取
@property (nonatomic, assign, readonly) PHAssetBurstSelectionType burstSelectionTypes;
@property (nonatomic, assign, readonly) BOOL representsBurst; //標(biāo)識(shí)這個(gè)資源是一系列連拍照片中的代表照片 (多張照片是在用戶按住快門時(shí)拍攝的)。
PHAssetCollection
PHAssetCollection是一組有序的資源集合,包括相冊(cè)、moments、智能相冊(cè)以及共享照片流.它的重要屬性如下:
typedef NS_ENUM(NSInteger, PHAssetCollectionType) {
PHAssetCollectionTypeAlbum = 1, //從 iTunes 同步來(lái)的相冊(cè),以及用戶在 Photos 中自己建立的相冊(cè)
PHAssetCollectionTypeSmartAlbum = 2, //系統(tǒng)生成的智能相冊(cè),eg:人物、地點(diǎn)等
PHAssetCollectionTypeMoment API_DEPRECATED("Will be removed in a future release", ios(8, 13), tvos(10, 13)) API_UNAVAILABLE(macos) = 3,
};
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 API_AVAILABLE(ios(9)) = 210,
PHAssetCollectionSubtypeSmartAlbumScreenshots API_AVAILABLE(ios(9)) = 211,
PHAssetCollectionSubtypeSmartAlbumDepthEffect API_AVAILABLE(macos(10.13), ios(10.2), tvos(10.1)) = 212,
PHAssetCollectionSubtypeSmartAlbumLivePhotos API_AVAILABLE(macos(10.13), ios(10.3), tvos(10.2)) = 213,
PHAssetCollectionSubtypeSmartAlbumAnimated API_AVAILABLE(macos(10.15), ios(11), tvos(11)) = 214,
PHAssetCollectionSubtypeSmartAlbumLongExposures API_AVAILABLE(macos(10.15), ios(11), tvos(11)) = 215,
PHAssetCollectionSubtypeSmartAlbumUnableToUpload API_AVAILABLE(macos(10.15), ios(13), tvos(13)) = 216,
PHAssetCollectionSubtypeSmartAlbumRAW API_AVAILABLE(macos(12), ios(15), tvos(15)) = 217,
PHAssetCollectionSubtypeSmartAlbumCinematic API_AVAILABLE(macos(12), ios(15), tvos(15)) = 218,
// Used for fetching, if you don't care about the exact subtype
PHAssetCollectionSubtypeAny = NSIntegerMax
};
@property (nonatomic, assign, readonly) PHAssetCollectionType assetCollectionType; //相冊(cè)類型
@property (nonatomic, assign, readonly) PHAssetCollectionSubtype assetCollectionSubtype; //相冊(cè)子類型
// These counts are just estimates; the actual count of objects returned from a fetch should be used if you care about accuracy. Returns NSNotFound if a count cannot be quickly returned.
@property (nonatomic, assign, readonly) NSUInteger estimatedAssetCount; //估算的asset數(shù)量
@property (nonatomic, strong, readonly, nullable) NSDate *startDate; //相冊(cè)的創(chuàng)建時(shí)間
@property (nonatomic, strong, readonly, nullable) NSDate *endDate; //collection內(nèi)最近一個(gè)文件的更新時(shí)間
獲取資源請(qǐng)求
- PHAsset都是通過(guò)類方法獲取對(duì)應(yīng)的資源的,具體API如下:
//獲取具體的圖片、視頻
@interface PHAsset : PHObject
//獲取一個(gè)相冊(cè)中的所有文件
+ (PHFetchResult<PHAsset *> *)fetchAssetsInAssetCollection:(PHAssetCollection *)assetCollection options:(nullable PHFetchOptions *)options;
//通過(guò)identifiers獲取文件
+ (PHFetchResult<PHAsset *> *)fetchAssetsWithLocalIdentifiers:(NSArray<NSString *> *)identifiers options:(nullable PHFetchOptions *)options; // includes hidden assets by default
+ (nullable PHFetchResult<PHAsset *> *)fetchKeyAssetsInAssetCollection:(PHAssetCollection *)assetCollection options:(nullable PHFetchOptions *)options;
//通過(guò)burstIdentifier獲取PHAsset,burstIdentifier是連拍模式才會(huì)有的id,通過(guò)該id可以獲取連拍模式的所有照片
+ (PHFetchResult<PHAsset *> *)fetchAssetsWithBurstIdentifier:(NSString *)burstIdentifier options:(nullable PHFetchOptions *)options API_AVAILABLE(macos(10.15));
// Fetches PHAssetSourceTypeUserLibrary assets by default (use includeAssetSourceTypes option to override)
+ (PHFetchResult<PHAsset *> *)fetchAssetsWithOptions:(nullable PHFetchOptions *)options API_AVAILABLE(macos(10.15));
+ (PHFetchResult<PHAsset *> *)fetchAssetsWithMediaType:(PHAssetMediaType)mediaType options:(nullable PHFetchOptions *)options API_AVAILABLE(macos(10.15));
// assetURLs are URLs retrieved from ALAsset's ALAssetPropertyAssetURL
+ (PHFetchResult<PHAsset *> *)fetchAssetsWithALAssetURLs:(NSArray<NSURL *> *)assetURLs options:(nullable PHFetchOptions *)options API_DEPRECATED("Will be removed in a future release", ios(8, 11), tvos(8, 11)) API_UNAVAILABLE(macos);
@end
- PHCollection同樣是通過(guò)類方法獲取對(duì)應(yīng)的資源的,具體API如下:
@interface PHAssetCollection : PHCollection
// Fetch asset collections of a single type matching the provided local identifiers (type is inferred from the local identifiers)
//通過(guò)LocalIdentifier和option獲取相冊(cè)
+ (PHFetchResult<PHAssetCollection *> *)fetchAssetCollectionsWithLocalIdentifiers:(NSArray<NSString *> *)identifiers options:(nullable PHFetchOptions *)options;
// Fetch asset collections of a single type and subtype provided (use PHAssetCollectionSubtypeAny to match all subtypes)
// 通過(guò)相冊(cè)類型獲取相冊(cè)
+ (PHFetchResult<PHAssetCollection *> *)fetchAssetCollectionsWithType:(PHAssetCollectionType)type subtype:(PHAssetCollectionSubtype)subtype options:(nullable PHFetchOptions *)options;
// Smart Albums are not supported, only Albums and Moments
// 檢索某個(gè)asset對(duì)象所在的集合
+ (PHFetchResult<PHAssetCollection *> *)fetchAssetCollectionsContainingAsset:(PHAsset *)asset withType:(PHAssetCollectionType)type options:(nullable PHFetchOptions *)options;
@end
PHFetchOptions
獲取資源的選項(xiàng)配置,我們可以設(shè)置獲取asset的條件,比如獲取哪種資源,如何分類等。獲取的時(shí)候,如果該參數(shù)為空,則使用系統(tǒng)的默認(rèn)值,當(dāng)我們調(diào)用如上所示方法獲取時(shí),可以直接傳nil。
@interface PHFetchOptions : NSObject <NSCopying>
// Some predicates / sorts may be suboptimal and we will log
@property (nonatomic, strong, nullable) NSPredicate *predicate; //做選擇的約束條件。比如,只獲取圖片,不獲取視頻。指定 PHAssetMediaType為image.
@property (nonatomic, strong, nullable) NSArray<NSSortDescriptor *> *sortDescriptors; //可指定字段用來(lái)對(duì)獲取結(jié)果進(jìn)行排序
// Whether hidden assets are included in fetch results. Defaults to NO
@property (nonatomic, assign) BOOL includeHiddenAssets; // 是否包含隱藏的資源
// Whether hidden burst assets are included in fetch results. Defaults to NO
@property (nonatomic, assign) BOOL includeAllBurstAssets API_AVAILABLE(macos(10.15)); //是否包含連拍的照片
// The asset source types included in the fetch results. Defaults to PHAssetSourceTypeNone.
// If set to PHAssetSourceTypeNone the asset source types included in the fetch results are inferred from the type of query performed (see PHAsset fetchAssetsWithOptions:)
@property (nonatomic, assign) PHAssetSourceType includeAssetSourceTypes API_AVAILABLE(ios(9)); //包含的資源來(lái)源類型
// Limits the maximum number of objects returned in the fetch result, a value of 0 means no limit. Defaults to 0.
@property (nonatomic, assign, readwrite) NSUInteger fetchLimit API_AVAILABLE(ios(9)); //獲取資源的數(shù)量限制
// Whether the owner of this object is interested in incremental change details for the results of this fetch (see PHChange)
// Defaults to YES
@property (nonatomic, assign) BOOL wantsIncrementalChangeDetails;
@end
PHFetchResult
When you use class methods on the
PHAsset,PHCollection,PHAssetCollection, andPHCollectionListclasses to retrieve objects, Photos provides the resulting objects in a fetch result. You access the contents of a fetch result with the same methods and conventions used by theNSArrayclass. Unlike anNSArrayobject, however, aPHFetchResultobject dynamically loads its contents from the Photos library as needed, providing optimal performance even when handling a large number of results.A fetch result provides thread-safe access to its contents. After a fetch, the fetch result’s
countvalue is constant, and all objects in the fetch result keep the samelocalIdentifiervalue. (To get updated content for a fetch, register a change observer with the sharedPHPhotoLibraryobject.)A fetch result caches its contents, keeping a batch of objects around the most recently accessed index. Because objects outside of the batch are no longer cached, accessing these objects results in refetching those objects. This process can result in changes to values previously read from those objects.
上面是蘋果官網(wǎng)對(duì)PHFetchResult的描述,翻譯過(guò)來(lái)的大致意思如下:
在使用 PHAsset、PHCollection、PHAssetCollection 和 PHCollectionList 類的類方法檢索對(duì)象時(shí),Photos 會(huì)在獲取結(jié)果中生成的對(duì)象。您可以使用與 NSArray 類相同的方法和約定來(lái)訪問(wèn)獲取結(jié)果的內(nèi)容。但與 NSArray 對(duì)象不同的是,PHFetchResult 對(duì)象會(huì)根據(jù)需要從照片庫(kù)動(dòng)態(tài)加載其內(nèi)容,即使在處理大量結(jié)果時(shí)也能提供最佳性能。
PHFetchResult提供了對(duì)其內(nèi)容的線程安全訪問(wèn)。取回結(jié)果后,取回結(jié)果的數(shù)量保持不變,取回結(jié)果中的所有對(duì)象都保持相同的 localIdentifier 值。(要獲取獲取結(jié)果的更新內(nèi)容,請(qǐng)向共享的 PHPhotoLibrary 對(duì)象注冊(cè)一個(gè)變化觀測(cè)器)。
PHFetchResult結(jié)果會(huì)緩存其內(nèi)容,在最近訪問(wèn)的索引周圍保留一批對(duì)象。由于批次之外的對(duì)象不再被緩存,訪問(wèn)這些對(duì)象會(huì)導(dǎo)致重新獲取這些對(duì)象。這一過(guò)程可能會(huì)導(dǎo)致之前從這些對(duì)象中讀取的值發(fā)生變化。
獲取資源內(nèi)容
PHImageManager
PHImageManager是一個(gè)專門請(qǐng)求圖像與視頻的類。每次請(qǐng)求完成后,會(huì)對(duì)已請(qǐng)求的圖像與視頻作緩存。當(dāng)下次使用相同的圖像與視頻時(shí),會(huì)更快的返回結(jié)果。
@interface PHImageManager : NSObject
...
+ (PHImageManager *)defaultManager; //全局單列,返回PHImageManager的實(shí)例
...
@end
PHImageManager請(qǐng)求圖片資源API
// 請(qǐng)求指定asset的圖像。
- (PHImageRequestID)requestImageForAsset:(PHAsset *)asset targetSize:(CGSize)targetSize contentMode:(PHImageContentMode)contentMode options:(nullable PHImageRequestOptions *)options resultHandler:(void (^)(UIImage *_Nullable result, NSDictionary *_Nullable info))resultHandler;
// 請(qǐng)求指定asset的圖像數(shù)據(jù)。
- (PHImageRequestID)requestImageDataAndOrientationForAsset:(PHAsset *)asset options:(nullable PHImageRequestOptions *)options resultHandler:(void (^)(NSData *_Nullable imageData, NSString *_Nullable dataUTI, CGImagePropertyOrientation orientation, NSDictionary *_Nullable info))resultHandler API_AVAILABLE(macos(10.15), ios(13), tvos(13));
// 取消圖片請(qǐng)求
- (void)cancelImageRequest:(PHImageRequestID)requestID;
這里重點(diǎn)講下第一個(gè)方法,請(qǐng)求data和請(qǐng)求image類似。
管理器會(huì)加載或生成一張圖像并通過(guò)resultHandler返回這張圖像。為了更快的返回,管理器會(huì)返回一張目標(biāo)大小或接近目標(biāo)大小的圖像給你,因?yàn)榭赡苤耙呀?jīng)緩存過(guò)該圖像或生成接近的圖像更有效率。
一般來(lái)說(shuō),回調(diào)是異步的并且可以回調(diào)多次,通過(guò)設(shè)置PHImageRequestOptions的synchronous為YES來(lái)同步回調(diào),同步時(shí)只會(huì)回調(diào)一次,且不能通過(guò)cancelImageRequest取消。
默認(rèn)情況下,第一次調(diào)用回調(diào)一般會(huì)返回一個(gè)合適的低質(zhì)量圖像作為臨時(shí)展示,然后管理器會(huì)去生成一個(gè)高質(zhì)量的圖像。當(dāng)成功生成了高質(zhì)量的圖像后,管理器再次調(diào)用resultHandler來(lái)返回這張圖像。若管理器已經(jīng)緩存了請(qǐng)求圖像的高質(zhì)量圖像,管理器只會(huì)調(diào)用一次resultHandler并直接返回高質(zhì)量圖像??赏ㄟ^(guò)resultHandler中的info中的PHImageResultIsDegradedKey判斷是否為高質(zhì)量圖像。
這個(gè)方法的行為有很多種情況,具體視PHImageRequestOptions的配置,注意不要踩坑。這里重點(diǎn)講下PHImageRequestOptions相關(guān)屬性:
@property (nonatomic, assign) PHImageRequestOptionsVersion version; //圖像的版本
typedef NS_ENUM(NSInteger, PHImageRequestOptionsVersion) {
PHImageRequestOptionsVersionCurrent = 0, //若圖像進(jìn)行了編輯,會(huì)返回編輯后的版本
PHImageRequestOptionsVersionUnadjusted, //返回沒(méi)做任何調(diào)整的原始版本
PHImageRequestOptionsVersionOriginal //返回原始版本,如果是組合格式,則將返回最高保真度格式(例如RAW表示RAW + JPG源圖像)
};
@property (nonatomic, assign) PHImageRequestOptionsDeliveryMode deliveryMode; //交付模式
typedef NS_ENUM(NSInteger, PHImageRequestOptionsDeliveryMode) {
PHImageRequestOptionsDeliveryModeOpportunistic = 0, //默認(rèn) 異步調(diào)用時(shí)可能會(huì)得到多個(gè)圖像結(jié)果,或者在同步調(diào)用時(shí)會(huì)得到一個(gè)結(jié)果
PHImageRequestOptionsDeliveryModeHighQualityFormat = 1, //只會(huì)得到一個(gè)結(jié)果,它將是所要求的或比之更好的結(jié)果
PHImageRequestOptionsDeliveryModeFastFormat = 2 //只會(huì)得到一個(gè)結(jié)果,它可能會(huì)是降級(jí)的結(jié)果
};
@property (nonatomic, assign) PHImageRequestOptionsResizeMode resizeMode; //調(diào)整大小模式,當(dāng)targetSize為PHImageManagerMaximumSize時(shí)不生效. 默認(rèn)是PHImageRequestOptionsResizeModeFast
typedef NS_ENUM(NSInteger, PHImageRequestOptionsResizeMode) {
//不調(diào)整大小
PHImageRequestOptionsResizeModeNone = 0,
//默認(rèn)
//當(dāng)源圖像為壓縮格式(即二次采樣)時(shí),會(huì)使用targetSize作為最佳解碼的大小,但交付的圖像可能大于targetSize
PHImageRequestOptionsResizeModeFast,
//與上述相同,但交付的圖像等于targetSize(必須在設(shè)置normalizedCropRect時(shí)才生效)
PHImageRequestOptionsResizeModeExact,
};
@property (nonatomic, assign) CGRect normalizedCropRect; //在原始圖像的單位坐標(biāo)中指定的裁剪矩形。默認(rèn)為CGRectZero。當(dāng)resizeMode = PHImageRequestOptionsResizeModeExact才生效。
@property (nonatomic, assign, getter=isNetworkAccessAllowed) BOOL networkAccessAllowed; //允許從iCloud下載圖像,默認(rèn)為NO??梢酝ㄟ^(guò)progressHandler監(jiān)視或取消下載
@property (nonatomic, assign, getter=isSynchronous) BOOL synchronous; //是否同步請(qǐng)求圖像,默認(rèn)為NO。YES僅返回一個(gè)結(jié)果,會(huì)阻塞線程直到請(qǐng)求成功(或失?。?。
@property (nonatomic, copy, nullable) PHAssetImageProgressHandler progressHandler; //當(dāng)從iCloud下載圖像時(shí),將會(huì)回調(diào)該handler,默認(rèn)為nil
@property (nonatomic) BOOL allowSecondaryDegradedImage API_AVAILABLE(macos(14), ios(17), tvos(17)); // 除了返回初始降級(jí)結(jié)果外,如果條件允許,還將返回另一個(gè)降級(jí)結(jié)果。
@end
然后來(lái)詳細(xì)講下,圖像獲取的其他相關(guān)參數(shù)
- asset: 要加載其圖像數(shù)據(jù)的PHAsset
- targetSize: 要返回的圖像的目標(biāo)尺寸,PHImageManagerMaximumSize表示最大尺寸(即原圖),結(jié)果不一定等于這個(gè)size,視PHImageRequestOptions的配置情況而定。
- contentMode: 圖像自適應(yīng)模式。當(dāng)圖像的寬高比與目標(biāo)尺寸不一致時(shí),通過(guò)contentMode決定該如何適應(yīng)。
typedef NS_ENUM(NSInteger, PHImageContentMode) {
//壓縮模式 圖像的長(zhǎng)邊與目標(biāo)尺寸一致,短邊則比例縮小,小于目標(biāo)尺寸。
PHImageContentModeAspectFit = 0,
//伸展模式 圖像的短邊與目標(biāo)尺寸一致,長(zhǎng)邊則比例放大,大于目標(biāo)尺寸。
PHImageContentModeAspectFill = 1,
//默認(rèn)
PHImageContentModeDefault = PHImageContentModeAspectFit
};
-
resultHandler 在當(dāng)前線程上同步或在主線程上異步調(diào)用一次或多次的回調(diào)block。
void (^)(UIImage *_Nullable result, NSDictionary *_Nullable info)- result 請(qǐng)求成功的圖像。
- info 圖像的信息,字典的key value如下
- PHImageResultIsInCloudKey ——NSNumber<Bool> 。資源在iCloud上,需要發(fā)出新請(qǐng)求(PHImageRequestOptions.networkAccessAllowed = YES)才能獲得結(jié)果
- PHImageResultIsDegradedKey ——NSNumber<Bool>。返回的圖像是降級(jí)的(僅針對(duì)異步請(qǐng)求),這意味著將獲取其他圖像,除非與此同時(shí)取消請(qǐng)求。如果本地?cái)?shù)據(jù)不可用且networkAccessAllowed不為YES,則請(qǐng)求可能會(huì)失敗。
- PHImageResultRequestIDKey ——NSNumber。請(qǐng)求的id
- PHImageCancelledKey ——NSNumber。結(jié)果不可用,因?yàn)檎?qǐng)求已被取消
- PHImageErrorKey ——NSError。獲取失敗的錯(cuò)誤
返回值PHImageRequestID,請(qǐng)求的id,可通過(guò)調(diào)用
cancelImageRequest取消請(qǐng)求,只能取消異步的請(qǐng)求。
對(duì)于視頻以及音頻,小伙伴可以自行查閱資料
PHCachingImageManager
PHCachingImageManager 是PHImageManager的子類,其能力除了父類的獲取資源能力外,還多了預(yù)先緩存圖像與視頻能力,以便請(qǐng)求的時(shí)候能更快返回。
@interface PHCachingImageManager : PHImageManager
// During fast scrolling clients should set this to improve responsiveness
@property (nonatomic, assign) BOOL allowsCachingHighQualityImages; // 默認(rèn)值為YES。如果設(shè)置為YES,圖像管理器將會(huì)準(zhǔn)備高質(zhì)量的圖像。這個(gè)選項(xiàng)將在高性能成本下提供更好的圖像。想要有更快更好的性能——比如說(shuō)用戶快速的滑動(dòng)縮略圖集合視圖的時(shí)候——設(shè)置這個(gè)屬性為NO。
// Asynchronous image preheating (aka caching), note that only image sources are cached (no crop or exact resize is ever done on them at the time of caching, only at the time of delivery when applicable).
// The options values shall exactly match the options values used in loading methods. If two or more caching requests are done on the same asset using different options or different targetSize the first
// caching request will have precedence (until it is stopped)
- (void)startCachingImagesForAssets:(NSArray<PHAsset *> *)assets targetSize:(CGSize)targetSize contentMode:(PHImageContentMode)contentMode options:(nullable PHImageRequestOptions *)options;
- (void)stopCachingImagesForAssets:(NSArray<PHAsset *> *)assets targetSize:(CGSize)targetSize contentMode:(PHImageContentMode)contentMode options:(nullable PHImageRequestOptions *)options;
- (void)stopCachingImagesForAllAssets;
@end
翻譯過(guò)來(lái):異步圖像預(yù)熱(也稱為緩存),請(qǐng)注意,只有圖像資源會(huì)被緩存(在緩存時(shí)不會(huì)進(jìn)行裁剪或精確調(diào)整大小,只有在傳遞時(shí)才會(huì)進(jìn)行)。 options必須與加載方法中使用的options完全匹配。如果對(duì)同一資源進(jìn)行兩個(gè)或更多的緩存請(qǐng)求,使用不同的選項(xiàng)或不同的目標(biāo)大小,則第一個(gè)緩存請(qǐng)求將優(yōu)先(直到停止為止)。
PHCachingImageManager 的實(shí)例最好由業(yè)務(wù)自己創(chuàng)建,而不要用父類的defaultManager,便于控制自己業(yè)務(wù)的圖片緩存&釋放。