圖片視頻管理 AssetsLibrary
在ios8之前 ,只能使用AssetsLibrary 來訪問設(shè)備的照片庫 跃洛。
ios8之后际看,蘋果提供了PhotoKit 框架 。
需要注意,在ios中 厂抖,照片庫中既包含 圖片 ,也包含 視頻 。
--
AssetsLibrary 組成介紹
- AssetsLibrary: 代表整個設(shè)備中的資源庫(照片庫)杈女,通過
- AssetsLibrary 可以獲取和包括設(shè)備中的照片和視頻
- ALAssetsGroup: 映射照片庫中的一個相冊,通過
- ALAssetsGroup 可以獲取某個相冊的信息吊圾,相冊下的資源碧信,同時也可以對某個相冊添加資源。
- ALAsset: 映射照片庫中的一個照片或視頻街夭,通過 ALAsset 可以獲取某個照片或視頻的詳細信息砰碴,或者保存照片和視頻。
- ALAssetRepresentation: ALAssetRepresentation 是對 ALAsset 的封裝(但不是其子類)板丽,可以更方便地獲取 ALAsset 中的資源信息呈枉,每個 ALAsset 都有至少有一個 ALAssetRepresentation 對象,可以通過 defaultRepresentation 獲取埃碱。而例如使用系統(tǒng)相機應(yīng)用拍攝的 RAW + JPEG 照片猖辫,則會有兩個 ALAssetRepresentation,一個封裝了照片的 RAW 信息砚殿,另一個則封裝了照片的 JPEG 信息啃憎。
主要需要掌握的方法就是從相冊加載圖片
- 首先是要檢查 App 是否有照片操作授權(quán):
NSString *tipTextWhenNoPhotosAuthorization; // 提示語
// 獲取當(dāng)前應(yīng)用對照片的訪問授權(quán)狀態(tài)
ALAuthorizationStatus authorizationStatus = [ALAssetsLibrary authorizationStatus];
// 如果沒有獲取訪問授權(quán),或者訪問授權(quán)狀態(tài)已經(jīng)被明確禁止似炎,則顯示提示語辛萍,引導(dǎo)用戶開啟授權(quán)
if (authorizationStatus == ALAuthorizationStatusRestricted || authorizationStatus == ALAuthorizationStatusDenied) {
NSDictionary *mainInfoDictionary = [[NSBundle mainBundle] infoDictionary];
NSString *appName = [mainInfoDictionary objectForKey:@"CFBundleDisplayName"];
tipTextWhenNoPhotosAuthorization = [NSString stringWithFormat:@"請在設(shè)備的\"設(shè)置-隱私-照片\"選項中,允許%@訪問你的手機相冊", appName];
// 展示提示語
}
- 獲取相冊列表
_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) {
// 把相冊儲存到數(shù)組中羡藐,方便后面展示相冊時使用
[_albumsArray addObject:group];
}
} else {
if ([_albumsArray count] > 0) {
// 把所有的相冊儲存完畢贩毕,可以展示相冊列表
} else {
// 沒有任何有資源的相冊,輸出提示
}
}
} failureBlock:^(NSError *error) {
NSLog(@"Asset group not found!\n");
}];
這里需要的主意
1 iOS 中允許相冊為空仆嗦,即相冊中沒有任何資源辉阶,如果不希望獲取空相冊,則需要像上面的代碼中那樣手動過濾
2 ALAssetsGroup 有一個 setAssetsFilter 的方法,可以傳入一個過濾器谆甜,控制只獲取相冊中的照片或只獲取視頻垃僚。一旦設(shè)置過濾,ALAssetsGroup 中資源列表和資源數(shù)量的獲取也會被自動更新规辱。
3 整個 AssetsLibrary 中對相冊冈在、資源的獲取和保存都是使用異步處理(Asynchronous),這是考慮到資源文件體積相當(dāng)比較大(還可能很大)按摘。例如上面的遍歷相冊操作包券,相冊的結(jié)果使用 block 輸出,如果相冊遍歷完畢炫贤,則最后一次輸出的 block 中的 group 參數(shù)值為 nil溅固。而 stop 參數(shù)則是用于手工停止遍歷,只要把 *stop 置 YES兰珍,則會停止下一次的遍歷侍郭。關(guān)于這一點常常會引起誤會,所以需要注意掠河。
- 接下來是獲取相冊中的資源:
_imagesAssetArray = [[NSMutableArray alloc] init];
[assetsGroup enumerateAssetsWithOptions:NSEnumerationReverse usingBlock:^(ALAsset *result, NSUInteger index, BOOL *stop) {
if (result) {
[_imagesAssetArray addObject:result];
} else {
// result 為 nil亮元,即遍歷相片或視頻完畢,可以展示資源列表
}
}];
跟遍歷相冊的過程類似唠摹,遍歷相片也是使用一系列的異步方法爆捞,其中上面的方法所輸出的 block 中,除了 result 參數(shù)表示資源信息勾拉,stop 用于手工停止遍歷外煮甥,還提供了一個 index 參數(shù),這個參數(shù)表示資源的索引藕赞。一般來說成肘,展示資源列表都會使用縮略圖(result.thumbnail),因此即使資源很多斧蜕,遍歷資源的速度也會相當(dāng)快双霍。但如果確實需要加載資源的高清圖或者其他耗時的處理,則可以利用上面的 index 參數(shù)和 stop 參數(shù)做一個分段拉取資源批销。例如:
NSUInteger _targetIndex; // index 目標(biāo)值洒闸,拉取資源直到這個值就手工停止拉取
NSUInteger _currentIndex; // 當(dāng)前 index,每次拉取資源時從這個值開始
_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
- 最后是獲取圖片的詳情
// 獲取資源圖片的詳細資源信息,其中 imageAsset 是某個資源的 ALAsset 對象
ALAssetRepresentation *representation = [imageAsset defaultRepresentation];
// 獲取資源圖片的 fullScreenImage
UIImage *contentImage = [UIImage imageWithCGImage:[representation fullScreenImage]];
對于一個 ALAssetRepresentation,里面包含了圖片的多個版本布朦。最常用的是 fullResolutionImage 和 fullScreenImage囤萤。fullResolutionImage 是圖片的原圖,通過 fullResolutionImage 獲取的圖片沒有任何處理是趴,包括通過系統(tǒng)相冊中“編輯”功能處理后的信息也沒有被包含其中涛舍,因此需要展示“編輯”功能處理后的信息,使用 fullResolutionImage 就比較不方便唆途,另外 fullResolutionImage 的拉取也會比較慢富雅,在多張 fullResolutionImage 中切換時能明顯感覺到圖片的加載過程。因此這里建議獲取圖片的 fullScreenImage肛搬,它是圖片的全屏圖版本没佑,這個版本包含了通過系統(tǒng)相冊中“編輯”功能處理后的信息,同時也是一張縮略圖温赔,但圖片的失真很少蛤奢,缺點是圖片的尺寸是一個適應(yīng)屏幕大小的版本,因此展示圖片時需要作出額外處理陶贼,但考慮到加載速度非称》罚快的原因(在多張圖片之間切換感受不到圖片加載耗時),仍建議使用 fullScreenImage拜秧。
系統(tǒng)相冊的處理過程大概也是如上痹屹,可以看出,在整個過程中并沒有使用到圖片的 fullResolutionImage枉氮,從相冊列表展示到最終查看資源痢掠,都是使用縮略圖,這也是 iOS 相冊加載快的一個重要原因嘲恍。
--
補充
獲取原圖
CGImageRef fullResolutionImageRef = [[(ALAsset *)asset defaultRepresentation] fullResolutionImage];
// // 通過 fullResolutionImage 獲取到的的高清圖實際上并不帶上在照片應(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淹辞,同時把圖片的 orientation 也補充上去
resultImage = [UIImage imageWithCGImage:fullResolutionImageRef
scale:[[asset defaultRepresentation] scale]
orientation:(UIImageOrientation)[[asset defaultRepresentation] orientation]];
帶一個block參數(shù) 作為輸入值 ,函數(shù)之行到block 俘侠。就會調(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 之行方法爷速,會調(diào)用block (這個block 需要一個image輸入值)央星。
manger 就是 用一個image 來調(diào)用block 。最后block 就回調(diào)他自己的代碼塊 { resultImage = image; }];
實現(xiàn)了image 從 manger 到其它類的 異步傳遞 惫东。
相冊管理 PhotoKit
- PHAsset: 代表照片庫中的一個資源莉给,跟 ALAsset 類似毙石,通過 PHAsset 可以獲取和保存資源
- PHFetchOptions: 獲取資源時的參數(shù),可以傳 nil颓遏,即使用系統(tǒng)默認值
- PHFetchResult: 表示一系列的資源集合徐矩,也可以是相冊的集合
- PHAssetCollection: 表示一個相冊或者一個時刻,或者是一個「智能相冊(系統(tǒng)提供的特定的一系列相冊叁幢,例如:最近刪除滤灯,視頻列表,收藏等等)
- PHImageManager: 用于處理資源的加載曼玩,加載圖片的過程帶有緩存處理鳞骤,可以通過傳入一個 PHImageRequestOptions 控制資源的輸出尺寸等規(guī)格
- PHImageRequestOptions: 如上面所說,控制加載圖片時的一系列參數(shù)
// 列出所有相冊智能相冊
PHFetchResult *smartAlbums = [PHAssetCollection fetchAssetCollectionsWithType:PHAssetCollectionTypeSmartAlbum subtype:PHAssetCollectionSubtypeAlbumRegular options:nil];
// 列出所有用戶創(chuàng)建的相冊
PHFetchResult *topLevelUserCollections = [PHCollectionList fetchTopLevelUserCollectionsWithOptions:nil];
// 獲取所有資源的集合黍判,并按資源的創(chuàng)建時間排序
PHFetchOptions *options = [[PHFetchOptions alloc] init];
options.sortDescriptors = @[[NSSortDescriptor sortDescriptorWithKey:@"creationDate" ascending:YES]];
PHFetchResult *assetsFetchResults = [PHAsset fetchAssetsWithOptions:options];
// 在資源的集合中獲取第一個集合豫尽,并獲取其中的圖片
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ù)样悟,無論是相冊拂募,還是資源,本質(zhì)上都是使用枚舉的方式窟她,遍歷照片庫取得相應(yīng)的數(shù)據(jù)陈症。而 PhotoKit 則是通過傳入?yún)?shù),直接獲取相應(yīng)的數(shù)據(jù)震糖,因而效率會提高不少录肯。
- 在 AssetsLibrary 中,相冊和資源是對應(yīng)不同的對象(ALAssetGroup 和 ALAsset)吊说,因此獲取相冊和獲取資源是兩個完全沒有關(guān)聯(lián)的接口论咏。而 PhotoKit 中則有 PHFetchResult 這個可以統(tǒng)一儲存相冊或資源的對象,因此處理相冊和資源時也會比較方便颁井。
- PhotoKit 返回資源結(jié)果時厅贪,同時返回了資源的元數(shù)據(jù),獲取元數(shù)據(jù)在 AssetsLibrary 中是很難辦到的一件事雅宾。同時通過 PHAsset养涮,開發(fā)者還能直接獲取資源是否被收藏(favorite)和隱藏(hidden),拍攝圖片時是否開啟了 HDR 或全景模式眉抬,甚至能通過一張連拍圖片獲取到連拍圖片中的其他圖片贯吓。這也是文章開頭說的,PhotoKit 能更好地與設(shè)備照片庫接入的一個重要因素蜀变。