首页 > 系统 > iOS > 正文

iOS瀑布流的简单实现(Swift)

2020-07-26 03:03:51
字体:
来源:转载
供稿:网友

这段时间突然想到一个很久之前用到的知识-瀑布流,本来想用一个简单的方法,发现自己走入了歧途,最终只能狠下心来重写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, width: 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(width: 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 UIKitextension 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, width: 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(width: 0, height: maxH + sectionInset.bottom - minimumLineSpacing)  }}

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持武林网。

发表评论 共有条评论
用户名: 密码:
验证码: 匿名发表