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閱讀 228,156評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,401評論 3 415
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,069評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,873評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,635評論 6 408
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,128評論 1 323
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,203評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,365評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,881評論 1 334
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,733評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,935評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,475評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,172評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,582評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,821評論 1 282
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,595評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,908評論 2 372