圖片視頻管理 AssetsLibrary
在ios8之前 ,只能使用AssetsLibrary 來(lái)訪問設(shè)備的照片庫(kù) 。
ios8之后,蘋果提供了PhotoKit 框架 。
需要注意,在ios中 ,照片庫(kù)中既包含 圖片 ,也包含 視頻 。
--
AssetsLibrary 組成介紹
- AssetsLibrary: 代表整個(gè)設(shè)備中的資源庫(kù)(照片庫(kù)),通過(guò)
- AssetsLibrary 可以獲取和包括設(shè)備中的照片和視頻
- ALAssetsGroup: 映射照片庫(kù)中的一個(gè)相冊(cè),通過(guò)
- ALAssetsGroup 可以獲取某個(gè)相冊(cè)的信息,相冊(cè)下的資源,同時(shí)也可以對(duì)某個(gè)相冊(cè)添加資源。
- ALAsset: 映射照片庫(kù)中的一個(gè)照片或視頻,通過(guò) ALAsset 可以獲取某個(gè)照片或視頻的詳細(xì)信息,或者保存照片和視頻。
- ALAssetRepresentation: ALAssetRepresentation 是對(duì) ALAsset 的封裝(但不是其子類),可以更方便地獲取 ALAsset 中的資源信息,每個(gè) ALAsset 都有至少有一個(gè) ALAssetRepresentation 對(duì)象,可以通過(guò) defaultRepresentation 獲取。而例如使用系統(tǒng)相機(jī)應(yīng)用拍攝的 RAW + JPEG 照片,則會(huì)有兩個(gè) ALAssetRepresentation,一個(gè)封裝了照片的 RAW 信息,另一個(gè)則封裝了照片的 JPEG 信息。
主要需要掌握的方法就是從相冊(cè)加載圖片
- 首先是要檢查 App 是否有照片操作授權(quán):
NSString *tipTextWhenNoPhotosAuthorization; // 提示語(yǔ)
// 獲取當(dāng)前應(yīng)用對(duì)照片的訪問授權(quán)狀態(tài)
ALAuthorizationStatus authorizationStatus = [ALAssetsLibrary authorizationStatus];
// 如果沒有獲取訪問授權(quán),或者訪問授權(quán)狀態(tài)已經(jīng)被明確禁止,則顯示提示語(yǔ),引導(dǎo)用戶開啟授權(quán)
if (authorizationStatus == ALAuthorizationStatusRestricted || authorizationStatus == ALAuthorizationStatusDenied) {
NSDictionary *mainInfoDictionary = [[NSBundle mainBundle] infoDictionary];
NSString *appName = [mainInfoDictionary objectForKey:@"CFBundleDisplayName"];
tipTextWhenNoPhotosAuthorization = [NSString stringWithFormat:@"請(qǐng)?jiān)谠O(shè)備的\"設(shè)置-隱私-照片\"選項(xiàng)中,允許%@訪問你的手機(jī)相冊(cè)", appName];
// 展示提示語(yǔ)
}
- 獲取相冊(cè)列表
_assetsLibrary = [[ALAssetsLibrary alloc] init];
_albumsArray = [[NSMutableArray alloc] init];
[_assetsLibrary enumerateGroupsWithTypes:ALAssetsGroupAll usingBlock:^(ALAssetsGroup *group, BOOL *stop) {
if (group) {
[group setAssetsFilter:[ALAssetsFilter allPhotos]];
if (group.numberOfAssets > 0) {
// 把相冊(cè)儲(chǔ)存到數(shù)組中,方便后面展示相冊(cè)時(shí)使用
[_albumsArray addObject:group];
}
} else {
if ([_albumsArray count] > 0) {
// 把所有的相冊(cè)儲(chǔ)存完畢,可以展示相冊(cè)列表
} else {
// 沒有任何有資源的相冊(cè),輸出提示
}
}
} failureBlock:^(NSError *error) {
NSLog(@"Asset group not found!\n");
}];
這里需要的主意
1 iOS 中允許相冊(cè)為空,即相冊(cè)中沒有任何資源,如果不希望獲取空相冊(cè),則需要像上面的代碼中那樣手動(dòng)過(guò)濾
2 ALAssetsGroup 有一個(gè) setAssetsFilter 的方法,可以傳入一個(gè)過(guò)濾器,控制只獲取相冊(cè)中的照片或只獲取視頻。一旦設(shè)置過(guò)濾,ALAssetsGroup 中資源列表和資源數(shù)量的獲取也會(huì)被自動(dòng)更新。
3 整個(gè) AssetsLibrary 中對(duì)相冊(cè)、資源的獲取和保存都是使用異步處理(Asynchronous),這是考慮到資源文件體積相當(dāng)比較大(還可能很大)。例如上面的遍歷相冊(cè)操作,相冊(cè)的結(jié)果使用 block 輸出,如果相冊(cè)遍歷完畢,則最后一次輸出的 block 中的 group 參數(shù)值為 nil。而 stop 參數(shù)則是用于手工停止遍歷,只要把 *stop 置 YES,則會(huì)停止下一次的遍歷。關(guān)于這一點(diǎn)常常會(huì)引起誤會(huì),所以需要注意。
- 接下來(lái)是獲取相冊(cè)中的資源:
_imagesAssetArray = [[NSMutableArray alloc] init];
[assetsGroup enumerateAssetsWithOptions:NSEnumerationReverse usingBlock:^(ALAsset *result, NSUInteger index, BOOL *stop) {
if (result) {
[_imagesAssetArray addObject:result];
} else {
// result 為 nil,即遍歷相片或視頻完畢,可以展示資源列表
}
}];
跟遍歷相冊(cè)的過(guò)程類似,遍歷相片也是使用一系列的異步方法,其中上面的方法所輸出的 block 中,除了 result 參數(shù)表示資源信息,stop 用于手工停止遍歷外,還提供了一個(gè) index 參數(shù),這個(gè)參數(shù)表示資源的索引。一般來(lái)說(shuō),展示資源列表都會(huì)使用縮略圖(result.thumbnail),因此即使資源很多,遍歷資源的速度也會(huì)相當(dāng)快。但如果確實(shí)需要加載資源的高清圖或者其他耗時(shí)的處理,則可以利用上面的 index 參數(shù)和 stop 參數(shù)做一個(gè)分段拉取資源。例如:
NSUInteger _targetIndex; // index 目標(biāo)值,拉取資源直到這個(gè)值就手工停止拉取
NSUInteger _currentIndex; // 當(dāng)前 index,每次拉取資源時(shí)從這個(gè)值開始
_targetIndex = 50;
_currentIndex = 0;
- (void)loadAssetWithAssetsGroup:(assetsGroup *)assetsGroup {
[assetsGroup enumerateAssetsAtIndexes:[NSIndexSet indexSetWithIndex:_currentIndex] options:NSEnumerationReverse usingBlock:^(ALAsset *result, NSUInteger index, BOOL *stop) {
_currentIndex = index;
if (index > _targetIndex) {
// 拉取資源的索引如果比目標(biāo)值大,則停止拉取
*stop = YES;
} else {
if (result) {
[_imagesAssetArray addObject:result];
} else {
// result 為 nil,即遍歷相片或視頻完畢
}
}
}];
}
// 之前拉取的數(shù)據(jù)已經(jīng)顯示完畢,需要展示新數(shù)據(jù),重新調(diào)用 loadAssetWithAssetsGroup
- 最后是獲取圖片的詳情
// 獲取資源圖片的詳細(xì)資源信息,其中 imageAsset 是某個(gè)資源的 ALAsset 對(duì)象
ALAssetRepresentation *representation = [imageAsset defaultRepresentation];
// 獲取資源圖片的 fullScreenImage
UIImage *contentImage = [UIImage imageWithCGImage:[representation fullScreenImage]];
對(duì)于一個(gè) ALAssetRepresentation,里面包含了圖片的多個(gè)版本。最常用的是 fullResolutionImage 和 fullScreenImage。fullResolutionImage 是圖片的原圖,通過(guò) fullResolutionImage 獲取的圖片沒有任何處理,包括通過(guò)系統(tǒng)相冊(cè)中“編輯”功能處理后的信息也沒有被包含其中,因此需要展示“編輯”功能處理后的信息,使用 fullResolutionImage 就比較不方便,另外 fullResolutionImage 的拉取也會(huì)比較慢,在多張 fullResolutionImage 中切換時(shí)能明顯感覺到圖片的加載過(guò)程。因此這里建議獲取圖片的 fullScreenImage,它是圖片的全屏圖版本,這個(gè)版本包含了通過(guò)系統(tǒng)相冊(cè)中“編輯”功能處理后的信息,同時(shí)也是一張縮略圖,但圖片的失真很少,缺點(diǎn)是圖片的尺寸是一個(gè)適應(yīng)屏幕大小的版本,因此展示圖片時(shí)需要作出額外處理,但考慮到加載速度非??斓脑颍ㄔ诙鄰垐D片之間切換感受不到圖片加載耗時(shí)),仍建議使用 fullScreenImage。
系統(tǒng)相冊(cè)的處理過(guò)程大概也是如上,可以看出,在整個(gè)過(guò)程中并沒有使用到圖片的 fullResolutionImage,從相冊(cè)列表展示到最終查看資源,都是使用縮略圖,這也是 iOS 相冊(cè)加載快的一個(gè)重要原因。
--
補(bǔ)充
獲取原圖
CGImageRef fullResolutionImageRef = [[(ALAsset *)asset defaultRepresentation] fullResolutionImage];
// // 通過(guò) fullResolutionImage 獲取到的的高清圖實(shí)際上并不帶上在照片應(yīng)用中使用“編輯”處理的效果,需要額外在 AlAssetRepresentation 中獲取這些信息
NSString *adjustment = [[[(ALAsset *)asset defaultRepresentation] metadata] objectForKey:@"AdjustmentXMP"];
if (adjustment) {
// 如果有在照片應(yīng)用中使用“編輯”效果,則需要獲取這些編輯后的濾鏡,手工疊加到原圖中
NSData *xmpData = [adjustment dataUsingEncoding:NSUTF8StringEncoding];
CIImage *tempImage = [CIImage imageWithCGImage:fullResolutionImageRef];
NSError *error;
NSArray *filterArray = [CIFilter filterArrayFromSerializedXMP:xmpData
inputImageExtent:tempImage.extent
error:&error];
CIContext *context = [CIContext contextWithOptions:nil];
if (filterArray && !error) {
for (CIFilter *filter in filterArray) {
[filter setValue:tempImage forKey:kCIInputImageKey];
tempImage = [filter outputImage];
}
fullResolutionImageRef = [context createCGImage:tempImage fromRect:[tempImage extent]];
}
}
// 生成最終返回的 UIImage,同時(shí)把圖片的 orientation 也補(bǔ)充上去
resultImage = [UIImage imageWithCGImage:fullResolutionImageRef
scale:[[asset defaultRepresentation] scale]
orientation:(UIImageOrientation)[[asset defaultRepresentation] orientation]];
帶一個(gè)block參數(shù) 作為輸入值 ,函數(shù)之行到block 。就會(huì)調(diào)用block 。也就是回調(diào)block 。
`[[XMNPhotoManager sharedManager] getOriginImageWithAsset:self.asset completionBlock:^(UIImage *image){
resultImage = image;
}];
`
- (void)getOriginImageWithAsset:(id _Nonnull)asset completionBlock:(void(^_Nonnull)(UIImage * _Nullable image))completionBlock;
這段代碼就是manger 之行方法,會(huì)調(diào)用block (這個(gè)block 需要一個(gè)image輸入值)。
manger 就是 用一個(gè)image 來(lái)調(diào)用block 。最后block 就回調(diào)他自己的代碼塊 { resultImage = image; }];
實(shí)現(xiàn)了image 從 manger 到其它類的 異步傳遞 。
相冊(cè)管理 PhotoKit
- PHAsset: 代表照片庫(kù)中的一個(gè)資源,跟 ALAsset 類似,通過(guò) PHAsset 可以獲取和保存資源
- PHFetchOptions: 獲取資源時(shí)的參數(shù),可以傳 nil,即使用系統(tǒng)默認(rèn)值
- PHFetchResult: 表示一系列的資源集合,也可以是相冊(cè)的集合
- PHAssetCollection: 表示一個(gè)相冊(cè)或者一個(gè)時(shí)刻,或者是一個(gè)「智能相冊(cè)(系統(tǒng)提供的特定的一系列相冊(cè),例如:最近刪除,視頻列表,收藏等等)
- PHImageManager: 用于處理資源的加載,加載圖片的過(guò)程帶有緩存處理,可以通過(guò)傳入一個(gè) PHImageRequestOptions 控制資源的輸出尺寸等規(guī)格
- PHImageRequestOptions: 如上面所說(shuō),控制加載圖片時(shí)的一系列參數(shù)
// 列出所有相冊(cè)智能相冊(cè)
PHFetchResult *smartAlbums = [PHAssetCollection fetchAssetCollectionsWithType:PHAssetCollectionTypeSmartAlbum subtype:PHAssetCollectionSubtypeAlbumRegular options:nil];
// 列出所有用戶創(chuàng)建的相冊(cè)
PHFetchResult *topLevelUserCollections = [PHCollectionList fetchTopLevelUserCollectionsWithOptions:nil];
// 獲取所有資源的集合,并按資源的創(chuàng)建時(shí)間排序
PHFetchOptions *options = [[PHFetchOptions alloc] init];
options.sortDescriptors = @[[NSSortDescriptor sortDescriptorWithKey:@"creationDate" ascending:YES]];
PHFetchResult *assetsFetchResults = [PHAsset fetchAssetsWithOptions:options];
// 在資源的集合中獲取第一個(gè)集合,并獲取其中的圖片
PHCachingImageManager *imageManager = [[PHCachingImageManager alloc] init];
PHAsset *asset = assetsFetchResults[0];
[imageManager requestImageForAsset:asset
targetSize:SomeSize
contentMode:PHImageContentModeAspectFill
options:nil
resultHandler:^(UIImage *result, NSDictionary *info) {
// 得到一張 UIImage,展示到界面上
}];
- 從 AssetsLibrary 中獲取數(shù)據(jù),無(wú)論是相冊(cè),還是資源,本質(zhì)上都是使用枚舉的方式,遍歷照片庫(kù)取得相應(yīng)的數(shù)據(jù)。而 PhotoKit 則是通過(guò)傳入?yún)?shù),直接獲取相應(yīng)的數(shù)據(jù),因而效率會(huì)提高不少。
- 在 AssetsLibrary 中,相冊(cè)和資源是對(duì)應(yīng)不同的對(duì)象(ALAssetGroup 和 ALAsset),因此獲取相冊(cè)和獲取資源是兩個(gè)完全沒有關(guān)聯(lián)的接口。而 PhotoKit 中則有 PHFetchResult 這個(gè)可以統(tǒng)一儲(chǔ)存相冊(cè)或資源的對(duì)象,因此處理相冊(cè)和資源時(shí)也會(huì)比較方便。
- PhotoKit 返回資源結(jié)果時(shí),同時(shí)返回了資源的元數(shù)據(jù),獲取元數(shù)據(jù)在 AssetsLibrary 中是很難辦到的一件事。同時(shí)通過(guò) PHAsset,開發(fā)者還能直接獲取資源是否被收藏(favorite)和隱藏(hidden),拍攝圖片時(shí)是否開啟了 HDR 或全景模式,甚至能通過(guò)一張連拍圖片獲取到連拍圖片中的其他圖片。這也是文章開頭說(shuō)的,PhotoKit 能更好地與設(shè)備照片庫(kù)接入的一個(gè)重要因素。