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