美文网首页iOS学习专题
iOS 加载网络图片缓存本地实现瀑布流(无需客服端提供图片尺寸)

iOS 加载网络图片缓存本地实现瀑布流(无需客服端提供图片尺寸)

作者: 周伊宇 | 来源:发表于2020-05-12 13:37 被阅读0次

    好久没写技术博客了,最近遇到一个需求瀑布流方式实现商品详情展示,并且图片尺寸各种各样都有,且后端不返回图片尺寸,这种条件下实现瀑布流还是相当困难的,在网上找了很多博客都没有达到想要的效果,所以有必要记录一下。大家都知道瀑布流实现的核心就是图片尺寸。如果是本地图片很容易计算得到图片尺寸(所谓本地图片实现瀑布流基本没什么用),但是网络图片就比较困难了,首先需要把图片异步缓存到本地,然后计算每张图片的尺寸,最后进行瀑布流布局展示。为了更加实用直接加上点击图片放大功能。一般这样的瀑布流都是用于详情的展示界面,点击放大以及各种手势肯定是要有的。

    先上几张效果图:

    好了直接上代码吧

    首先是关于网络图片加载缓存本地

    ////  UIView+MZwebCache.h

    //  Gray_main//

    //  Created by CE on 17/5/22.

    //  Copyright © 2017年 CE. All rights reserved.

    //#importtypedef void (^MZwebCacheBlock)(UIImage *image, BOOL bFromCache, NSError *error);

    @interface UIView (MZwebCache)

    - (void)setImageWithUrl:(NSURL *)url

    placeHolder:(UIImage *)holderImage

    completion:(MZwebCacheBlock)block;

    - (void)setImageWithUrl:(NSURL *)url placeHolder:(UIImage *)holderImage;

    - (void)setImageWithUrl:(NSURL *)url;

    @end

    @interface CachedImageManager : NSObject

    + (CachedImageManager *)shareInstance;

    - (void)clearCache;                                    //清除缓存

    - (BOOL)cacheUrl:(NSURL *)url WithData:(NSData *)data; //存入url

    - (NSString *)imagePathForUrl:(NSURL *)url;            //取出url对应的path

    @property (nonatomic, copy, readonly) NSString *cachePath; //缓存目录

    @end

    ////  UIView+MZwebCache.m

    //  Gray_main//

    //  Created by CE on 17/5/22.

    //  Copyright © 2017年 CE. All rights reserved.

    //#import "UIView+MZwebCache.h"

    #import//用于MD5

    @implementation UIView (MZwebCache)

    - (void)setImageWithUrl:(NSURL *)url

    placeHolder:(UIImage *)holderImage

    completion:(MZwebCacheBlock)block {

    __weak typeof(self) weakSelf = self;

    @autoreleasepool {

    //去找真实图片

    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

    // 1.搜索对应文件名

    NSString *savedName = [[CachedImageManager shareInstance] imagePathForUrl:url];

    // 2.如存在,则直接block;如果不存在,下载

    if (savedName) {

    UIImage *image = [UIImage imageWithContentsOfFile:savedName];

    dispatch_async(dispatch_get_main_queue(), ^{

    [weakSelf showImage:image];

    if (block) {

    block(image, YES, nil);

    }

    });

    }

    else {

    if (url == nil) {

    NSLog(@"图片地址为空");

    return ;

    }

    //先加载holder

    holderImage ? [weakSelf showImage:holderImage] : nil;

    NSError *error = nil;

    NSData *imageData = [[NSData alloc] initWithContentsOfURL:url options:NSDataReadingMappedIfSafe error:&error];

    if (error) { //下载失败

    if (block) {

    block(nil, NO, error);

    }

    }

    else { //下载成功

    UIImage *image = [UIImage imageWithData:imageData];

    dispatch_async(dispatch_get_main_queue(), ^{

    [weakSelf showImage:image];

    if (block) {

    block(image, NO, nil);

    }

    });

    //缓存

    if (![[CachedImageManager shareInstance] cacheUrl:url WithData:imageData]) {

    NSLog(@"缓存失败");

    }

    }

    }

    });

    }

    }

    - (void)setImageWithUrl:(NSURL *)url placeHolder:(UIImage *)holderImage {

    [self setImageWithUrl:url placeHolder:holderImage completion:nil];

    }

    - (void)setImageWithUrl:(NSURL *)url {

    [self setImageWithUrl:url placeHolder:nil completion:nil];

    }

    //设置图片到控件上

    - (void)showImage:(UIImage *)image {

    if ([self isKindOfClass:[UIImageView class]]) {

    UIImageView *temp = (UIImageView *)self;

    [temp setImage:image];

    } else if ([self isKindOfClass:[UIButton class]]) {

    UIButton *temp = (UIButton *)self;

    [temp setBackgroundImage:image forState:UIControlStateNormal];

    temp.contentMode = UIViewContentModeScaleAspectFill;

    temp.layer.masksToBounds = YES;

    }

    }

    @end

    #pragma mark - 已缓存图片文件管理

    static dispatch_once_t once;

    static CachedImageManager *manager = nil;

    @interface

    CachedImageManager () {

    NSString *plistPath;        //存储的plist路径

    NSFileManager *fileManager; //文件管理器

    NSMutableDictionary *plistContent; // plist里存储的内容

    NSDateFormatter *format; // date类型

    }

    @end

    #define plistCacheName @"imageCache.plist"

    @implementation CachedImageManager

    + (CachedImageManager *)shareInstance {

    dispatch_once(&once, ^{

    manager = [[CachedImageManager alloc] init];

    });

    return manager;

    }

    - (id)init {

    self = [super init];

    if (self) {

    format = [[NSDateFormatter alloc] init];

    format.dateFormat = @"yyyyMMdd-hhmmss";

    plistContent = [NSMutableDictionary dictionary];

    fileManager = [NSFileManager defaultManager];

    _cachePath

    = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory,

    NSUserDomainMask, YES)[0] stringByAppendingPathComponent:@"ZMZCache"];

    //如果不存在文件夹,则创建

    if (![fileManager fileExistsAtPath:_cachePath]) {

    NSError *error = nil;

    BOOL isok = [fileManager createDirectoryAtPath:_cachePath withIntermediateDirectories:YES attributes:nil error:&error];

    if (!isok) {

    NSLog(@"%@", error);

    }

    }

    plistPath = [_cachePath stringByAppendingPathComponent:plistCacheName];

    NSLog(@"%@", plistPath);

    //如果不存在plist文件,则创建

    if (![fileManager fileExistsAtPath:plistPath]) {

    [fileManager createFileAtPath:plistPath contents:nil attributes:nil];

    } else {

    //读取plist内容

    plistContent = [NSMutableDictionary dictionaryWithContentsOfFile:plistPath];

    }

    }

    return self;

    }

    #pragma mark - 清理缓存

    - (void)clearCache {

    NSError *error;

    if ([fileManager removeItemAtPath:_cachePath error:&error]) {

    NSLog(@"清除image缓存成功");

    } else {

    NSLog(@"清除image缓存失败,原因:%@", error);

    }

    }

    #pragma mark - 缓存文件到本地

    - (BOOL)cacheUrl:(NSURL *)url WithData:(NSData *)data {

    //计算名字

    NSString *cacheString = [self caculateNameForKey:url.absoluteString];

    NSString *writePath = [_cachePath stringByAppendingPathComponent:cacheString];

    //写入

    [data writeToFile:writePath atomically:NO];

    [plistContent setValue:cacheString forKey:url.absoluteString];

    [plistContent writeToFile:plistPath atomically:NO];

    return YES;

    }

    #pragma mark - url图片对应名称

    - (NSString *)imagePathForUrl:(NSURL *)url {

    id searchResult = [plistContent valueForKey:url.absoluteString];

    if (searchResult) {

    return [_cachePath stringByAppendingPathComponent:searchResult];

    }

    return nil;

    }

    #pragma mark - 计算缓存名称

    - (NSString *)caculateNameForKey:(NSString *)key {

    const char *str = [key UTF8String];

    if (str == NULL) {

    str = "";

    }

    unsigned char r[CC_MD5_DIGEST_LENGTH];

    CC_MD5(str, (CC_LONG) strlen(str), r);

    NSString *filename = [NSString

    stringWithFormat:

    @"%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%@", r[0], r[1],

    r[2], r[3], r[4], r[5], r[6], r[7], r[8], r[9], r[10], r[11], r[12], r[13], r[14],

    r[15], [format stringFromDate:[NSDate date]]];

    return filename;

    }

    @end

    布局和计算

    粘贴的代码格式乱了,还是直接上图吧。

    ////  WaterFlowLayout.m

    //  Gray_main

    //  Created by CE on 17/5/20.//

    Copyright © 2017年 CE. All rights reserved.

    //#import "WaterFlowLayout.h"#define preloadHeight 100

      //豫加载上下各100@interface WaterFlowLayout ()

    //用于计算frame@property (nonatomic, assign) NSInteger lineNum;         ///< 列数

    @property (nonatomic, assign) NSInteger eachLineWidth;                      ///< 每列宽度,现平均,以后再扩展

    @property (nonatomic, assign) CGFloat horizontalSpace;                      ///< 水平间距

    @property (nonatomic, assign) CGFloat verticalSpace;                        ///< 竖直间距

    @property (nonatomic, assign) UIEdgeInsets edgeInset;                       ///< 边距//所有frame

    @property (nonatomic, strong) NSMutableArray*rectArray;                                     ///< 保存每个Frame值

    @property (nonatomic, strong) NSMutableArray*eachLineLastRectArray;  //< 每列的最后一个rect

    @property (nonatomic, strong) NSMutableArray*visibleAttributes;    ///< 可见Attributes

    @end

    //有四个必须改写项:collectionViewContentSize、layoutAttributesForElementsInRect、layoutAttributesForItemAtIndexPath:、shouldInvalidateLayoutForBoundsChange

    @implementation WaterFlowLayout

    - (void)prepareLayout {

    [super prepareLayout];

    //水平间距

    if (_delegate && [_delegate respondsToSelector:@selector(collectionView:layout:minimumLineSpacingForSectionAtIndex:)]) {

    _horizontalSpace = [_delegate collectionView:self.collectionView layout:self minimumLineSpacingForSectionAtIndex:0];

    }

    //竖直间距

    if

    (_delegate && [_delegate

    respondsToSelector:@selector(collectionView:layout:minimumInteritemSpacingForSectionAtIndex:)])

    {

    _verticalSpace = [_delegate collectionView:self.collectionView layout:self minimumInteritemSpacingForSectionAtIndex:0];

    }

    //边距

    if (_delegate && [_delegate respondsToSelector:@selector(collectionView:layout:insetForSectionAtIndex:)]) {

    _edgeInset = [_delegate collectionView:self.collectionView layout:self insetForSectionAtIndex:0];

    }

    //列数

    if (_delegate && [_delegate respondsToSelector:@selector(collectionView:numberOfLineForSection:)]) {

    NSInteger lineNum = [_delegate collectionView:self.collectionView numberOfLineForSection:0];

    _lineNum = lineNum;

    }

    //每列宽度

    _eachLineWidth

    = (self.collectionView.frame.size.width - _edgeInset.left -

    _edgeInset.right - MAX(0, _lineNum - 1) * _verticalSpace)/_lineNum;

    //初始化

    self.rectArray = [NSMutableArray array];

    self.eachLineLastRectArray = [NSMutableArray array];

    //计算rects,并把所有item的frame存起来

    NSInteger count = 0;

    if (_delegate && [_delegate respondsToSelector:@selector(collectionView:numberOfItemsInSection:)]) {

    count = [_delegate collectionView:self.collectionView numberOfItemsInSection:0];

    }

    for (NSInteger i = 0; i < count; i++) {

    CGSize size = CGSizeZero;

    if (_delegate && [_delegate respondsToSelector:@selector(collectionView:layout:sizeForItemAtIndexPath:)]) {

    size

    = [_delegate collectionView:self.collectionView layout:self

    sizeForItemAtIndexPath:[NSIndexPath indexPathForItem:i inSection:0]];

    }

    [self caculateLowestRectAppendToRectArrayAndEachLineLastRectArray:size];

    }

    }

    #pragma mark - ==========================四大需要重写项=========================

    - (CGSize)collectionViewContentSize {

    CGRect highest = [self caculateHighestRect];

    return CGSizeMake(self.collectionView.frame.size.width, CGRectGetMaxY(highest) + _edgeInset.bottom);

    }

    /**

    *  只加载rect内部分Attributes,确保低内存

    */

    - (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect {

    NSArray *visibleIndexPaths = [self indexPathsOfItemsInRect:rect];

    self.visibleAttributes = [NSMutableArray array];

    for (NSIndexPath *indexPath in visibleIndexPaths) {

    [_visibleAttributes addObject:[self layoutAttributesForItemAtIndexPath:indexPath]];

    }

    return _visibleAttributes;

    }

    /**

    *  从rectArray中取对应path的rect赋值。

    */

    - (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath {

    UICollectionViewLayoutAttributes *attributes =

    [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath];

    CGRect rect = [_rectArray[indexPath.item] CGRectValue];

    attributes.frame = rect;

    return attributes;

    }

    /**

    *  是否应该刷新layout(理想状态是豫加载上一屏和下一屏,这样就可以避免频繁刷新,加载过多会导致内存过大,具体多远由preloadHeight控制)

    */

    - (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds {

    //直接拿第一个和最后一个计算其实不精确,以后再改进

    CGFloat startY = CGRectGetMaxY([[_visibleAttributes firstObject] frame]);

    CGFloat endY = CGRectGetMinY([[_visibleAttributes lastObject] frame]);

    CGFloat offsetY = self.collectionView.contentOffset.y;

    if (startY + preloadHeight >= offsetY ||

    endY - preloadHeight <= offsetY + self.collectionView.frame.size.height) {

    return YES;

    }

    return NO;

    }

    #pragma mark - ==================其它====================

    //计算最低rect,并把最低rect添加进rectArray和eachLineLastRectArray

    - (void)caculateLowestRectAppendToRectArrayAndEachLineLastRectArray:(CGSize)newSize {

    CGRect newRect;

    if (_rectArray.count < _lineNum) {

    newRect

    = CGRectMake(_rectArray.count * (_eachLineWidth + _horizontalSpace) +

    _edgeInset.left, _edgeInset.top, _eachLineWidth, newSize.height);

    [_eachLineLastRectArray addObject:[NSValue valueWithCGRect:newRect]];

    }

    else {

    CGRect lowestRect = [[_eachLineLastRectArray firstObject] CGRectValue];

    NSInteger lowestIndex = 0;

    for (NSInteger i = 0; i < _eachLineLastRectArray.count; i++) {

    CGRect curruntRect = [_eachLineLastRectArray[i] CGRectValue];

    if (CGRectGetMaxY(curruntRect) < CGRectGetMaxY(lowestRect)) {

    lowestRect = curruntRect;

    lowestIndex = i;

    }

    }

    newRect = CGRectMake(lowestRect.origin.x, CGRectGetMaxY(lowestRect) + _verticalSpace, _eachLineWidth, newSize.height);

    [_eachLineLastRectArray replaceObjectAtIndex:lowestIndex withObject:[NSValue valueWithCGRect:newRect]];

    }

    [_rectArray addObject:[NSValue valueWithCGRect:newRect]];

    }

    //计算最高rect,用来调整contentSize

    - (CGRect)caculateHighestRect {

    if (_rectArray.count < _lineNum) {

    CGRect

    newRect = CGRectMake(_rectArray.count * (_eachLineWidth +

    _horizontalSpace) + _edgeInset.left, _edgeInset.top, _eachLineWidth, 0);

    return newRect;

    }

    else {

    CGRect highestRect = [_rectArray[_rectArray.count - _lineNum] CGRectValue];

    for (NSInteger i = _rectArray.count - _lineNum; i < _rectArray.count; i++) {

    CGRect curruntRect = [_rectArray[i] CGRectValue];

    if (CGRectGetMaxY(curruntRect) > CGRectGetMaxY(highestRect)) {

    highestRect = curruntRect;

    }

    }

    return highestRect;

    }

    }

    //当前应该显示到屏幕上的items

    - (NSArray *)indexPathsOfItemsInRect:(CGRect)rect {

    CGFloat startY = self.collectionView.contentOffset.y;

    CGFloat endY = startY + self.collectionView.frame.size.height;

    NSMutableArray *items = [NSMutableArray array];

    for (NSInteger i = 0; i < _rectArray.count; i++) {

    CGRect rect = [_rectArray[i] CGRectValue];

    if ((CGRectGetMaxY(rect) >= startY &&

    CGRectGetMaxY(rect) <= endY ) ||

    (CGRectGetMinY(rect) >= startY &&

    CGRectGetMinY(rect) <= endY )) {

    [items addObject:[NSIndexPath indexPathForItem:i inSection:0]];

    }

    }

    return items;

    }

    @end

    然后为了实现类似于淘宝商品详情点击图片放大功能,再写一个图片放大视图控制器

    ////  ToyDetailsBigImgaeViewController.m

    //  WaterfallsFlowNetworkImage//

    //  Created by CE on 2017/6/6.

    //  Copyright © 2017年 CE. All rights reserved.

    //#import "ToyDetailsBigImgaeViewController.h"

    #import "UIImageView+WebCache.h"

    #import "ViewController.h"

    @interface ToyDetailsBigImgaeViewController (){

    UIScrollView *_scrollView;

    }

    @end

    @implementation ToyDetailsBigImgaeViewController

    - (void)viewDidLoad {

    [super viewDidLoad];

    [self createScrollView];

    self.navigationController.navigationBar.hidden = YES;

    }

    - (void)viewWillDisappear:(BOOL)animated{

    self.navigationController.navigationBar.hidden = NO;

    }

    -(void)createScrollView{

    _scrollView = [[UIScrollView alloc]initWithFrame:self.view.frame];

    _scrollView.backgroundColor = [UIColor grayColor];

    UIImageView *imageView = [[UIImageView alloc]initWithFrame:self.view.frame];

    //UIImageView *imageView = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, SCREEN_W, SCREEN_LH)];

    [_scrollView addSubview:imageView];

    imageView.contentMode = UIViewContentModeScaleAspectFit;

    [imageView sd_setImageWithURL:[NSURL URLWithString:[NSString stringWithFormat:@"%@",self.url]]];

    //尺寸

    _scrollView.contentSize = imageView.frame.size;

    //偏移量

    _scrollView.contentOffset = CGPointMake(1000, 500);

    //设置是否回弹

    _scrollView.bounces = NO;

    //设置边距

    //_scrollView.contentInset = UIEdgeInsetsMake(10, 10, 10, 10);

    _scrollView.contentInset = UIEdgeInsetsMake(1, 1, 1, 1);

    //设置是否可以滚动

    _scrollView.scrollEnabled = YES;

    //是否可以会到顶部

    _scrollView.scrollsToTop = YES;

    //按页滚动

    //scrollView.pagingEnabled = YES;

    //设置滚动条

    _scrollView.showsHorizontalScrollIndicator = YES;

    _scrollView.showsVerticalScrollIndicator = NO;

    //设置滚动条的样式

    _scrollView.indicatorStyle = UIScrollViewIndicatorStyleWhite;

    imageView.userInteractionEnabled = YES;

    //代理

    _scrollView.delegate = self;

    //CGFloat imageWidth = imageView.frame.size.width;

    //设置最小和最大缩放比例

    //_scrollView.minimumZoomScale = SCREEN_W/imageWidth;

    //_scrollView.maximumZoomScale = 1.5;

    _scrollView.minimumZoomScale = 0.2;

    //_scrollView.maximumZoomScale = 2.0;

    _scrollView.maximumZoomScale = imageView.frame.size.width * 3 / self.view.frame.size.width;

    [self.view addSubview:_scrollView];

    //给imageView添加手势

    //创建单击双击手势

    UITapGestureRecognizer *oneTgr = [[UITapGestureRecognizer alloc]initWithTarget:self action:@selector(tapClick:)];

    //oneTgr.numberOfTapsRequired = 1;

    [imageView addGestureRecognizer:oneTgr];

    UITapGestureRecognizer *tgr = [[UITapGestureRecognizer alloc]initWithTarget:self action:@selector(tapClick:)];

    tgr.numberOfTapsRequired = 2;

    [imageView addGestureRecognizer:tgr];

    [oneTgr requireGestureRecognizerToFail:tgr];

    }

    -(void)tapClick:(UITapGestureRecognizer *)tap{

    if (tap.numberOfTapsRequired == 1) {

    printf("单击手势识别成功\n");

    [self.navigationController popViewControllerAnimated:NO];

    } else {

    printf("双击手势识别成功\n");

    //zoomScale当前的缩放比例

    if (_scrollView.zoomScale == 1.0) {

    [_scrollView setZoomScale:_scrollView.maximumZoomScale animated:YES];

    } else {

    [_scrollView setZoomScale:1.0 animated:YES];

    }

    }

    }

    #pragma mark - 代理

    //- (void)scrollViewDidScroll:(UIScrollView *)scrollView{

    //

    //    NSLog(@"滚动");

    //

    //}

    - (void)scrollViewDidEndZooming:(UIScrollView *)scrollView withView:(nullable UIView *)view atScale:(CGFloat)scale {

    if (scale <= 0.5) {

    //当缩放比例小于0.5时返回上一级

    [self.navigationController popViewControllerAnimated:NO];

    }

    }

    //只要缩放就会调用此方法

    - (void)scrollViewDidZoom:(UIScrollView *)scrollView {

    NSLog(@"发生缩放");

    }

    - (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView{

    NSLog(@"将要开始拖动");

    }

    -

    (void)scrollViewWillEndDragging:(UIScrollView *)scrollView

    withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint

    *)targetContentOffset {

    NSLog(@"将要结束拖动");

    }

    - (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate{

    NSLog(@"拖动结束");

    }

    - (void)scrollViewWillBeginDecelerating:(UIScrollView *)scrollView{

    NSLog(@"将要开始减速");

    }

    - (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView{

    NSLog(@"已经结束减速");//停止滚动

    }

    - (void)scrollViewDidEndScrollingAnimation:(UIScrollView *)scrollView{

    NSLog(@"滚动动画结束");

    }

    - (nullable UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView{

    NSLog(@"正在缩放");

    //放回对那个子视图进行缩放  前提是有缩放比例

    return scrollView.subviews[0];

    }

    - (void)scrollViewWillBeginZooming:(UIScrollView *)scrollView withView:(nullable UIView *)view{

    NSLog(@"缩放开始");

    }

    //- (void)scrollViewDidEndZooming:(UIScrollView *)scrollView withView:(nullable UIView *)view atScale:(CGFloat)scale{

    //    NSLog(@"%@",view);

    //

    //

    //#if 0

    //

    //    //放大时会出现问题

    //    if (scale <1.0) {

    //        CGPoint center = view.center;

    //        center.y = HEIGHT/2-64;

    //        view.center = center;

    //    }

    //

    //#endif

    //

    //

    //    if (view.frame.size.width > SCREEN_W) {

    //        scrollView.contentInset = UIEdgeInsetsMake(0, 0, 0, 0);

    //

    //    } else {

    //

    //        //距边框的距离

    //        [UIView animateWithDuration:0.5 animations:^{

    //            scrollView.contentInset = UIEdgeInsetsMake((SCREEN_LH-view.frame.size.width)/2, 0, 0, 0 );

    //

    //        }];

    //    }

    //

    //    NSLog(@"缩放结束");

    //}

    //是否可以滚动到顶部 前提是前面scrollToTop = YES;

    - (BOOL)scrollViewShouldScrollToTop:(UIScrollView *)scrollView{

    return YES;

    }

    - (void)scrollViewDidScrollToTop:(UIScrollView *)scrollView{

    NSLog(@"已经滚动到顶部");

    }

    - (void)didReceiveMemoryWarning {

    [super didReceiveMemoryWarning];

    // Dispose of any resources that can be recreated.

    }

    /*

    #pragma mark - Navigation

    // In a storyboard-based application, you will often want to do a little preparation before navigation

    - (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {

    // Get the new view controller using [segue destinationViewController].

    // Pass the selected object to the new view controller.

    }

    */

    @end

    在这里处理主要的业务逻辑。这里之前有一个BUG,当图片显示高度超过屏幕高度时,会闪一下然后不显示,单纯的从cell高度方面找解决方案不太容易,最快的就是直接在self.view

    上放一个scrollView ,

    然后把collectionView放在scrollView上,依靠scrollView的滚动代替collectionView滑动。这样需要动态地计算scrollView的contentSize来实现如原生collectionView滑动展示效果。当然解决这个BUG的方法很多,还有很多更简单的。可以留言交流。

    ////  ViewController.m

    //  WaterfallsFlowNetworkImage//

    //  Created by CE on 2017/6/6.

    //  Copyright © 2017年 CE. All rights reserved.

    //#import "ViewController.h"

    #import "WaterFlowLayout.h"

    #import "UIView+MZwebCache.h"

    #import "AFNetworking.h"

    #import "MJRefresh.h"

    #import "UIImageView+WebCache.h"

    #import "UIButton+WebCache.h"

    #import "SDWebImageManager.h"

    #import "SDWebImageDownloader.h"

    #import "UIImage+GIF.h"

    #import "NSData+ImageContentType.h"

    #import "ToyDetailsBigImgaeViewController.h"

    @interface ViewController (){

    NSInteger lines;

    //cell高度

    CGFloat cellCurrentHight;

    //最大图片高度

    CGFloat imageMAXHight;

    }

    @property (nonatomic, strong) NSMutableArray *dataArray;

    @property (nonatomic, strong) UICollectionView *collectionView;

    @property (nonatomic,strong) NSArray *imagArray;

    @property (nonatomic,strong) UIScrollView *backgroundScrollView;

    @property (nonatomic,strong) NSMutableDictionary *MDic;

    @end

    @implementation ViewController

    //屏幕尺寸

    #define SCREEN_H [UIScreen mainScreen].bounds.size.height

    #define SCREEN_W [UIScreen mainScreen].bounds.size.width

    - (void)viewDidLoad {

    [super viewDidLoad];

    self.MDic = [[NSMutableDictionary alloc] init];

    [self createUI];

    }

    - (void)createUI{

    self.backgroundScrollView = [[UIScrollView alloc]initWithFrame:self.view.bounds];

    [self.view addSubview:self.backgroundScrollView];

    self.backgroundScrollView.scrollEnabled = YES;

    self.backgroundScrollView.contentSize = CGSizeMake(SCREEN_W, SCREEN_H * 1.2);

    self.backgroundScrollView.backgroundColor = [UIColor whiteColor];

    //是否回弹

    //self.backgroundScrollView.bounces = NO;

    self.backgroundScrollView.alwaysBounceVertical = YES;

    //self.backgroundScrollView.showsHorizontalScrollIndicator = NO;

    //self.backgroundScrollView.showsVerticalScrollIndicator = NO;

    WaterFlowLayout *flowOut = [[WaterFlowLayout alloc] init];

    flowOut.delegate = self;

    self.collectionView =

    [[UICollectionView alloc] initWithFrame:CGRectMake(0, 0, SCREEN_W, SCREEN_H * 1.2)

    collectionViewLayout:flowOut];

    _collectionView.delegate = self;

    _collectionView.dataSource = self;

    _collectionView.alwaysBounceVertical = YES;

    _collectionView.scrollEnabled = NO;

    _collectionView.backgroundColor = [UIColor colorWithWhite:0.9 alpha:0.8];

    [self.backgroundScrollView addSubview:_collectionView];

    [_collectionView registerNib:[UINib nibWithNibName:@"MainCell" bundle:nil]

    forCellWithReuseIdentifier:@"MainCell"];

    //默认列数

    lines = 1;

    self.title = [NSString stringWithFormat:@"%ld列",lines];

    UISegmentedControl *segment = [[UISegmentedControl alloc]

    initWithItems:@[ @"1", @"2", @"3", @"4", @"5", @"6", @"7", @"8" ]];

    segment.frame = CGRectMake(0, SCREEN_H - 40, SCREEN_W, 40);

    segment.selectedSegmentIndex = 0;

    [self.view addSubview:segment];

    [segment addTarget:self

    action:@selector(changeLines:)

    forControlEvents:UIControlEventValueChanged];

    //加载数据

    [self prepareData];

    }

    - (void)prepareData {

    _imagArray = @[        //图片链接

    @"http://r.photo.store.qq.com/psb?/V14FKYxo4VCpyi/QVQTlkgfGtvY8Ml1e5*C.0.r2rvYkiNmkuEgOxChKdE!/r/dIIBAAAAAAAA",

    @"http://r.photo.store.qq.com/psb?/V14FKYxo4VCpyi/d6DS.ut7JDKCngxXd0CaTDVjzkZCCjDfPQgRVThM9vE!/r/dG0BAAAAAAAA",

    @"http://r.photo.store.qq.com/psb?/V14FKYxo4VCpyi/*TawSnqTpDyDN9StiXJlG6naEToM6KLa0XoRAFgOxi4!/r/dGwBAAAAAAAA",

    @"http://a3.qpic.cn/psb?/V14FKYxo0UhIAP/py.OcSKU4wVb4vXlqxv.DKIY.XEkzx7U.n838lTPfak!/b/dN0AAAAAAAAA&bo=gAJTGwAAAAAFB.4!&rf=viewer_4",

    @"http://r.photo.store.qq.com/psb?/V14FKYxo4VCpyi/Vjr2oZ*N4wty.iWKnF4TGfqh7SBFusq2bYZ7pzgISNQ!/r/dGwBAAAAAAAA",

    @"http://r.photo.store.qq.com/psb?/V14FKYxo4VCpyi/tSDFJivi0z0vnoXdiEdkYUr6pnwmedJYdt*Y2QgXBg8!/r/dG4BAAAAAAAA",

    @"http://r.photo.store.qq.com/psb?/V14FKYxo4VCpyi/gCOv8dKdS0v21xG9MX2UngH655hg5AsuWyIu*0u5WZk!/r/dGwBAAAAAAAA",

    @"http://r.photo.store.qq.com/psb?/V14FKYxo4VCpyi/x2TP3LgwjRjrLWhK*TwGOUvfB9Ipyv8pXS10FQPJRQY!/r/dGwBAAAAAAAA",

    @"http://r.photo.store.qq.com/psb?/V14FKYxo4VCpyi/wCAh6JN5RffRMbIabosoKoOqEFz8RP7FuFZl2vMVwkI!/r/dG0BAAAAAAAA",

    @"http://r.photo.store.qq.com/psb?/V14FKYxo4VCpyi/1M7RPK9zA5EWUIkzf01qfx*Q*fdlGcq7jAFZqC40m5g!/r/dG0BAAAAAAAA",

    @"http://r.photo.store.qq.com/psb?/V14FKYxo4VCpyi/XnxwggzMNrYrhLWdEMSfCazNiJuO8nDysOyZ0Qx3DhQ!/r/dGwBAAAAAAAA",

    @"http://r.photo.store.qq.com/psb?/V14FKYxo4VCpyi/TMJrjlo3D*oMYXrpLJmDNyfrW0dKnzPZF2DMSW8Y.Ek!/r/dIMBAAAAAAAA",

    @"http://r.photo.store.qq.com/psb?/V14FKYxo4VCpyi/DK7tRNLecsbzH9FB7hT1pzrlQnz6vfKsCrg3GqE5qRA!/r/dIQBAAAAAAAA",];

    self.dataArray = [NSMutableArray array];

    for (NSInteger i = 0; i < _imagArray.count; i++) {

    MainModel *model = [[MainModel alloc] init];

    model.imageUrl = _imagArray[i % _imagArray.count];

    [_dataArray addObject:model];

    }

    }

    //更改列数

    - (void)changeLines:(UISegmentedControl *)segment {

    lines = segment.selectedSegmentIndex + 1;

    [_collectionView reloadData];

    self.title = [NSString stringWithFormat:@"%ld列",lines];

    }

    #pragma mark - UICollectionView DataSource Methods

    - (NSInteger)collectionView:(UICollectionView *)collectionView

    numberOfItemsInSection:(NSInteger)section {

    return _dataArray.count;

    }

    - (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView

    cellForItemAtIndexPath:(NSIndexPath *)indexPath {

    __weak typeof(self) weakSelf = self;

    MainCell *cell = (MainCell *)[collectionView dequeueReusableCellWithReuseIdentifier:@"MainCell" forIndexPath:indexPath];

    cell.indexPath = indexPath;

    cell.model = _dataArray[indexPath.row];

    cell.sizeChanged = ^() {

    //这里每次加载完图片后,得到图片的比例会再次调用刷新此item,重新计算位置,会导致效率低。最优做法是服务器返回图片宽高比例;其次把加载完成后的宽高数据也缓存起来。

    [weakSelf.collectionView reloadItemsAtIndexPaths:@[indexPath]];

    };

    return cell;

    }

    #pragma mark - UICollectionView Delegate Methods

    - (CGFloat)collectionView:(UICollectionView *)collectionView

    layout:(UICollectionViewLayout *)collectionViewLayout

    minimumLineSpacingForSectionAtIndex:(NSInteger)section {

    return 5;

    }

    - (CGFloat)collectionView:(UICollectionView *)collectionView

    layout:(UICollectionViewLayout *)collectionViewLayout

    minimumInteritemSpacingForSectionAtIndex:(NSInteger)section {

    return 5;

    }

    - (UIEdgeInsets)collectionView:(UICollectionView *)collectionView

    layout:(UICollectionViewLayout *)collectionViewLayout

    insetForSectionAtIndex:(NSInteger)section {

    return UIEdgeInsetsMake(10, 10, 10, 10);

    }

    //返回每个小方块宽高,但由于是在WaterFlowLayout处理,只取了高,宽是由列数平均分

    - (CGSize)collectionView:(UICollectionView *)collectionView

    layout:(UICollectionViewLayout *)collectionViewLayout

    sizeForItemAtIndexPath:(NSIndexPath *)indexPath {

    MainModel *model = _dataArray[indexPath.row];

    NSInteger lineNum = [self collectionView:_collectionView numberOfLineForSection:0];

    CGFloat width = ((SCREEN_W - 10) - (lineNum - 1) * 5) / lineNum;

    if (model.imageSize.width > 0) {

    CGSize imageSize = model.imageSize;

    //获取每个cell的高度 存入字典

    CGFloat HHH = width / imageSize.width * imageSize.height;

    NSLog(@"HHH = %f",width / imageSize.width * imageSize.height);

    NSLog(@"indexPath.row = %ld",indexPath.row);

    NSNumber *cellHight = [NSNumber numberWithFloat:HHH];

    NSString *indexPathRow = [NSString stringWithFormat:@"%ld",indexPath.row];

    NSLog(@"indexPathRow = %@",indexPathRow);

    [self.MDic setValue:cellHight forKey:indexPathRow];

    NSLog(@"self.MDic = %@",self.MDic);

    NSArray *otherCellHightArray = [self.MDic allValues];

    cellCurrentHight = 0;

    for (NSNumber *cellHightNumber  in otherCellHightArray) {

    CGFloat cellHightFloat = [cellHightNumber floatValue];

    cellCurrentHight += cellHightFloat;

    if (indexPath.row == 0) {

    imageMAXHight = cellHightFloat;

    }

    if (imageMAXHight < cellHightFloat) {

    imageMAXHight = cellHightFloat;

    }

    }

    cellCurrentHight = cellCurrentHight / lines;

    if (imageMAXHight > cellCurrentHight) {

    cellCurrentHight = imageMAXHight;

    }

    NSLog(@"cellCurrentHight = %f",cellCurrentHight);

    //赋值

    self.backgroundScrollView.contentSize = CGSizeMake(SCREEN_W, cellCurrentHight + 64 + 40 + 40);

    _collectionView.frame = CGRectMake(0, 0, SCREEN_W, cellCurrentHight + 64 + 40 + 40);

    NSLog(@"cellCurrentHight = %f",cellCurrentHight);

    return CGSizeMake(width, width / imageSize.width * imageSize.height);

    }

    return CGSizeMake(width, 300);

    }

    - (void)collectionView:(UICollectionView *)collectionView

    didSelectItemAtIndexPath:(nonnull NSIndexPath *)indexPath {

    NSLog(@"点击了第%ld个", indexPath.row);

    ToyDetailsBigImgaeViewController *toyDetailsBigImgaeVC = [[ToyDetailsBigImgaeViewController alloc] init];

    NSString *url = _imagArray[indexPath.row];

    toyDetailsBigImgaeVC.url = url;

    [self.navigationController pushViewController:toyDetailsBigImgaeVC animated:NO];

    }

    #pragma mark - WaterFlowout代理,请填入返回多少列

    - (NSInteger)collectionView:(UICollectionView *)collectionView

    numberOfLineForSection:(NSInteger)section {

    return lines;

    }

    - (void)didReceiveMemoryWarning {

    [super didReceiveMemoryWarning];

    // Dispose of any resources that can be recreated.

    }

    @end

    @implementation MainCell

    - (void)setModel:(MainModel *)model {

    _model = model;

    __weak typeof(self) weakSelf = self;

    [_mainImgv

    setImageWithUrl:[NSURL URLWithString:model.imageUrl]

    placeHolder:[UIImage imageNamed:@"loading.jpg"] completion:^(UIImage

    *image, BOOL bFromCache, NSError *error) {

    if (!error && image) {

    if (model.imageSize.width < 0.0001) {

    model.imageSize = image.size;

    if (weakSelf.sizeChanged) {

    weakSelf.sizeChanged();

    }

    }

    }

    }];

    }

    - (void)dealloc {

    NSLog(@"=======%@ =%@ deallloc",self ,[self class]);

    }

    @end

    @implementation MainModel

    @end

    到此为止基本所有的代码都贴出来,这个详情页很容易实现。代码已经上传到GitHub,可以直接下载浏览。

    相关文章

      网友评论

        本文标题:iOS 加载网络图片缓存本地实现瀑布流(无需客服端提供图片尺寸)

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