前言
上一篇文章里我们已经将图片选择器的基本功能完成了。相册里图片比较多的小伙伴有没有觉得上下滚动起来不是很顺呢(当然,像小编这种屌丝挫男可能不会有这烦恼,相册空空如也,没有自拍就没有噩梦),那怎么来处理这种同时请求巨巨巨巨巨多图片的情况呢,这里Apple API里给我们指了条路(蹩脚英文得派上场了LOOOOOOOOOOL)
如果需要同时加载多个资源的图片数据,使用PHCachingImageManager类通过加载你马上想要的图片来“预加载”缓存。打个比方,在一个显示图片资源缩略图的collection view内,你可以在滚动到当前位置前就缓存好图片。
使用这些类来请求有关Photos资源的图片,视频,活图内容。
Photos框架根据你的要求自动下载并生成图片,缓存它们,为了更快的复用。为了大量资源能更快地表现,你也可以批请求预加载图片。
那现在的思路就是在UICollectionView滚动前,先缓存可能显示的图片,也就是当前可显示区域前后将要显示的图片。
开始
定义所需属性
//缓存图片管理器
@property (nonatomic, strong) PHCachingImageManager *imageManager;
//先前预加载区域,用于比较
@property CGRect previousPreheatRect;
更新缓存
- 判断界面是否显示
- 初始化预加载区域大小,在可显示区域基础上前后各增加半个可显示区域
- 当滚动范围大于当前显示界面的三分之一时,进行缓存操作(由于此方法会在
scrollview
的代理方法scrollViewDidScroll:
中调用,频繁调用会影响性能,这里的规则可以根据需求进行调控) - 区别新增资源以及移除资源,进行缓存更新
- (void)updateCachedAssets {
BOOL isViewVisible = [self isViewLoaded] && [[self view] window] != nil;
if (!isViewVisible) { return; }
// 预加载区域是可显示区域的两倍
CGRect preheatRect = self.collectionView.bounds;
preheatRect = CGRectInset(preheatRect, 0.0f, -0.5f * CGRectGetHeight(preheatRect));
// 比较是否显示的区域与之前预加载的区域有不同
CGFloat delta = ABS(CGRectGetMidY(preheatRect) - CGRectGetMidY(self.previousPreheatRect));
if (delta > CGRectGetHeight(self.collectionView.bounds) / 3.0f) {
// 区分资源分别操作
NSMutableArray *addedIndexPaths = [NSMutableArray array];
NSMutableArray *removedIndexPaths = [NSMutableArray array];
[self computeDifferenceBetweenRect:self.previousPreheatRect andRect:preheatRect removedHandler:^(CGRect removedRect) {
NSArray *indexPaths = [self indexPathsForElementsInCollectionView:self.collectionView rect:removedRect];
[removedIndexPaths addObjectsFromArray:indexPaths];
} addedHandler:^(CGRect addedRect) {
NSArray *indexPaths = [self indexPathsForElementsInCollectionView:self.collectionView rect:addedRect];
[addedIndexPaths addObjectsFromArray:indexPaths];
}];
NSArray *assetsToStartCaching = [self assetsAtIndexPaths:addedIndexPaths];
NSArray *assetsToStopCaching = [self assetsAtIndexPaths:removedIndexPaths];
// 更新缓存
[self.imageManager startCachingImagesForAssets:assetsToStartCaching
targetSize:AssetGridThumbnailSize
contentMode:PHImageContentModeAspectFill
options:nil];
[self.imageManager stopCachingImagesForAssets:assetsToStopCaching
targetSize:AssetGridThumbnailSize
contentMode:PHImageContentModeAspectFill
options:nil];
// 存储预加载矩形已供比较
self.previousPreheatRect = preheatRect;
}
}
- 此方法是区分增加的区域以及减少的区域
- (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);
if (newMaxY > oldMaxY) {
CGRect rectToAdd = CGRectMake(newRect.origin.x, oldMaxY, newRect.size.width, (newMaxY - oldMaxY));
addedHandler(rectToAdd);
}
if (oldMinY > newMinY) {
CGRect rectToAdd = CGRectMake(newRect.origin.x, newMinY, newRect.size.width, (oldMinY - newMinY));
addedHandler(rectToAdd);
}
if (newMaxY < oldMaxY) {
CGRect rectToRemove = CGRectMake(newRect.origin.x, newMaxY, newRect.size.width, (oldMaxY - newMaxY));
removedHandler(rectToRemove);
}
if (oldMinY < newMinY) {
CGRect rectToRemove = CGRectMake(newRect.origin.x, oldMinY, newRect.size.width, (newMinY - oldMinY));
removedHandler(rectToRemove);
}
} else {
addedHandler(newRect);
removedHandler(oldRect);
}
}
- (NSArray *)assetsAtIndexPaths:(NSArray *)indexPaths {
if (indexPaths.count == 0) { return nil; }
NSMutableArray *assets = [NSMutableArray arrayWithCapacity:indexPaths.count];
for (NSIndexPath *indexPath in indexPaths) {
PHAsset *asset = self.assetsFetchResults[indexPath.item];
[assets addObject:asset];
}
return assets;
}
- 此方法作用是获取某区域内元素的indexPaths
- (NSArray *)indexPathsForElementsInCollectionView:(UICollectionView *)collection rect:(CGRect)rect {
NSArray *allLayoutAttributes = [collection.collectionViewLayout layoutAttributesForElementsInRect:rect];
if (allLayoutAttributes.count == 0) { return nil; }
NSMutableArray *indexPaths = [NSMutableArray arrayWithCapacity:allLayoutAttributes.count];
for (UICollectionViewLayoutAttributes *layoutAttributes in allLayoutAttributes) {
NSIndexPath *indexPath = layoutAttributes.indexPath;
[indexPaths addObject:indexPath];
}
return indexPaths;
}
- 界面显示时需第一次更新缓存
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
// 可见区域刷新缓存
[self updateCachedAssets];
}
- UICollectionView滚动时更新缓存
#pragma mark -- UIScrollViewDelegate
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
[self updateCachedAssets];
}
上一篇我们实现了相册变化的检测,那么如果相册资源变化了,缓存如何操作呢?
这里小编直接将缓存重置,简单粗暴~
- (void)resetCachedAssets {
[self.imageManager stopCachingImagesForAllAssets];
self.previousPreheatRect = CGRectZero;
}
是不是觉得已经大功告成了?其实还有个很重要的地方要改,不然就白忙活了!
在-collectionView:collectionView cellForItemAtIndexPath:
这个代理方法内,我们请求图片使用的[PHImageManager defaultManager]
换成self.imageManager
。因为我们使用的是此对象来进行一系列缓存操作的。
附上DEMO
结束语
这个简单的选择器功能还不够完善,主要是熟悉Photos框架,如果读者感兴趣的话,可以持续关注小编的ASImagePicker
ASImagePicker持续更新中...
https://github.com/alanshen0118/ASImagePicker
文章中有任何错误希望读者能积极指出,我会及时更正。
如果喜欢,请持续关注,顺便点个喜欢噢👇👇👇帮五菱加加油~@_@