美文网首页UIKitiOS综合相关iOS进阶相关
PhotoKit制作相册或选择器(四):预加载策略

PhotoKit制作相册或选择器(四):预加载策略

作者: Alan_Sim | 来源:发表于2016-09-27 11:07 被阅读251次

    前言

    上一篇文章里我们已经将图片选择器的基本功能完成了。相册里图片比较多的小伙伴有没有觉得上下滚动起来不是很顺呢(当然,像小编这种屌丝挫男可能不会有这烦恼,相册空空如也,没有自拍就没有噩梦),那怎么来处理这种同时请求巨巨巨巨巨多图片的情况呢,这里Apple API里给我们指了条路(蹩脚英文得派上场了LOOOOOOOOOOL


    如果需要同时加载多个资源的图片数据,使用PHCachingImageManager类通过加载你马上想要的图片来“预加载”缓存。打个比方,在一个显示图片资源缩略图的collection view内,你可以在滚动到当前位置前就缓存好图片。

    使用这些类来请求有关Photos资源的图片,视频,活图内容。
    Photos框架根据你的要求自动下载并生成图片,缓存它们,为了更快的复用。为了大量资源能更快地表现,你也可以批请求预加载图片。

    那现在的思路就是在UICollectionView滚动前,先缓存可能显示的图片,也就是当前可显示区域前后将要显示的图片。

    开始

    定义所需属性

    //缓存图片管理器
    @property (nonatomic, strong) PHCachingImageManager *imageManager;
    //先前预加载区域,用于比较
    @property CGRect previousPreheatRect;
    

    更新缓存

    1. 判断界面是否显示
    2. 初始化预加载区域大小,在可显示区域基础上前后各增加个可显示区域
    3. 当滚动范围大于当前显示界面的三分之一时,进行缓存操作(由于此方法会在scrollview的代理方法scrollViewDidScroll:中调用,频繁调用会影响性能,这里的规则可以根据需求进行调控)
    4. 区别新增资源以及移除资源,进行缓存更新
    - (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

    文章中有任何错误希望读者能积极指出,我会及时更正。
    如果喜欢,请持续关注,顺便点个喜欢噢👇👇👇帮五菱加加油~@_@

    Thanks!!!

    相关文章

      网友评论

        本文标题:PhotoKit制作相册或选择器(四):预加载策略

        本文链接:https://www.haomeiwen.com/subject/tquvettx.html