zoukankan      html  css  js  c++  java
  • iOS瀑布流实现(Swift)

    这段时间突然想到一个很久之前用到的知识-瀑布流,本来想用一个简单的方法,发现自己走入了歧途,最终只能狠下心来重写UICollectionViewFlowLayout.下面我将用两种方法实现瀑布流,以及会介绍第一种实现的bug.

    <1>第一种

    效果图如下所示:

    这种实现方法的思路:  

      1)首先调用随机函数,产生随机高度,并把它保存到数组中

    - (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath {
        CGFloat cellW = 100;
        CGFloat cellH = 100 + (arc4random() % 80);
        [self.heightArrayM addObject:@(cellH)];
        
        return CGSizeMake(cellW, cellH);
        
    }

     2)在设置cell的frame的地方,通过取余,取整确定cell的高度,并设定cell的frame

    - (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
        
        UICollectionViewCell *cell = [self.collectionView dequeueReusableCellWithReuseIdentifier:ID forIndexPath:indexPath];
        //当前处于多少行
        NSInteger num1 = indexPath.row / count;
        //当前处于多少列
        int num2 = indexPath.row % count;
        CGFloat cellX = num2 * 100 + (num2 + 1) * margin;
        CGFloat cellY = 0;
        for (int i = 0; i < num1; i++) {
            NSInteger position =  num2 + i * 3;
            cellY += [self.heightArrayM[position] floatValue] + margin;
        }
        CGFloat cellW = 100;
        CGFloat cellH = cellHeight;
        cell.frame = CGRectMake(cellX, cellY, cellW, cellH);
    //    cell.backgroundColor = [UIColor redColor];
        cell.backgroundColor = [UIColor colorWithRed:(arc4random() % 250) / 250.0 green:(arc4random() % 250) / 250.0 blue:(arc4random() % 250) / 250.0 alpha:1.0];
        
    //    NSLog(@"%@", NSStringFromCGRect(cell.frame)); 
        return cell;
    }

    弊端 : 其实这种方法的弊端,相信从上面的动态图中可以看出来,当往上面滑的时候,由于cell的循环机制,下面的cell的会消失,但是由于高度不一致,同时撤销的是最后一行的cell,所以下面的cell在屏幕上就会消失.

    下面附上第一种方法的源代码:

    #import "ViewController.h"
    
    #define margin 10
    #define count 3
    #define cellHeight [self.heightArrayM[indexPath.row] floatValue]
    static NSString * const ID = @"cell";
    @interface ViewController ()<UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout>
    @property (weak, nonatomic) IBOutlet UICollectionView *collectionView;
    @property (nonatomic, strong) NSMutableArray *heightArrayM;
    
    @end
    
    @implementation ViewController
    
    - (NSMutableArray *)heightArrayM {
        if (_heightArrayM == nil) {
            _heightArrayM = [NSMutableArray array];
        }
        return _heightArrayM;
    }
    
    - (void)viewDidLoad {
        [super viewDidLoad];
        
        [self.collectionView registerClass:[UICollectionViewCell class] forCellWithReuseIdentifier:ID];
        self.collectionView.dataSource = self;
        self.collectionView.delegate = self;
        //设置collectionView
        [self setupCollectionView];
    }
    
    //设置collectionView的布局
    - (UICollectionViewFlowLayout *)setupCollectionLayout {
        UICollectionViewFlowLayout *flowLayout = [[UICollectionViewFlowLayout alloc] init];
       
        flowLayout.minimumInteritemSpacing = margin;
        flowLayout.minimumLineSpacing = margin;
        flowLayout.sectionInset = UIEdgeInsetsMake(margin, margin, margin, margin);
        return flowLayout;
    }
    
    //设置collectionView
    - (void)setupCollectionView {
        self.collectionView.collectionViewLayout =[self setupCollectionLayout];
        
    }
    
    #pragma mark - UICollectionViewDataSouce
    - (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {
        return 60;
    }
    
    - (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
        
        UICollectionViewCell *cell = [self.collectionView dequeueReusableCellWithReuseIdentifier:ID forIndexPath:indexPath];
        //当前处于多少行
        NSInteger num1 = indexPath.row / count;
        //当前处于多少列
        int num2 = indexPath.row % count;
        CGFloat cellX = num2 * 100 + (num2 + 1) * margin;
        CGFloat cellY = 0;
        for (int i = 0; i < num1; i++) {
            NSInteger position =  num2 + i * 3;
            cellY += [self.heightArrayM[position] floatValue] + margin;
        }
        CGFloat cellW = 100;
        CGFloat cellH = cellHeight;
        cell.frame = CGRectMake(cellX, cellY, cellW, cellH);
    //    cell.backgroundColor = [UIColor redColor];
        cell.backgroundColor = [UIColor colorWithRed:(arc4random() % 250) / 250.0 green:(arc4random() % 250) / 250.0 blue:(arc4random() % 250) / 250.0 alpha:1.0];
        
    //    NSLog(@"%@", NSStringFromCGRect(cell.frame)); 
        return cell;
    }
    
    - (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath {
        CGFloat cellW = 100;
        CGFloat cellH = 100 + (arc4random() % 80);
        [self.heightArrayM addObject:@(cellH)];
        
        return CGSizeMake(cellW, cellH);
        
    }
    @end

    <2>下面介绍第二种(Swift实现)

    效果图如下所示:

    这种实现方法就是比较成熟的了,我把它封装成一个类.其实主要是实现三个函数

      1)重写父类的prepare方法,准备所有cell的样式

    extension WaterfallLayout {
        // prepare准备所有Cell的布局样式
        override func prepare() {
            super.prepare()
            
            // 0.获取item的个数
            let itemCount = collectionView!.numberOfItems(inSection: 0)
            
            // 1.获取列数
            let cols = dataSource?.numberOfColsInWaterfallLayout?(self) ?? 2
            
            // 2.计算Item的宽度
            let itemW = (collectionView!.bounds.width - self.sectionInset.left - self.sectionInset.right - self.minimumInteritemSpacing * CGFloat((cols - 1))) / CGFloat(cols)
            
            // 3.计算所有的item的属性
            for i in startIndex..<itemCount {
                // 1.设置每一个Item位置相关的属性
                let indexPath = IndexPath(item: i, section: 0)
                
                // 2.根据位置创建Attributes属性
                let attrs = UICollectionViewLayoutAttributes(forCellWith: indexPath)
                
                // 3.随机一个高度
                guard let height = dataSource?.waterfallLayout(self, indexPath: indexPath) else {
                    fatalError("请设置数据源,并且实现对应的数据源方法")
                }
                
                // 4.取出最小列的位置
                var minH = colHeights.min()!
                let index = colHeights.index(of: minH)!
                minH = minH + height + minimumLineSpacing
                colHeights[index] = minH
                
                // 5.设置item的属性
                attrs.frame = CGRect(x: self.sectionInset.left + (self.minimumInteritemSpacing + itemW) * CGFloat(index), y: minH - height - self.minimumLineSpacing,  itemW, height: height)
                attrsArray.append(attrs)
            }
            
            // 4.记录最大值
            maxH = colHeights.max()!
            
            // 5.给startIndex重新复制
            startIndex = itemCount
        }
    }

      2)返回设置cell样式的数组

     override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
            return attrsArray
        }

      3)返回当前的contentSize

    override var collectionViewContentSize: CGSize {
            return CGSize( 0, height: maxH + sectionInset.bottom - minimumLineSpacing)
        }

    总结:

    在下面我封装的这个类中,只需要遵守我的数据代理源协议并且实现我的协议中的两个方法,传给我对应得高度(我这里是传的随机的),可选的方法,若是不实现,会有一个默认值,就可以实现该功能.协议如下:

    @objc protocol WaterfallLayoutDataSource : class {
        func waterfallLayout(_ layout : WaterfallLayout, indexPath : IndexPath) -> CGFloat
        @objc optional func numberOfColsInWaterfallLayout(_ layout : WaterfallLayout) -> Int
    }

    完成代码如下所示:
    ViewController.swift中的代码:

    import UIKit
    
    
    extension UIColor {
        class func randomColor() -> UIColor {
            return UIColor(colorLiteralRed: Float(arc4random_uniform(256)) / 255.0, green: Float(arc4random_uniform(256)) / 255.0, blue: Float(arc4random_uniform(256)) / 255.0, alpha: 1.0)
        }
    }
    
    private let kWaterCellID = "kWaterCellID"
    
    class ViewController: UIViewController {
        
        var count : Int = 20
        
        override func viewDidLoad() {
            super.viewDidLoad()
            
            // 1.设置布局
            let layout = WaterfallLayout()
            layout.minimumLineSpacing = 10
            layout.minimumInteritemSpacing = 10
            layout.sectionInset = UIEdgeInsets(top: 10, left: 10, bottom: 10, right: 10)
            layout.dataSource = self
            
            // 2.创建UICollectionView
            let collectionView = UICollectionView(frame: view.bounds, collectionViewLayout: layout)
            collectionView.dataSource = self
            collectionView.register(UICollectionViewCell.self, forCellWithReuseIdentifier: kWaterCellID)
            view.addSubview(collectionView)
        }
        
    }
    
    extension ViewController : UICollectionViewDataSource {
        func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
            return count
        }
        
        func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
            let cell = collectionView.dequeueReusableCell(withReuseIdentifier: kWaterCellID, for: indexPath)
            
            cell.backgroundColor = UIColor.randomColor()
            
            if indexPath.item == count - 1 {
                count += 20
                
                collectionView.reloadData()
            }
            
            return cell
        }
    }
    
    
    extension ViewController : WaterfallLayoutDataSource {
        func waterfallLayout(_ layout: WaterfallLayout, indexPath: IndexPath) -> CGFloat {
            return CGFloat(arc4random_uniform(80) + 100)
        }
        
        func numberOfColsInWaterfallLayout(_ layout: WaterfallLayout) -> Int {
            return 3
        }
    }

    封装自定义布局中的WaterfallLayout.swift代码如下:

    import UIKit
    
    @objc protocol WaterfallLayoutDataSource : class {
        func waterfallLayout(_ layout : WaterfallLayout, indexPath : IndexPath) -> CGFloat
        @objc optional func numberOfColsInWaterfallLayout(_ layout : WaterfallLayout) -> Int
    }
    
    class WaterfallLayout: UICollectionViewFlowLayout {
        
        // MARK: 对外提供属性
        weak var dataSource : WaterfallLayoutDataSource?
        
        // MARK: 私有属性
        fileprivate lazy var attrsArray : [UICollectionViewLayoutAttributes] = [UICollectionViewLayoutAttributes]()
        
        fileprivate var totalHeight : CGFloat = 0
        fileprivate lazy var colHeights : [CGFloat] = {
            let cols = self.dataSource?.numberOfColsInWaterfallLayout?(self) ?? 2
            var colHeights = Array(repeating: self.sectionInset.top, count: cols)
            return colHeights
        }()
        fileprivate var maxH : CGFloat = 0
        fileprivate var startIndex = 0
    }
    
    
    extension WaterfallLayout {
        // prepare准备所有Cell的布局样式
        override func prepare() {
            super.prepare()
            
            // 0.获取item的个数
            let itemCount = collectionView!.numberOfItems(inSection: 0)
            
            // 1.获取列数
            let cols = dataSource?.numberOfColsInWaterfallLayout?(self) ?? 2
            
            // 2.计算Item的宽度
            let itemW = (collectionView!.bounds.width - self.sectionInset.left - self.sectionInset.right - self.minimumInteritemSpacing * CGFloat((cols - 1))) / CGFloat(cols)
            
            // 3.计算所有的item的属性
            for i in startIndex..<itemCount {
                // 1.设置每一个Item位置相关的属性
                let indexPath = IndexPath(item: i, section: 0)
                
                // 2.根据位置创建Attributes属性
                let attrs = UICollectionViewLayoutAttributes(forCellWith: indexPath)
                
                // 3.随机一个高度
                guard let height = dataSource?.waterfallLayout(self, indexPath: indexPath) else {
                    fatalError("请设置数据源,并且实现对应的数据源方法")
                }
                
                // 4.取出最小列的位置
                var minH = colHeights.min()!
                let index = colHeights.index(of: minH)!
                minH = minH + height + minimumLineSpacing
                colHeights[index] = minH
                
                // 5.设置item的属性
                attrs.frame = CGRect(x: self.sectionInset.left + (self.minimumInteritemSpacing + itemW) * CGFloat(index), y: minH - height - self.minimumLineSpacing,  itemW, height: height)
                attrsArray.append(attrs)
            }
            
            // 4.记录最大值
            maxH = colHeights.max()!
            
            // 5.给startIndex重新复制
            startIndex = itemCount
        }
    }
    
    extension WaterfallLayout {
        override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
            return attrsArray
        }
        
        override var collectionViewContentSize: CGSize {
            return CGSize( 0, height: maxH + sectionInset.bottom - minimumLineSpacing)
        }
    }
  • 相关阅读:
    最短路径覆盖问题
    js 程序执行与顺序实现详解
    ajax中的application/x-www-form-urlencoded中的使用[转]
    javascript跨浏览器操作xml
    javascript 正则表达式
    JavaScript面向对象编程(2)-- 类的定义
    JavaScript面向对象编程(1)-- 基础
    constructor、prototype、isPrototypeOf、instanceof、in 、hasOwnProperty
    valueOf()对象返回值
    javascript中的toString()、toLocaleString()方法
  • 原文地址:https://www.cnblogs.com/muzichenyu/p/6108040.html
Copyright © 2011-2022 走看看