美文网首页首页投稿(暂停使用,暂停投稿)
iOS开发UI篇-几句代码实现瀑布流

iOS开发UI篇-几句代码实现瀑布流

作者: willphonez | 来源:发表于2016-08-20 01:02 被阅读163次
瀑布流.gif

在开发中,瀑布流用的挺频繁的,尤其是在做一些电商应用的时候,由于错落有致的外观能防止用户在浏览商品时所产生的视觉疲劳,瀑布流就应运而生了,废话不多说,直接上Demo.

在这个Demo中,我对瀑布流布局做了个封装,实现瀑布流只需遵循下协议,实现几个代理方法即可,由于最近swift用得比较频繁,这个Demo就用swift来演示了.(需要OC版的可以私信我).

一.布局核心实现


** 要实现瀑布流相当于是自己写一个布局,因此需要继承于UICollectionViewLayout,重写里面的几个方法来确定布局:**

  • func prepareLayout()这个方法中对布局进行一些初始化的操作
// 初始化布局方法
override func prepareLayout() {
    super.prepareLayout()
    // 清空之前所有的列数的高度数据,并初始化
    columnHeightArray.removeAll()
    for _ in 0..<columnCount() {
        columnHeightArray.append(0)
    }
  
    // 清空之前所有cell的布局属性
    itemAttributeArray.removeAll()
    
    // collectionView中的cell的个数
    let count = collectionView?.numberOfItemsInSection(0) ?? 0
    
    for i in 0..<count {
        
        let indexPath = NSIndexPath(forItem: i, inSection: 0)
        
        // 根据indexPath设置对应的layoutAttributes
        let layoutAttribute = layoutAttributesForItemAtIndexPath(indexPath)!
        
        itemAttributeArray.append(layoutAttribute)
    }
    
}
  • 在这个方法中确定指定范围内(rect)的item的布局属性,由于指定范围内的item可能有多个,所以返回一个数组
override func layoutAttributesForElementsInRect(rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
    return itemAttributeArray
}
  • 确定每个item的布局属性(核心代码),在这个方法里确定每个item的frame
override func layoutAttributesForItemAtIndexPath(indexPath: NSIndexPath) -> UICollectionViewLayoutAttributes? {
    // 创建单个cell的布局属性
    let layoutAttribute = UICollectionViewLayoutAttributes(forCellWithIndexPath: indexPath)
    
    // 设置布局属性
    
    // collectionView的宽度
    let collectionViewW = collectionView!.frame.size.width
    // 一行中所有item的总宽度
    let itemWs = collectionViewW - collectViewEdgeInsets().left - collectViewEdgeInsets().right - CGFloat(columnCount() - 1) * columnMargin()
    
    // 找出最矮的一列
    // 假设第一列最矮
    var minHeight : CGFloat = columnHeightArray[0]
    var desRow : Int = 0
    for i in 1..<columnHeightArray.count {
        let height = columnHeightArray[i]
        
        if height < minHeight {
            minHeight = height
            desRow = i
        }
    }
    
    // 确定item的frame
    let w : CGFloat = itemWs /  CGFloat(columnCount())
    let x : CGFloat = collectViewEdgeInsets().left + (columnMargin() + w) * CGFloat(desRow)
    let y : CGFloat = columnHeightArray[desRow] + rowMargin()
    let h : CGFloat = (delegate?.waterFlowLayout(self, heightForItemAtIndex: indexPath.item, itemWidth: w))!
    layoutAttribute.frame = CGRect(x: x, y: y, width: w, height: h)
    
    // 更新列高度数据
    columnHeightArray[desRow] = CGRectGetMaxY(layoutAttribute.frame)
    
    return layoutAttribute
}
  • 确定collectionView的contentSize
override func collectionViewContentSize() -> CGSize {
    // 找出最高的一列
    // 假设第一列最高
    var maxHeight : CGFloat = columnHeightArray[0]
    var desRow : Int = 0
    for i in 1..<columnHeightArray.count {
        let height = columnHeightArray[i]
        
        if height > maxHeight {
            maxHeight = height
            desRow = i
        }
    }
    
    return CGSize(width: 0, height: columnHeightArray[desRow] + rowMargin())
}

二.布局的一些相关数据(item的大小,item间的间距等)


  • 对于和布局所需的数据(比如item的高度),需要由实际显示的图片大小来决定,在这里数据的传输有两种方式:

  • 第一种是在布局类对象里定义一个item的高度属性,在外界用到的时候给这个属性赋值,这个方式虽然方便快捷,但一方面,代码的耦合性太强了,不一定每次显示的东西都是一样的,换到别的地方可能就不能用了;另一方面,定义了这个属性相当于给外界反复修改item的高度提供了可能,而item高度一改,内部瀑布流的布局又要重新刷新,耗性能

  • 另一种方式就是通过代理来实现数据的传输,只需要将布局所需的属性,定义成代理方法,外界实现代理方法,实现数据传输,这种方法完美的消除了耦合性,而且能控制用户的反复输入,方法的调用时机也有布局对象内部决定,本案例采取的就是这个方法

  • 定义一份协议用来让外界传递瀑布流所需的相应的数据

@objc protocol ZWFWaterFlowLayoutDelegate {
    // 每个item的高度
    func waterFlowLayout(waterLayout: ZWFWaterFlowLayout, heightForItemAtIndex index: NSInteger, itemWidth : CGFloat) -> CGFloat
    // collectionView的列数
    optional func columnCountInWaterFlow(waterLayout : ZWFWaterFlowLayout) -> Int
    // item间的列间距
    optional func columnMarginInWaterFlow(waterLayout : ZWFWaterFlowLayout) -> CGFloat
    // item间的行间距
    optional func rowMarginInWaterFlow(waterLayout : ZWFWaterFlowLayout) -> CGFloat
    // collectionView的内边距
    optional func collectViewEdgeInWaterFlow(waterLayout : ZWFWaterFlowLayout) -> UIEdgeInsets
}
  • 将属性定义成方法是为了方便集中管理,以及设置默认值
// MARK:- 一些基本属性(由外界提供)
extension ZWFWaterFlowLayout {
    // MARK:- 列间距
    func columnMargin() -> CGFloat {
        // 校验有没有代理
        if delegate == nil {  // 默认列间距为10
            return 10
        }
        // 校验代理有没有实现方法
        guard let margin = delegate!.columnMarginInWaterFlow?(self) else { // 默认列间距为10
            return 10
        }
        return margin
    }
    // MARK:- 列数
    func columnCount() -> Int {
        // 校验有没有代理
        if delegate == nil { // 默认列数为3
            return 3
        }
        // 校验代理有没有实现方法
        guard let count = delegate!.columnCountInWaterFlow?(self) else { // 默认列数为3
            return 3
        }
        return count
    }
    // MARK:- 行间距
    func rowMargin() -> CGFloat {
        // 校验有没有代理
        if delegate == nil {  // 默认行间距为10
            return 10
        }
        // 校验代理有没有实现方法
        guard let margin = delegate!.rowMarginInWaterFlow?(self) else {  // 默认行间距为10
            return 10
        }
        return margin
    }
    // MARK:- 四边内间距
    func collectViewEdgeInsets() -> UIEdgeInsets {
        // 校验有没有代理
        if delegate == nil {  // 默认四周内边距为10
            return UIEdgeInsets(top: 10, left: 10, bottom: 10, right: 10)
        }
        // 校验代理有没有实现方法
        guard let edgeInsets = delegate!.collectViewEdgeInWaterFlow?(self) else {  // 默认四周内边距为10
            return UIEdgeInsets(top: 10, left: 10, bottom: 10, right: 10)
        }
        return edgeInsets
    }
}

三.布局的使用


import UIKit

// MARK:- 主函数
class ViewController: UIViewController {
    
    // MARK:- 懒加载控件
    private lazy var collectionView = UICollectionView()
    // 所有的商品数据
    private lazy var shops = [ShopItem]()
    
    // cell的标识
    let ID : String = "cell"
    
    // MARK:- 系统回调函数
    override func viewDidLoad() {
        super.viewDidLoad()
        
        // 初始化collectionView
        setupCollectionView()
       
        // 加载数据
        setupRefresh()
    }
}

// MARK:- 加载数据
extension ViewController {
    
    func setupRefresh() {
        // 设置下拉刷新
        collectionView.header = MJRefreshNormalHeader(refreshingTarget: self, refreshingAction: "loadData")
        collectionView.header.beginRefreshing()
        
        // 设置上拉加载更多数据
        collectionView.footer = MJRefreshAutoNormalFooter(refreshingTarget: self, refreshingAction: "loadMoreData")
        collectionView.footer.beginRefreshing()
    }
    
    // 加载数据
    @objc private func loadData() {
        
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, Int64(UInt64(2.0) * NSEC_PER_SEC)), dispatch_get_main_queue()) { () -> Void in
            
            let path = NSBundle.mainBundle().pathForResource("1.plist", ofType: nil)!
            let shopArray = NSArray(contentsOfFile: path)!
            
            // 清空保存的所有数据
            self.shops.removeAll()
            
            for dict in shopArray {
                let shop = ShopItem.init(dict: dict as! [String : NSObject])
                self.shops.append(shop)
            }
            
            self.collectionView.reloadData()
            self.collectionView.header.endRefreshing()
        }
        
    }
    
    // 加载更多数据
    @objc private func loadMoreData() {
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, Int64(UInt64(2.0) * NSEC_PER_SEC)), dispatch_get_main_queue()) { () -> Void in
            
            let path = NSBundle.mainBundle().pathForResource("1.plist", ofType: nil)!
            let shopArray = NSArray(contentsOfFile: path)!
            for dict in shopArray {
                let shop = ShopItem.init(dict: dict as! [String : NSObject])
                self.shops.append(shop)
            }
            
            self.collectionView.reloadData()
            self.collectionView.footer.endRefreshing()
        }
    }
    
}


// MARK:- 初始化collectionView
extension ViewController {
    
    private func setupCollectionView() {
        
        // 创建瀑布流布局
        let waterFlowLayout = ZWFWaterFlowLayout()
        waterFlowLayout.delegate = self
        
        // 创建collectionView
        let collectionView = UICollectionView(frame: view.bounds, collectionViewLayout: waterFlowLayout)
        
        collectionView.dataSource = self
        collectionView.backgroundColor = UIColor.whiteColor()
        
        // 注册cell
        collectionView.registerClass(ShopCell.self, forCellWithReuseIdentifier: ID)
        
        self.collectionView = collectionView
        view.addSubview(collectionView)
    }
}

// MARK:- UICollectionViewDataSource
extension ViewController : UICollectionViewDataSource {
    
    func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        collectionView.footer.hidden = shops.count == 0
        return shops.count
    }
    
    func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
        // 取出cell
        let cell = collectionView.dequeueReusableCellWithReuseIdentifier(ID, forIndexPath: indexPath) as! ShopCell
        
        // 设置cell的属性
        cell.shop = shops[indexPath.item];
        
        return cell
    }
    
}

// MARK:- CollectionViewWaterLayoutDelegate
extension ViewController : ZWFWaterFlowLayoutDelegate {
    
    // 返回每个item的高度
    func waterFlowLayout(waterLayout: ZWFWaterFlowLayout, heightForItemAtIndex index: NSInteger, itemWidth : CGFloat) -> CGFloat {
        
        let shop = shops[index]
        
        return shop.h * itemWidth / shop.w
    }
    
    // 返回collectionView的列数
    func columnCountInWaterFlow(waterLayout: ZWFWaterFlowLayout) -> Int {
        return 3
    }
    
    // 返回列间距
    func columnMarginInWaterFlow(waterLayout: ZWFWaterFlowLayout) -> CGFloat {
        return 20
    }
    
    // 返回行间距
    func rowMarginInWaterFlow(waterLayout: ZWFWaterFlowLayout) -> CGFloat {
        return 20
    }
    
    // 返回collectionView的内边距
    func collectViewEdgeInWaterFlow(waterLayout: ZWFWaterFlowLayout) -> UIEdgeInsets {
        return UIEdgeInsets(top: 20, left: 10, bottom: 30, right: 20)
    }
    
}

Demo以上传github,欢迎下载,如有错漏之处,欢迎指正瀑布流源码

相关文章

  • iOS开发UI篇-几句代码实现瀑布流

    在开发中,瀑布流用的挺频繁的,尤其是在做一些电商应用的时候,由于错落有致的外观能防止用户在浏览商品时所产生的视觉疲...

  • 关于自定义瀑布流的布局(Swift)

    瀑布流 UICollectionView Swift 最近整理以前iOS开发中用到的功能代码,发觉对瀑布流的布局有...

  • 瀑布流布局 的学习

    1- 实现瀑布流布局效果 瀑布流效果瀑布流代码木桶布局效果木桶布局代码瀑布流布局 2- 实现一个新闻瀑布流新闻...

  • js/jQuery实现瀑布流

    html中主要代码: 实现瀑布流的js代码: css实现瀑布流只需要三行代码: 实现下拉刷新的js代码: js/c...

  • Swift实现UICollectionView支持多 secti

    通过UICollectionViewLayout实现瀑布流 代码如下 调用如下 完整demo链接 瀑布流

  • UICollectionViewLayout之瀑布流

    原帖子:华山论剑之浅谈iOS瀑布流 我是模仿上面的代码自己写了一份,实现的是简单的瀑布流:就是把UICollect...

  • iOS使用UICollectionView实现瀑布流

    UICollectionView实现瀑布流 在iOS中可以实现瀑布流的目前已知的有2种方案: 使用UIScroll...

  • 瀑布流布局

    题目1: 实现一个瀑布流布局效果瀑布流代码题目2:实现木桶布局效果木桶布局代码题目3:实现一个新闻瀑布流新闻网站,...

  • UI常用的控件

    #iOS开发之UI篇#iOS开发之UI篇 #常用控件介绍1## #UI第09天:滚动视图# ##UIScrollV...

  • iOS UICollectionViewFlowLayout流式

    新年第一发、先来个iOS温习温习 实现瀑布流这个瀑布流是16年写的了、那个时候没怎么玩简书 。现在把代码粘贴过来

网友评论

    本文标题:iOS开发UI篇-几句代码实现瀑布流

    本文链接:https://www.haomeiwen.com/subject/kktisttx.html