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, 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 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, 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)
    }
}
时间: 2024-10-01 02:26:40

iOS瀑布流实现(Swift)的相关文章

IOS 瀑布流UICollectionView实现

IOS 瀑布流UICollectionView实现 在实现瀑布流之前先来看看瀑布流的雏形(此方法的雏形 UICollectionView) 对于UICollectionView我们有几点注意事项 它和tableView不一样,ContentView的内容完全需要我们自己去添加. 它与tableview相比,他的初始化需要FlowLayout并且大部分操作在其上. UIcollectionView的实用性极强,虽然有时他并不是最好的解决方案,但是它可以很灵活的实现各种效果. 图(一) 如图,模拟器

ios瀑布流

一般来说瀑布流主要有两种实现方式.方法一:使用UITableView.方法二:使用UIScrollView.先介绍方法一(也是官方推荐的方式)1. 总先做成几列是事先要清楚,有多少条记录.2. 假设要做成3列,就用三个uitableview,宽度平均,高度动态,页面高度取uitableview中最高的.3. 三个uitableview初始化的时候用到tag(我越来越觉得tag在ios中的用处很大,就像js中读取html控件中的id一样),然后 showsVerticalScrollIndicat

ios 瀑布流

瀑布流,也被称为瀑布流布局. 行的一种页面布局,视觉表现为參差不齐的多栏布局,随着页面滚动栏向下滚动,这样的布局还会不断载入数据块并附加至当前尾部. 说明:(1)瀑布流每个的宽度是一样的,都是高度不一样(2)补齐算法,哪里比較短就补哪里,不是简单的从左到右排(两列之间的差距越来越大). 在ios中眼下比較好的实现方式有2种.1.仿照UITableView思路自己定义ScrollView 2.使用UICollectionView,自己定义UICollectionViewLayout实现. 使用UI

ios图片瀑布流代码

ios瀑布流,实现简单的瀑布流视图布局,可以显示网络图片,下拉刷新,上拉加载更多. 下载:http://www.huiyi8.com/sc/9087.html ios图片瀑布流代码,布布扣,bubuko.com

iOS开发笔记15:地图坐标转换那些事、block引用循环、UICollectionviewLayout及瀑布流、图层混合

1.地图坐标转换那些事 (1)投影坐标系与地理坐标系 地理坐标系使用三维球面来定义地球上的位置,单位即经纬度.但经纬度无法精确测量距离戒面积,也难以在平面地图戒计算机屏幕上显示数据.通过投影的方式可以将其转换成平面的投影坐标系,不同的投影方式可能会带来不同的变形及误差,类似于把一个橘子的橘子皮剥开摊平到桌面. GPS以及iOS系统定位获得的坐标是地理坐标系WGS1984,Web地图一般用的坐标细是投影坐标系WGS 1984 Web Mercator,国内出于相关法律法规要求,对国内所有GPS设备

iOS运营级B2B服务平台App、自定义图标库、个人中心页面、识别身份证Demo、瀑布流等源码

iOS精选源码 简单的个人中心页面-自定义导航栏并予以渐变动画 一个近乎完整的可识别中国身份证信息的Demo 可自动快速... iOS可自定义图表库 - PNChart 开源一款曾是运营级的B2B服务平台APP<云采> 使用ffmpeg解码最简iOS播放器 注释得非常清楚的瀑布流,和自己的一些想法 iOS日志框架学习分享 在iOS App中录制MP3和AMR:ZWAudioRecordTool 一套应用于swift项目的空白页组件EmptyPage 2.0 扫雷简单实现 iOS优质博客 iOS

iOS开发UI篇—自定义瀑布流控件(蘑菇街数据刷新操作)

iOS开发UI篇—自定义瀑布流控件(蘑菇街数据刷新操作) 一.简单说明 使用数据刷新框架: 该框架提供了两种刷新的方法,一个是使用block回调(存在循环引用问题,_ _weak),一个是使用调用. 问题:在进行下拉刷新之前,应该要清空之前的所有数据(在刷新数据这个方法中). 移除正在显示的cell: (1)把字典中的所有的值,都从屏幕上移除 (2)清除字典中的所有元素 (3)清除cell的frame,每个位置的cell的frame都要重新计算 (4)清除可复用的缓存池. 该部分的代码如下: 1

iOS开发之窥探UICollectionViewController(三) --使用UICollectionView自定义瀑布流

上篇博客的实例是自带的UICollectionViewDelegateFlowLayout布局基础上来做的Demo, 详情请看<iOS开发之窥探UICollectionViewController(二) --详解CollectionView各种回调>.UICollectionView之所以强大,是因为其具有自定义功能,这一自定义就不得了啦,自由度非常大,定制的高,所以功能也是灰常强大的.本篇博客就不使用自带的流式布局了,我们要自定义一个瀑布流.自定义的瀑布流可以配置其参数: 每个Cell的边距

iOS开发之窥探UICollectionViewController(四) --一款功能强大的自定义瀑布流

在上一篇博客中<iOS开发之窥探UICollectionViewController(三) --使用UICollectionView自定义瀑布流>,自定义瀑布流的列数,Cell的外边距,Cell的最大以及最小高度是在我们的布局文件中是写死的,换句话说也就是不可配置的.为了循序渐进,由浅入深呢,上篇博客暂且那么写.不过那样写太过死板,本来使用起来比较灵活的自定义布局,如果把其配置参数给写死了,就相当于在笼中的猛兽,再厉害不也白扯蛮. 在今天这篇博客中我们要接着上篇博客中的Demo,使其自定义布局