PhotoKit簡介
PhotoKit 是iOS8之后新出一套比 AssetsLibrary 更完整也更高效的庫,對資源的處理跟 AssetsLibrary 也有很大的不同户矢。
先簡單介紹幾個Photos的類:
- PHAsset: 代表照片庫中的一個資源失晴,跟 ALAsset 類似,通過 PHAsset 可以獲取和保存資源
- PHFetchOptions: 獲取資源時的參數(shù),可以傳 nil模闲,即使用系統(tǒng)默認值
- PHFetchResult: 表示一系列的資源集合腾节,也可以是相冊的集合
- PHAssetCollection: 表示一個相冊或者一個時刻忘嫉,或者是一個「智能相冊(系統(tǒng)提供的特定的一系列相冊,例如:最近刪除案腺,視頻列表庆冕,收藏等等,如下圖所示)
- PHImageManager: 用于處理資源的加載劈榨,加載圖片的過程帶有緩存處理访递,可以通過傳入一個 PHImageRequestOptions 控制資源的輸出尺寸,同異步獲取同辣,是否獲取iCloud圖片等
- PHCachingImageManager: 繼承 PHImageManager 拷姿,對Photos的圖片或視頻資源提供了加載或生成預(yù)覽縮略圖和全尺寸圖片的方法,針對預(yù)處理巨量的資源進行了優(yōu)化旱函。
- PHImageRequestOptions: 如上面所說响巢,控制加載圖片時的一系列參數(shù)
PhotoKit使用
- 導(dǎo)入
#import <Photos/Photos.h>
- 在
Info.plist
添加key:
<key>NSPhotoLibraryUsageDescription</key>
<string>需要訪問您的相冊,請點擊允許</string>
- 如需要系統(tǒng)相冊名稱跟隨系統(tǒng)語言本地化陡舅,則在
Info.plist
添加key:
<key>CFBundleAllowMixedLocalizations</key>
<true/>
- 獲取相冊列表
- (void)fetchAssetCollection
{
PHFetchOptions *allPhotosOptions = [[PHFetchOptions alloc] init];
// 按創(chuàng)建時間升序
allPhotosOptions.sortDescriptors = @[[NSSortDescriptor sortDescriptorWithKey:@"creationDate" ascending:YES]];
// 獲取所有照片(按創(chuàng)建時間升序)
_allPhotos = [PHAsset fetchAssetsWithOptions:allPhotosOptions];
// 獲取所有智能相冊
_smartAlbums = [PHAssetCollection fetchAssetCollectionsWithType:PHAssetCollectionTypeSmartAlbum subtype:PHAssetCollectionSubtypeAlbumRegular options:nil];
// 獲取所有用戶創(chuàng)建相冊
_userCollections = [PHAssetCollection fetchAssetCollectionsWithType:PHAssetCollectionTypeAlbum subtype:PHAssetCollectionSubtypeSmartAlbumUserLibrary options:nil];
//_userCollections = [PHCollectionList fetchTopLevelUserCollectionsWithOptions:nil];
}
效果如下:
- 獲取具體某個相冊的所有
PHAsset
PHFetchResult<PHAsset *> *fetchResult = [PHAsset fetchAssetsInAssetCollection:collection options:nil];
- 用
PHAsset
請求圖片抵乓,得到UIImage
對象
cell.representedAssetIdentifier = asset.localIdentifier;
// targetSize 是以像素計量的,所以需要實際的 size * UIScreen.mainScreen.scale
[_imageManager requestImageForAsset:asset targetSize:thumbnailSize contentMode:PHImageContentModeAspectFill options:nil resultHandler:^(UIImage * _Nullable result, NSDictionary * _Nullable info) {
// 當(dāng) resultHandler 被調(diào)用時靶衍,cell可能已被回收灾炭,所以此處加個判斷條件
if ([cell.representedAssetIdentifier isEqualToString:asset.localIdentifier]) {
cell.thumbnailImage = result;
}
}];
此處有人肯定會有疑惑,- (PHImageRequestID)requestImageForAsset:(PHAsset *)asset targetSize:(CGSize)targetSize contentMode:(PHImageContentMode)contentMode options:(nullable PHImageRequestOptions *)options resultHandler:(void (^)(UIImage *__nullable result, NSDictionary *__nullable info))resultHandler
這個方法放在<UICollectionViewDataSource>的- (__kindof UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
中執(zhí)行颅眶,不會很卡么蜈出?(也許,相冊中照片比較少的時候可能感覺不太明顯涛酗,可以試試超過1000張的滑動效果)當(dāng)然铡原,如果僅僅是這樣偷厦,那用戶體驗太差了。
最重要的核心代碼是下面要說的燕刻,合理使用PHCachingImageManager
只泼,在滾動一系列縮略圖時,我們可以在可視區(qū)域前后維護一些數(shù)據(jù)緩存卵洗。借圖表達下:
何時執(zhí)行
Start Caching
和Stop Caching
當(dāng)然得分向上滑動和向下滑動時2種情況各做討論;所以需要在<UIScrollViewDelegate>的協(xié)議方法中根據(jù)
collectionView.contentOffset.y
判斷什么時候Start Caching
和Stop Caching
代碼如下:
#pragma mark - UIScrollViewDelegate -
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
[self updateCachedAssets];
}
- (void)updateCachedAssets
{
if (!self.isViewLoaded || self.view.window == nil) {
return;
}
// 預(yù)熱區(qū)域 preheatRect 是 可見區(qū)域 visibleRect 的兩倍高
CGRect visibleRect = CGRectMake(0.f, self.collectionView.contentOffset.y, self.collectionView.bounds.size.width, self.collectionView.bounds.size.height);
CGRect preheatRect = CGRectInset(visibleRect, 0, -0.5*visibleRect.size.height);
// 只有當(dāng)可見區(qū)域與最后一個預(yù)熱區(qū)域顯著不同時才更新
CGFloat delta = fabs(CGRectGetMidY(preheatRect) - CGRectGetMidY(previousPreheatRect));
if (delta > self.view.bounds.size.height / 3.f) {
// 計算開始緩存和停止緩存的區(qū)域
[self computeDifferenceBetweenRect:previousPreheatRect andRect:preheatRect removedHandler:^(CGRect removedRect) {
[self imageManagerStopCachingImagesWithRect:removedRect];
} addedHandler:^(CGRect addedRect) {
[self imageManagerStartCachingImagesWithRect:addedRect];
}];
previousPreheatRect = preheatRect;
}
}
計算開始緩存和停止緩存的區(qū)域
- (void)computeDifferenceBetweenRect:(CGRect)oldRect andRect:(CGRect)newRect removedHandler:(void (^)(CGRect removedRect))removedHandler addedHandler:(void (^)(CGRect addedRect))addedHandler
{
if (CGRectIntersectsRect(newRect, oldRect)) {
CGFloat oldMaxY = CGRectGetMaxY(oldRect);
CGFloat oldMinY = CGRectGetMinY(oldRect);
CGFloat newMaxY = CGRectGetMaxY(newRect);
CGFloat newMinY = CGRectGetMinY(newRect);
//添加 向下滑動時 newRect 除去與 oldRect 相交部分的區(qū)域(即:屏幕外底部的預(yù)熱區(qū)域)
if (newMaxY > oldMaxY) {
CGRect rectToAdd = CGRectMake(newRect.origin.x, oldMaxY, newRect.size.width, (newMaxY - oldMaxY));
addedHandler(rectToAdd);
}
//添加 向上滑動時 newRect 除去與 oldRect 相交部分的區(qū)域(即:屏幕外底部的預(yù)熱區(qū)域)
if (oldMinY > newMinY) {
CGRect rectToAdd = CGRectMake(newRect.origin.x, newMinY, newRect.size.width, (oldMinY - newMinY));
addedHandler(rectToAdd);
}
//移除 向上滑動時 oldRect 除去與 newRect 相交部分的區(qū)域(即:屏幕外底部的預(yù)熱區(qū)域)
if (newMaxY < oldMaxY) {
CGRect rectToRemove = CGRectMake(newRect.origin.x, newMaxY, newRect.size.width, (oldMaxY - newMaxY));
removedHandler(rectToRemove);
}
//移除 向下滑動時 oldRect 除去與 newRect 相交部分的區(qū)域(即:屏幕外頂部的預(yù)熱區(qū)域)
if (oldMinY < newMinY) {
CGRect rectToRemove = CGRectMake(newRect.origin.x, oldMinY, newRect.size.width, (newMinY - oldMinY));
removedHandler(rectToRemove);
}
}
else {
//當(dāng) oldRect 與 newRect 沒有相交區(qū)域時
addedHandler(newRect);
removedHandler(oldRect);
}
}
執(zhí)行startCachingImagesForAssets
和stopCachingImagesForAssets
- (void)imageManagerStartCachingImagesWithRect:(CGRect)rect
{
NSMutableArray<PHAsset *> *addAssets = [self indexPathsForElementsWithRect:rect];
[_imageManager startCachingImagesForAssets:addAssets targetSize:thumbnailSize contentMode:PHImageContentModeAspectFill options: nil];
}
- (void)imageManagerStopCachingImagesWithRect:(CGRect)rect
{
NSMutableArray<PHAsset *> *removeAssets = [self indexPathsForElementsWithRect:rect];
[_imageManager stopCachingImagesForAssets:removeAssets targetSize:thumbnailSize contentMode:PHImageContentModeAspectFill options:nil];
}
- (NSMutableArray<PHAsset *> *)indexPathsForElementsWithRect:(CGRect)rect
{
UICollectionViewLayout *layout = self.collectionView.collectionViewLayout;
NSArray<__kindof UICollectionViewLayoutAttributes *> *layoutAttributes = [layout layoutAttributesForElementsInRect:rect];
NSMutableArray<PHAsset *> *assets = [NSMutableArray array];
for (__kindof UICollectionViewLayoutAttributes *layoutAttr in layoutAttributes) {
NSIndexPath *indexPath = layoutAttr.indexPath;
PHAsset *asset = [_fetchResult objectAtIndex:indexPath.item];
[assets addObject:asset];
}
return assets;
}
切記:此處執(zhí)行 startCachingImagesForAssets 和 stopCachingImagesForAssets 時的參數(shù) targetSize过蹂,contentMode十绑,options 都要必須和 requestImageForAsset方法里的保持一致
本人手機里有3924張照片,跑了個demo酷勺,占用內(nèi)存最高不超過30M本橙,快速滑動也不會出現(xiàn)掉幀現(xiàn)象。Demo地址??SamplePhotosDemo