iOS 加载网络图片缓存本地实现瀑布流(无需客服端提供图片尺寸)以及点击图片放大功能

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

先上几张效果图:

好了直接上代码吧

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

////  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,可以直接下载浏览。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,126评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,254评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,445评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,185评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,178评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,970评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,276评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,927评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,400评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,883评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,997评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,646评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,213评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,204评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,423评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,423评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,722评论 2 345