首页投稿(暂停使用,暂停投稿)

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

2016-08-20  本文已影响163人  willphonez
瀑布流.gif

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

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

一.布局核心实现


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

// 初始化布局方法
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)
    }
    
}
override func layoutAttributesForElementsInRect(rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
    return itemAttributeArray
}
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
}
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间的间距等)


@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,欢迎下载,如有错漏之处,欢迎指正瀑布流源码

上一篇 下一篇

猜你喜欢

热点阅读