iOS開發:一個瀑布流的設計與實現(已實現緩存池功能,該功能使得瀑布流cell可以循環利用)


一個瀑布流的實現有三種方式:

    1. 繼承自UIScrollView,仿寫UITableView的dataSource和delegate,創造一個緩存池用來實現循環利用cell
    2. 寫多個UITableview(UITableView的cell寬度是與UITableView寬度一樣的,那么每行可以擺設多個寬度相等的UITableView,從而實現瀑布流),不過這種方法是最差的,因為不能有效的做到循環利用cell
    3. 可以自定義UICollectionViewCell的布局,從而實現瀑布流,UICollectionView自帶cell的循環利用功能

這里是使用UIScorllView實現的,就是第一種方案實現的,這種實現方式,自由度比之第三種方案更高。有時間我會寫篇基於UICollectionView實現瀑布流的博客。

在此篇之前,最好是對UITableView里面dataSource和delegate有些深入的了解,如果有需要,可以參考這篇博客:http://www.cnblogs.com/ziyi--caolu/p/4769703.html

這里面提到了一些UITableView內部實現細節(當然是個人的猜想),然后仿照UITableView的delegate和dataSource寫出了一個比較簡單的例子,瀑布流可以算是前面那個例子的深入版。

 

 

從圖中,可以得到基本的需求,也就是說,瀑布流每張圖片的寬度是確定的,而高度是變化的(還有一種是高度是固定的,而寬度是變化的,實現原理一樣)。

流布局結構,這說明要可以滾動。每張圖片上,既要可以顯示圖片,又要可以顯示文字,這僅僅是我寫的這個medo的需求,在實際編程中,很有可能里面會裝有各種控件。這種情況下,第一想法是UITableView和UITableViewCell,因為除了尺寸的問題外,UITableView的UITableViewCell是滿足上述要求的,也就是一個cell里面可以裝有各種控件,同時還可以循環利用cell。

那么,是否可以根據UITableView和UITableViewCell的設計,從而實現瀑布流呢?那么對應的,應該有ZYWaterFlowView和ZYWaterFlowViewCell吧?

首先,每張圖片和文字會放在一個view里面,可以稱呼它為waterFlowViewCell,如此先創建一個ZYWaterFlowViewCell類,里面有一個UIImageView和一個UILabel,這樣cell部分好像可以滿足基本需求了,至於要如何顯示這個cell,應該是它父控件,也就是ZYWaterFlowView的事情。(先這樣設計,后面有需要再修改)

再就是ZYWaterFlowView了,它要可以滾動,那么應當繼承自UIScrollView,接下來就是獲取cell如何排列的數據了。

這一部分,可以參考UITableView的設計,UITableVIew是通過讓viewController遵守它的UITableViewDataSource和UITableViewDelegate協議,從而不斷的向viewController要各種必須的數據,進而展示出cell的,而UITableView的重新排列是reLoaddata方法。(開篇提到的參考博客里詳細說了這個過程,這里不再重述)

那么,ZYWaterFlowView完全可以參照這種設計,從而拿到自己必須的數據,處理響應的事件。

  1. ZYWaterFlowView肯定是要知道會有多少數目的ZYWaterFlowViewCell的,就如遵守UITableViewDataSource總要實現這兩個方法:

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section

UITableView通過在.m文件里調用[self.dataSource numberOfSectionsInTableView:self]從而得知,有多少組?

通過調用[self.dataSource tableView:self numberOfRowsInSection:section]每組有多少行?(開篇提到的參考博客里詳細說了這個過程,這里不再重述)

那么,我們可以對應的寫出下面這個方法,創建一個ZYWaterFlowViewDataSource協議,在協議里面聲明下面的這個必須要實現的方法,從而讓ZYWaterFlowView可以通過同樣的方式而得知cell的具體數目:

//返回cell的數目

- (NSInteger)numberOfCellsInWaterFlowView:(ZYWaterFlowView *)waterFlowView;

 

2. 我們總得拿到cell吧?不然怎么展示它?怎么實現循環利用功能?就如UITableViewDataSource里面的這個方法:

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath

UITableView是通過這樣調用[self.dataSource tableView:self cellForRowAtIndexPath:indexPath],從而得到相對應位置的cell的,那么我的ZYWaterFlowView完全可以參照這種方式寫出一個方法,從而得到我對應index的cell,如此,應該有下面這個方法:

//返回index位置對應的cell

- (ZYWaterFlowViewCell *)waterFlowView:(ZYWaterFlowView *)waterFlowView cellAtIndex:(NSUInteger)index;

 

3. 上面兩個方法,在ZYWaterFlowViewDataSource協議是要用@required關鍵字修飾的,意為遵守協議就必須實現的方法,負責就會產生錯誤。那么總該有一些不那么重要的數據,給我們來自定義一些東西吧?這里我是聲明了一個返回自定義列數(默認為三列)的方法:

@optional

//一共多少列

- (NSInteger)numberOfColumnsInWaterFlowView:(ZYWaterFlowView *)waterFlowView;

 

下面就是仿照UITableViewDelegate的一些設計了,用法和UITableViewDataSource是相同的,這里不再復述,只是需要注意的是,我們應該盡可能好的設計UIWaterFlowViewDelegate,從而讓用這套框架的同事覺得很好用,不需要再修改。(我並未去設計很多方法,主要是沒時間,事實上,我覺得完全可以將UITableViewDelegate里面的很多方法在這里寫上)。

@optional

//返回index的cell的高度

- (CGFloat)waterFlowView:(ZYWaterFlowView *)waterFlowView heightAtIndex:(NSUInteger)index;

 

//各種間距

- (CGFloat)waterFlowView:(ZYWaterFlowView *)waterFlowView marginForType:(ZYWaterFlowViewMarginType)type;

 

//返回被選中的(孩子~~)cell

- (ZYWaterFlowViewCell *)waterFlowView:(ZYWaterFlowView *)waterFlowView didSelectedAtIndex:(NSUInteger)index;

 

僅有上面說的這些還不夠,參考UITableView,他還有reloadData方法,可以猜想,這個方法是用來重新布局cell的(也就是從新計算cell尺寸的),如果需要創建緩存池,從而實現cell的循環利用,那么UITableView提供了這個方法:

- (id)dequeueReusableCellWithIdentifier:(NSString *)identifier;  

那么從仿照的角度上來說,ZYWaterFlowView也得實現這兩個方法,從而處理相應的事情。到此,ZYWaterFlowView的.h文件,有了下面的設計:

#import <UIKit/UIKit.h>

typedef enum {
    ZYWaterFlowViewMarginTypeTop,
    ZYWaterFlowViewMarginTypeLeft,
    ZYWaterFlowViewMarginTypeBottom,
    ZYWaterFlowViewMarginTypeRight,
    ZYWaterFlowViewMarginTypeRow,
    ZYWaterFlowViewMarginTypeColumn
    
}ZYWaterFlowViewMarginType;

@class ZYWaterFlowView, ZYWaterFlowViewCell;

@protocol ZYWaterFlowViewDataSource <NSObject>

@required
//返回cell的數目
- (NSInteger)numberOfCellsInWaterFlowView:(ZYWaterFlowView *)waterFlowView;
//返回index位置對應的cell
- (ZYWaterFlowViewCell *)waterFlowView:(ZYWaterFlowView *)waterFlowView cellAtIndex:(NSUInteger)index;

@optional
//一共多少列
- (NSInteger)numberOfColumnsInWaterFlowView:(ZYWaterFlowView *)waterFlowView;
@end


@protocol ZYWaterFlowViewDelegate <UIScrollViewDelegate>

@optional
//返回index的cell的高度
- (CGFloat)waterFlowView:(ZYWaterFlowView *)waterFlowView heightAtIndex:(NSUInteger)index;

//各種間距
- (CGFloat)waterFlowView:(ZYWaterFlowView *)waterFlowView marginForType:(ZYWaterFlowViewMarginType)type;

//返回被選中的(孩子~~)cell
- (ZYWaterFlowViewCell *)waterFlowView:(ZYWaterFlowView *)waterFlowView didSelectedAtIndex:(NSUInteger)index;

@end

@interface ZYWaterFlowView : UIScrollView

@property (nonatomic, weak) id<ZYWaterFlowViewDataSource> dataSource;

@property (nonatomic, weak) id<ZYWaterFlowViewDelegate> delegate;

//刷新數據
- (void)reloadData;

//根據identifier去緩存池找到對應的cell
- (id)dequeueReusableCellWithIdentifier:(NSString *)identifier;
@end

 接下來就是.m文件里面的實現了。上面提到,我們應該在reloadData方法里面布局cell,那么瀑布流的cell應該如何布局呢?是按照從左到右、從上到下的順序依次放入cell么?肯定不是的,前面提到,瀑布流的cell只是寬度相同,而高度不相同,如果是按照從左到右、從上到下的順序依次放入cell的排列,最后非常有可能出現某一列高度很高,某一列高度很低等的情況。

基於這樣的思考,我們該這樣,如果要排列當前的一個cell,那么應該知道各列的最高高度,將當前的這個cell放入最高高度最小的那一列,然后更新那一列的最高高度,並保存。這里我用c語言中的數組保存,因為oc數組必須保存對象,操作也耗時些。

想想,在reloadData方法里面,我們必須拿到總的cell數目,還好,dataSource協議里面已經提供了方法,我們只需要調用那個方法,然后拿到相應的數據即可,然后是根據代理里面提供的各種間距、寬度、高度,根據“將當前的這個cell放入最高高度最小的那一列”布局好cell(也就是設置好所有的cell的frame),並更新最大高度,設置好UIScrollView的contentSize屬性(也就是UIScrollView的滾動范圍,應該是最高列+底部間距)。還有一個問題,就是該如何保存這些cell的frame呢?很簡單,放到數組(NSMutableArray *cellFrames)里面即可,要用到的時候,根據相應的index(索引)取出來即可。

既然每個cell的frame已經計算好了,那么該拿到相應的cell(view),展示在屏幕上了。scrollView在滾動的時候,除了會調用它delegate里面的一些方法之外,還會調用

- (void)layoutSubviews

如此,我們在這里面拿到cell,並拿到對應下標的cellFrames里面的元素設置cell的frame。初看,並沒有什么問題,但實際上,這樣做的話,並未實現循環利用,不僅僅如此,還會重復創建cell的bug。

因此,在這個方法里面,應該先拿到對應下標的cell的frame,再判斷這個cell是否正展示在屏幕上?可以與scrollView的contentOffSet屬性判斷,只要,cell的最大的y大於contentOffset.y,最小的y小於contentOffset.y + self(這里應該是屏幕)的高度,那么該cell就是要展示在屏幕上的。這里會有一個字典(NSMutableDictionary *displayingCells),會裝有這一次滾動前展示在屏幕上得cell,如果只是小范圍滾動的話,那么displayingCells里可能還有一些cell展示在屏幕上,而我們應該並那些沒有展示在當前屏幕上得cell從scrollView(也就是ZYWaterFlowView,也就是self)里面移除掉,又因為displayingCells存放的是當前正展示在屏幕上的cell,那么相應的,應當將之從displayingCells里面移除,放到緩存池里面去(這里的緩存池,設計為NSMutableSet *reusableCells)。下面是主要代碼:

//當scrollView滾動,除了會調用它的代理方法之外,還會時時調用這個方法
//所以,在這個方法里面拿到當前顯示在屏幕的cell,設置尺寸~~
- (void)layoutSubviews
{
    [super layoutSubviews];
    
    int numberOfCells = (int)[self.dataSource numberOfCellsInWaterFlowView:self];
    
    for (int i = 0; i < numberOfCells; i++) {
        //先在scrollView上存在的cell里看當前cell是否存在scrollView(self)上
        ZYWaterFlowViewCell *cell = self.displayingCells[@(i)];
        CGRect cellF = [self.cellFrames[i] CGRectValue];
        
        if ([self isInScreen:cellF]) { //判斷當前cell是否有在屏幕展示(注意,這里與在scrollView(self)上展示不同)
            if (cell == nil) {  //需要在屏幕展示,所以cell不存在的時候需要創建
                cell = [self.dataSource waterFlowView:self cellAtIndex:i];
                cell.frame = cellF;
                [self addSubview:cell];
                self.displayingCells[@(i)] = cell;  //添加到了scrollView上,所以將它加入字典中
            }
        }else
        {
            if (cell) { //沒在屏幕上,所以不需要cell,把它放入緩存池
                [cell removeFromSuperview];
                [self.displayingCells removeObjectForKey:@(i)];  //這個字典是用來記錄正展示在屏幕上得數組的,沒有在
                                                                // 屏幕上了,應當移除相應的cell
                //放入緩存池
                [self.reusableCells addObject:cell];            //既然要循環利用,那么創建了,就不應該在
                                                                //ZYWaterFlowView生命周期還未結束之前被銷毀
                                                                //那么將之放入緩存池
            }
        }
        
    }
}

 接下來,應擔是緩存池的實現主要代碼,其實有了上面的鋪墊,是很簡單的,主要是根據identifier找到對應identifier的cell,如果沒找到,就返回nil,下面試代碼:

//需要根據標示符去緩存池找到對應的cell
- (id)dequeueReusableCellWithIdentifier:(NSString *)identifier
{
    __block ZYWaterFlowViewCell *cell;
    [self.reusableCells enumerateObjectsUsingBlock:^(ZYWaterFlowViewCell *obj, BOOL *stop) {
        if ([obj.identifier isEqualToString:identifier]) {
            cell = obj;
            *stop = YES;
        }
    }];
    
    if (cell) {  //被用了,就從緩存池中移除
        [self.reusableCells removeObject:cell];
    }
    return cell;
}

 相對應的,上面所說的ZYWaterFlowViewCell的設計不合理,事實上,參考UITableViewCell的設計,其實ZYWaterFlowViewCell只需要有一個identifier屬性就好,如何你要用的cell有很復雜的控件,那么只需要繼承ZYWaterFlowViewCell,給identifier標示,就可以了,用法和UITableViewCell一致,所以,我最終的ZYWaterFlowViewCell是這樣設計的:

#import <UIKit/UIKit.h>

@interface ZYWaterFlowViewCell : UIView
@property (nonatomic, copy) NSString *identifier;

- (instancetype)initWithIdentifier:(NSString *)identifier;
@end



#import "ZYWaterFlowViewCell.h"

@implementation ZYWaterFlowViewCell
- (instancetype)initWithIdentifier:(NSString *)identifier
{
    if (self = [super init]) {
        self.identifier = identifier;
    }
    return self;
}
@end

 寫到了這,最主要的功能就剩下點擊時間沒處理了,比如說,我點擊了某個cell,想要對它進行一定的處理,也就是ZYWaterFlowViewDelegate里面的這個方法碰觸時:

//返回被選中的(孩子~~)cell

- (ZYWaterFlowViewCell *)waterFlowView:(ZYWaterFlowView *)waterFlowView didSelectedAtIndex:(NSUInteger)index;

 

如何實現這樣一個功能呢?其實就是在touches系類方法里面,判斷此次點擊事件在哪個位置,然后哪個cell包含這個位置,調用上面的方法,返回響應索引即可,代碼:

- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
    //如果沒有實現此代理,直接返回
    if (![self.delegate respondsToSelector:@selector(waterFlowView:didSelectedAtIndex:)]) return;
    
    UITouch *touch = [touches anyObject];
    CGPoint currentPoint = [touch locationInView:self];
    
    __block NSNumber *index = nil;
    //判斷觸摸點在哪個cell上,沒必要遍歷所有的cell,只需要遍歷,當前展示在scrollView的cell(屏幕更好)
    [self.displayingCells enumerateKeysAndObjectsUsingBlock:^(id key, ZYWaterFlowViewCell *obj, BOOL *stop) {
        if (CGRectContainsPoint(obj.frame, currentPoint)) {
            index = key;
            *stop = YES;
        }
    }];
    if (index) {
        [self.delegate waterFlowView:self didSelectedAtIndex:index.unsignedIntegerValue];
    }
}

 下面是真個.m文件的代碼,一些次要的東西,就沒有詳細說明了,我覺得,這玩意主要是思路,因為相對來說,技術是學不完的,最值錢的是思想。

#import "ZYWaterFlowView.h"
#import "ZYWaterFlowViewCell.h"
#define ZYWaterFlowViewDefaultNumberOfColumns 3
#define ZYWaterFlowViewDefaultCellH 65
#define ZYWaterFlowViewDefaultMargin 10

@interface ZYWaterFlowView ()
//存放所有cell的frame
@property (nonatomic, strong)NSMutableArray *cellFrames;
//存放在scrollView的cell,之所以用字典,因為可以用key來存取,方便添加和移除
//當,發現當前key值得cell在字典里面(也就是還在scrollView上,也許正在屏幕上,也許沒有在屏幕上,但在scrollView上),直接從字典中取出即可,如果字典中不存在
//則去緩存池里取,如果緩存池沒有,那么創建cell
@property (nonatomic, strong)NSMutableDictionary *displayingCells;

//“緩存池”,存放離開屏幕的cell
@property (nonatomic, strong)NSMutableSet *reusableCells;
@end

@implementation ZYWaterFlowView

- (NSMutableArray *)cellFrames
{
    if (_cellFrames == nil) {
        _cellFrames = [NSMutableArray array];
    }
    return _cellFrames;
}

- (NSMutableDictionary *)displayingCells
{
    if (_displayingCells == nil) {
        _displayingCells = [NSMutableDictionary dictionary];
    }
    return _displayingCells;
}

- (NSMutableSet *)reusableCells
{
    if (_reusableCells == nil) {
        _reusableCells = [NSMutableSet set];
    }
    return _reusableCells;
}

- (CGFloat)cellWidth
{
    int numberOfColumns = (int)[self numberOfColumns];
    CGFloat marginOfColumn = [self marginForType:ZYWaterFlowViewMarginTypeColumn];
    CGFloat marginOfRight = [self marginForType:ZYWaterFlowViewMarginTypeRight];
    CGFloat marginOfLeft = [self marginForType:ZYWaterFlowViewMarginTypeLeft];
    CGFloat cellW = (self.frame.size.width - (numberOfColumns - 1) * marginOfColumn - marginOfLeft - marginOfRight) / numberOfColumns;
    return cellW;
}

//刷新數據
- (void)reloadData
{
    
    
    [self.displayingCells.allValues makeObjectsPerformSelector:@selector(removeFromSuperview)];//先把當前展示在scrollView上得移除,再去清空其他容器
    [self.displayingCells removeAllObjects];
    [self.cellFrames removeAllObjects];
    [self.reusableCells removeAllObjects];
    int numberOfCells = (int)[self.dataSource numberOfCellsInWaterFlowView:self];
    
    int numberOfColumns = (int)[self numberOfColumns];
    
    CGFloat marginOfTop = [self marginForType:ZYWaterFlowViewMarginTypeTop];
    CGFloat marginOfBottom = [self marginForType:ZYWaterFlowViewMarginTypeBottom];
    CGFloat marginOfRow = [self marginForType:ZYWaterFlowViewMarginTypeRow];
    CGFloat marginOfLeft = [self marginForType:ZYWaterFlowViewMarginTypeLeft];
    CGFloat marginOfColumn = [self marginForType:ZYWaterFlowViewMarginTypeColumn];
    //這里,使用一個c語言數組,來裝載每一列的最大高度
    //為什么不是oc數組?因為oc數組要裝對象,還不能預先開好位置
    CGFloat maxYOfColumns[numberOfColumns];
    for (int i = 0; i < numberOfColumns; i++) {
        maxYOfColumns[i] = 0.0;
    }
    
    CGFloat cellW = [self cellWidth];
    
    for (int i = 0; i < numberOfCells; i++) {
        //最小的y所在列
        int minYAtCol = 0;
        //所有列中最小的y
        CGFloat minY = maxYOfColumns[minYAtCol];
        
        for (int j = 1; j < numberOfColumns; j++) {
            if (minY > maxYOfColumns[j]) {
                minY = maxYOfColumns[j];
                minYAtCol = j;
            }
        }
        CGFloat cellH = [self cellHeightAtIndex:i];
        
        CGFloat cellX = marginOfLeft + minYAtCol * (cellW + marginOfColumn);
        
        CGFloat cellY = 0;
        
        if (minY == 0.0) {
            cellY = marginOfTop;
        }else
        {
            cellY = minY + marginOfRow;
        }
        
        CGRect cellFrame = CGRectMake(cellX, cellY, cellW, cellH);
        [self.cellFrames addObject:[NSValue valueWithCGRect:cellFrame]];
        
        //更新這一行的y
        maxYOfColumns[minYAtCol] = CGRectGetMaxY(cellFrame);
    }
    
    //設置scrollView的滾動范圍
    CGFloat maxY = maxYOfColumns[0];
    for (int i = 1; i < numberOfColumns; i++) {
        if (maxY < maxYOfColumns[i]) {
            maxY = maxYOfColumns[i];
        }
    }
    maxY += marginOfBottom;
    self.contentSize = CGSizeMake(0, maxY);
}

//當scrollView滾動,除了會調用它的代理方法之外,還會時時調用這個方法
//所以,在這個方法里面拿到當前顯示在屏幕的cell,設置尺寸~~
- (void)layoutSubviews
{
    [super layoutSubviews];
    
    int numberOfCells = (int)[self.dataSource numberOfCellsInWaterFlowView:self];
    
    for (int i = 0; i < numberOfCells; i++) {
        //先在scrollView上存在的cell里看當前cell是否存在scrollView(self)上
        ZYWaterFlowViewCell *cell = self.displayingCells[@(i)];
        CGRect cellF = [self.cellFrames[i] CGRectValue];
        
        if ([self isInScreen:cellF]) { //判斷當前cell是否有在屏幕展示(注意,這里與在scrollView(self)上展示不同)
            if (cell == nil) {  //需要在屏幕展示,所以cell不存在的時候需要創建
                cell = [self.dataSource waterFlowView:self cellAtIndex:i];
                cell.frame = cellF;
                [self addSubview:cell];
                self.displayingCells[@(i)] = cell;  //添加到了scrollView上,所以將它加入字典中
            }
        }else
        {
            if (cell) { //沒在屏幕上,所以不需要cell,把它放入緩存池
                [cell removeFromSuperview];
                [self.displayingCells removeObjectForKey:@(i)];  //這個字典是用來記錄正展示在屏幕上得數組的,沒有在
                                                                // 屏幕上了,應當移除相應的cell
                //放入緩存池
                [self.reusableCells addObject:cell];            //既然要循環利用,那么創建了,就不應該在
                                                                //ZYWaterFlowView生命周期還未結束之前被銷毀
                                                                //那么將之放入緩存池
            }
        }
        
    }
}

//需要根據標示符去緩存池找到對應的cell
- (id)dequeueReusableCellWithIdentifier:(NSString *)identifier
{
    __block ZYWaterFlowViewCell *cell;
    [self.reusableCells enumerateObjectsUsingBlock:^(ZYWaterFlowViewCell *obj, BOOL *stop) {
        if ([obj.identifier isEqualToString:identifier]) {
            cell = obj;
            *stop = YES;
        }
    }];
    
    if (cell) {  //被用了,就從緩存池中移除
        [self.reusableCells removeObject:cell];
    }
    return cell;
}


#pragma Private方法

//判斷是否在屏幕上,只需要,最大的y大於contentOffset.y,最小的y小於contentOffset.y + self高度
- (BOOL)isInScreen:(CGRect)rect
{
    return (CGRectGetMaxY(rect) > self.contentOffset.y) && (CGRectGetMinY(rect) < self.contentOffset.y + self.frame.size.height);
}

- (CGFloat)cellHeightAtIndex:(int)index
{
    if ([self.delegate respondsToSelector:@selector(waterFlowView:heightAtIndex:)]) {
        return [self.delegate waterFlowView:self heightAtIndex:index];
    }
    
    return ZYWaterFlowViewDefaultCellH;
}

- (int)numberOfColumns
{
    if ([self.dataSource respondsToSelector:@selector(numberOfColumnsInWaterFlowView:)]) {
        return (int)[self.dataSource numberOfColumnsInWaterFlowView:self];
    }
    
    return ZYWaterFlowViewDefaultNumberOfColumns;
}

- (CGFloat)marginForType:(ZYWaterFlowViewMarginType)type
{
    if ([self.delegate respondsToSelector:@selector(waterFlowView:marginForType:)]) {
        return [self.delegate waterFlowView:self marginForType:type];
    }
    
    return ZYWaterFlowViewDefaultMargin;
}

- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
    //如果沒有實現此代理,直接返回
    if (![self.delegate respondsToSelector:@selector(waterFlowView:didSelectedAtIndex:)]) return;
    
    UITouch *touch = [touches anyObject];
    CGPoint currentPoint = [touch locationInView:self];
    
    __block NSNumber *index = nil;
    //判斷觸摸點在哪個cell上,沒必要遍歷所有的cell,只需要遍歷,當前展示在scrollView的cell(屏幕更好)
    [self.displayingCells enumerateKeysAndObjectsUsingBlock:^(id key, ZYWaterFlowViewCell *obj, BOOL *stop) {
        if (CGRectContainsPoint(obj.frame, currentPoint)) {
            index = key;
            *stop = YES;
        }
    }];
    if (index) {
        [self.delegate waterFlowView:self didSelectedAtIndex:index.unsignedIntegerValue];
    }
}

//當view將要移到superView時,刷新數據,避免手動刷新
- (void)willMoveToSuperview:(UIView *)newSuperview
{
    [self reloadData];
}
@end

 

這是這個項目的github地址,medo已經集成上拉刷新、下拉刷新:https://github.com/wzpziyi1/waterFlowView-demo

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM