Swift - UICollectionView 瀑布流

使用Swift實(shí)現(xiàn)簡(jiǎn)單的瀑布流,OC的實(shí)現(xiàn)和Swift大同小異互亮,主要還是了解一下實(shí)現(xiàn)思路犁享,我這里做了三種效果:

  1. 豎向滑動(dòng),不帶Header和Footer豹休;
  2. 豎向滑動(dòng)炊昆,帶自定義Header和Footer;
  3. 橫向滑動(dòng)威根,不帶Header和Footer凤巨;

效果如下:


豎向滑動(dòng)(無Header和Footer).png
豎向滑動(dòng)(有Header和Footer).png
橫向滑動(dòng)(無Header和Footer).png

蘋果為我們提供的UICollectionView可以很方便的實(shí)現(xiàn)很多不同的布局效果,其中它的布局精髓在于UICollectionViewLayout類医窿,所有的item布局在這里邊進(jìn)行磅甩。因此我們可以自定義一個(gè)類來繼承UICollectionViewLayout,重寫UICollectionViewLayout類中的一些函數(shù)/屬性姥卢,來實(shí)現(xiàn)我們想要的瀑布流布局(自定義的這個(gè)類可參照UICollectionViewFlowLayout類)卷要。

  • JYWaterfallFlowLayout 實(shí)現(xiàn)思路:
  1. 新建一個(gè)繼承UICollectionViewLayout類的文件JYWaterfallFlowLayout;
  2. 新建一個(gè)協(xié)議類JYWaterfallFlowLayoutProtocol独榴,聲明一些函數(shù)用于變量設(shè)置的回調(diào)定義我們布局時(shí)需要的常量/變量等僧叉;
  3. 定義私有/公有的變量/常量等,并賦默認(rèn)值棺榔;
  4. 重寫UICollectionViewLayout中的一些方法/變量瓶堕,對(duì)item、header症歇、footer進(jìn)行布局計(jì)算等郎笆;
  /// 這里我們先了解一下需要重寫的函數(shù)/屬性的作用
  /// 準(zhǔn)備布局,初始化一些信息和所有布局(在UICollectionView布局(或重新布局)時(shí)會(huì)調(diào))
  override func prepare() {} 
  /// 獲取 rect 范圍內(nèi)的所有 item 的布局忘晤,并返回計(jì)算好的布局結(jié)果
  override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {}
  /// 自定義 item 的布局
  override func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? {}
  /// 自定義 header/footer 的布局
  override func layoutAttributesForSupplementaryView(ofKind elementKind: String, at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? {}
  /// 設(shè)置 collectionVIew 的 contentSize(滾動(dòng)范圍)
  override var collectionViewContentSize: CGSize



下面就讓我們進(jìn)入自定義布局重點(diǎn)代碼:

  1. 定義我們需要的協(xié)議方法

JYWaterfallFlowLayoutProtocol.swift 文件中
這里的協(xié)議函數(shù)是參照UICollectionViewFlowLayout定義的
注意:swift定義協(xié)議的可選函數(shù)時(shí)必須添加“@ objc”關(guān)鍵字

@objc protocol JYWaterfallFlowLayoutDelegate: NSObjectProtocol {
    // item 的 size (寬高轉(zhuǎn)換:WaterfallFlowVertical根據(jù)寬算高宛蚓,WaterfallFlowHorizontal根據(jù)高算寬)
    @objc optional func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: JYWaterfallFlowLayout, heightForItemAt indexPath: IndexPath, itemWidth: CGFloat) -> CGFloat
    @objc optional func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: JYWaterfallFlowLayout, heightForItemAt indexPath: IndexPath, itemHeight: CGFloat) -> CGFloat
    
    // header/footer 的 size(僅限豎向滑動(dòng)時(shí)使用)
    @objc optional func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: JYWaterfallFlowLayout, referenceSizeForHeaderInSection section: Int) -> CGSize
    @objc optional func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: JYWaterfallFlowLayout, referenceSizeForFooterInSection section:Int) -> CGSize
    
    // 每個(gè) section 的內(nèi)邊距
    @objc optional func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: JYWaterfallFlowLayout, insetForSectionAt section: Int) -> UIEdgeInsets
    // 每個(gè) section 下顯示的 item 有多少列,返回每個(gè) section 下的 item 的列數(shù)
    @objc optional func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: JYWaterfallFlowLayout, columnNumberAt section:Int) -> Int
    // 每個(gè) section 下顯示的 item 的最小行間距
    @objc optional func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: JYWaterfallFlowLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat
    // 每個(gè) section 下顯示的 item 的最小列間距
    @objc optional func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: JYWaterfallFlowLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat
    // 本 section 的頭部和上個(gè) section 的尾部的間距
    @objc optional func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: JYWaterfallFlowLayout, spacingWithPreviousSectionForSectionAt section: Int) -> CGFloat
}


以下代碼在JYWaterfallFlowLayout.swift 文件中

  1. 定義一個(gè)瀑布流滑動(dòng)方向的枚舉
/**
 *  枚舉:collectionView 滑動(dòng)方向
 **/
enum UICollectionViewScrollDirection {
    case vertical    // 豎向滑動(dòng)
    case horizontal  // 橫向滑動(dòng)
}


  1. 定義需要的常量和變量
class JYWaterfallFlowLayout: UICollectionViewLayout {
    /**
     *  MARK: - 定義變量
     **/
    /// 屏幕的寬高
    fileprivate let screenWidth = UIScreen.main.bounds.size.width
    fileprivate let screenHeight = UIScreen.main.bounds.size.height
    public var NavigationHeight: CGFloat = 0
    public var iPhoneXBottomHeight: CGFloat = 0
    
    /// collectionView 相關(guān)
    // 記錄 collectionView 的 content 可滑動(dòng)的范圍
    fileprivate var contentScope: CGFloat = 0
    // collectionView 的滑動(dòng)方向设塔,默認(rèn)為豎向滑動(dòng)
    public var scrollDirection: UICollectionViewScrollDirection = .vertical
    
    /// item 相關(guān)
    // item 的行/列數(shù)凄吏,默認(rèn)為2行/列
    public var lineCount: Int = 2
    public var interitemCount: Int = 2
    // item 之間的間距(行/列),默認(rèn)間距為10
    public var lineSpacing: CGFloat = 10
    public var interitemSpacing: CGFloat = 10
    
    /// section 相關(guān)
    // section 的內(nèi)邊距,默認(rèn)上下左右都為10
    public var sectionInset: UIEdgeInsets = UIEdgeInsets.zero
    // 是否要顯示 header/footer痕钢,默認(rèn)不顯示
    public var isShowHeader: Bool = false
    public var isShowFooter: Bool = false
    // section 的 header/footer 的 size
    public var headerReferenceSize: CGSize = CGSize.zero
    public var footerReferenceSize: CGSize = CGSize.zero
    
    /// 數(shù)據(jù)處理相關(guān)
    // 存儲(chǔ) item 的所有 layoutAttributes 數(shù)組
    fileprivate lazy var layoutAttributesArray: [UICollectionViewLayoutAttributes] = []
    // 存儲(chǔ)橫向滑動(dòng)時(shí) section 每一行的寬度/豎向滑動(dòng)時(shí) section 每一列的高度
    fileprivate lazy var lineWidthArray: [CGFloat] = []
    fileprivate lazy var interitemHeightArray: [CGFloat] = []
    
    /// 協(xié)議代理
    weak var delegate: JYWaterfallFlowLayoutDelegate?
}


  1. 重寫UICollectionViewLayout類中的一些函數(shù)/屬性

重寫prepare() 函數(shù)

/**
 *  MARK: - 重寫UICollectionViewLayout中的一些方法
 **/
extension JYWaterfallFlowLayout {
    /// 為自定義布局做些準(zhǔn)備操作(在collectionView重新布局時(shí)图柏,總會(huì)調(diào)用此方法)
    override func prepare() {
        super.prepare()
        
        /// 初始化數(shù)據(jù)
        // 清空數(shù)組中之前保存的所有布局?jǐn)?shù)據(jù)
        layoutAttributesArray.removeAll()
        self.contentScope = 0.0

        // 設(shè)置代理
        if self.delegate == nil {
            self.delegate = self.collectionView?.delegate as? JYWaterfallFlowLayoutDelegate
        }
        
        /// 計(jì)算 section 下 item/header/footer 的布局
        setAllLayoutsForSection()
    }
}

自定義函數(shù),用于分解prepare()函數(shù)里的代碼

    /*
     *  設(shè)置 section 下的 item/header/footer 的布局任连,并緩存
     */
    fileprivate func setAllLayoutsForSection() {
        // 獲取 collectionView 中 section 的個(gè)數(shù)
        let getSectionCount = self.collectionView?.numberOfSections
        guard let sectionCount = getSectionCount else { return }
        
        // 遍歷 section 下的所有 item/header/footer蚤吹,并計(jì)算出所有 item/header/footer 的布局
        for i in 0..<sectionCount {
            // 獲取 NSIndexPath
            let indexPath = NSIndexPath(index: i)
            // 這里獲取到的 IndexPath 是個(gè)數(shù)組,取其內(nèi)容要用 indexPath.first/indexPath[0]随抠,不能用 indexPath.section距辆,否則會(huì) crash
//            let indexPath = IndexPath(index: i)
            
            // 通過代理調(diào)用協(xié)議方法,更新一些變量的值
            invokeProxy(inSection: indexPath.section)
            
            // 設(shè)置 header 的布局暮刃,并緩存
            if isShowHeader {
                let headerAttributesArray = supplementaryViewAttributes(ofKind: UICollectionElementKindSectionHeader, indexPath: indexPath as IndexPath)
                self.layoutAttributesArray.append(contentsOf: headerAttributesArray)
            }
            
            // 清空數(shù)組中之前緩存的高度跨算,留待使用(給下面的 item 計(jì)算 y 坐標(biāo)時(shí)使用 -- 必須寫在header后面,否則會(huì)計(jì)算錯(cuò)誤)
            interitemHeightArray.removeAll()
            lineWidthArray.removeAll()
            for _ in 0..<interitemCount {
                // 判斷是橫向滑動(dòng)還是豎向滑動(dòng)
                if scrollDirection == .horizontal {
                    // 緩存 collectionView 的 content 的寬度椭懊,為 item 的 x 坐標(biāo)開始的位置
                    self.lineWidthArray.append(contentScope)
                } else {
                    // 緩存 collectionView 的 content 的高度诸蚕,為 item 的 y 坐標(biāo)開始的位置
                    self.interitemHeightArray.append(contentScope)
                }
            }
            // 設(shè)置 item 的布局,并緩存
            let itemAttributesArray = itemAttributes(inSection: indexPath.section)
            self.layoutAttributesArray.append(contentsOf: itemAttributesArray)
            
            // 設(shè)置 footer 的布局氧猬,并緩存
            if isShowFooter {
                let footerAttributesArray = supplementaryViewAttributes(ofKind: UICollectionElementKindSectionFooter, indexPath: indexPath as IndexPath)
                self.layoutAttributesArray.append(contentsOf: footerAttributesArray)
            }
        }
    }

調(diào)用代理方法重新給變量設(shè)置的新值(若已經(jīng)在外部實(shí)現(xiàn)了協(xié)議函數(shù)的前提下)

    /*
     *  調(diào)用代理方法
     */
    fileprivate func invokeProxy(inSection section: Int) {
        /// 返回 section 下 item 的列數(shù)
        if (delegate != nil && (delegate?.responds(to: #selector(JYWaterfallFlowLayoutDelegate.collectionView(_:layout:columnNumberAt:)))) ?? false) {
            self.interitemCount = (delegate?.collectionView!(self.collectionView!, layout: self, columnNumberAt: section)) ?? interitemCount
        }
        /// 返回 section 的內(nèi)邊距
        if (delegate != nil && (delegate?.responds(to: #selector(JYWaterfallFlowLayoutDelegate.collectionView(_:layout:insetForSectionAt:)))) ?? false) {
            self.sectionInset = (delegate?.collectionView!(self.collectionView!, layout: self, insetForSectionAt: section)) ?? sectionInset
        }
        /// 返回當(dāng)前 section 的 header 與上個(gè) section 的 footer 之間的間距
        if (delegate != nil && (delegate?.responds(to: #selector(JYWaterfallFlowLayoutDelegate.collectionView(_:layout:spacingWithPreviousSectionForSectionAt:)))) ?? false) {
            self.spacingWithPreviousSection = (delegate?.collectionView!(self.collectionView!, layout: self, spacingWithPreviousSectionForSectionAt: section)) ?? spacingWithPreviousSection
        }
        /// 返回 section 下的 item 之間的最小行間距
        if (delegate != nil && (delegate?.responds(to: #selector(JYWaterfallFlowLayoutDelegate.collectionView(_:layout:minimumLineSpacingForSectionAt:)))) ?? false) {
            self.lineSpacing = (delegate?.collectionView!(self.collectionView!, layout: self, minimumLineSpacingForSectionAt: section)) ?? lineSpacing
        }
        /// 返回 section 下的 item 之間的最小列間距
        if (delegate != nil && (delegate?.responds(to: #selector(JYWaterfallFlowLayoutDelegate.collectionView(_:layout:minimumInteritemSpacingForSectionAt:)))) ?? false) {
            self.interitemSpacing = (delegate?.collectionView!(self.collectionView!, layout: self, minimumInteritemSpacingForSectionAt: section)) ?? interitemSpacing
        }
    }

重寫布局item相關(guān)函數(shù)

    /// 獲取 rect 范圍內(nèi)的所有 item 的布局背犯,并返回計(jì)算好的布局結(jié)果
    override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
        return layoutAttributesArray
    }
    
    /// 自定義 item 的布局
    override func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
        // 通過 indexPath,創(chuàng)建一個(gè) UICollectionViewLayoutAttributes
        let layoutAttributes = UICollectionViewLayoutAttributes(forCellWith: indexPath)
        
        // 判斷是豎向布局還是橫向布局
        if scrollDirection == .vertical {
            // 計(jì)算 item 的布局屬性
            self.verticalLayoutForItem(layoutAttributes, at: indexPath)
            
            /// 設(shè)置 collectionView 的 content 的高度
            //  獲取最大列的高度
            let (_, maximumInteritemHeight) = maximumInteritemForSection(heightArray: interitemHeightArray)
            //  判斷 collectionView 的 content 的高度 是否比當(dāng)前計(jì)算的最大列的高度小盅抚,若小于則更新 collectionView 的 content 的值
            if contentScope < maximumInteritemHeight {
                self.contentScope = maximumInteritemHeight
                // collectionView的content的高度 + section底部?jī)?nèi)邊距 + iphoneX的底部高度
                self.contentScope = contentScope + sectionInset.bottom + iPhoneXBottomHeight
            }
            
        } else if scrollDirection == .horizontal {
            // 計(jì)算 item 的布局屬性
            self.horizontalLayoutForItem(layoutAttributes, at: indexPath)
            
            /// 設(shè)置 collectionView 的 content 的寬度
            //  獲取最大列的高度
            let (_, maximumLineWidth) = maximumInteritemForSection(heightArray: lineWidthArray)  // maximumLineForSection(widthArray: lineWidthArray)
            //  判斷 collectionView 的 content 的寬度 是否比當(dāng)前計(jì)算的最大行的寬度小漠魏,若小于則更新 collectionView 的 content 的值
            if contentScope < maximumLineWidth {
                self.contentScope = maximumLineWidth
                // collectionView的content的寬度 + section右側(cè)內(nèi)邊距
                self.contentScope = contentScope + sectionInset.right
            }
        }
        
        return layoutAttributes
    }

以下是計(jì)算item自定義函數(shù),分解layoutAttributesForItem函數(shù)的功能

    /*
     *  豎向布局:計(jì)算 item 的布局妄均,并存儲(chǔ)每一列的高度
     *
     *  @param layoutAttributes: 布局屬性
     *  @param indexPath: 索引
     */
    fileprivate func verticalLayoutForItem(_ layoutAttributes: UICollectionViewLayoutAttributes, at indexPath: IndexPath) {
        /// 獲取 collectionView 的寬度
        let collectionViewWidth = self.collectionView?.frame.size.width ?? screenWidth
        
        /// 計(jì)算 item 的 frame
        //  item 的寬度【item的寬度 = (collectionView的寬度 - 左右內(nèi)邊距 - 列之間的間距 * (列數(shù) - 1)) / 列數(shù)】
        var itemWidth = (collectionViewWidth - sectionInset.left - sectionInset.right - interitemSpacing * CGFloat(interitemCount - 1)) / CGFloat(interitemCount)
        //  item 的高度【通過代理方法柱锹,并根據(jù) item 的寬度計(jì)算出 item 的高度】
        var itemHeight: CGFloat = 0
        if delegate != nil && ((delegate?.responds(to: #selector(JYWaterfallFlowLayoutDelegate.collectionView(_:layout:heightForItemAt:itemWidth:)))) ?? false) {
            itemHeight = delegate?.collectionView!(self.collectionView!, layout: self, heightForItemAt: indexPath, itemWidth: itemWidth) ?? 0
        }
        if delegate != nil && ((delegate?.responds(to: #selector(JYWaterfallFlowLayoutDelegate.collectionView(_:layout:sizeForItemAt:)))) ?? false) {
            let size = delegate?.collectionView!(self.collectionView!, layout: self, sizeForItemAt: indexPath) ?? CGSize.zero
            itemWidth = size.width
            itemHeight = size.height
        }
        //  獲取高度最小的那一列的列號(hào)和高度值
        let (minimumInteritemNumber, minimumInteritemHeight) = minimumInteritemForSection(heightArray: interitemHeightArray)
        //  item 的 x 坐標(biāo)
        // 【x的坐標(biāo) = 左內(nèi)邊距 + 列號(hào) * (item的寬度 + 列之間的間距)】
        let itemX = sectionInset.left + CGFloat(minimumInteritemNumber) * (itemWidth + interitemSpacing)
        //  item 的 y 坐標(biāo),初始位置為最小列的高度
        var itemY: CGFloat = minimumInteritemHeight
        //  如果item的y值不等于上個(gè)區(qū)域的最高的高度 既不是此區(qū)的第一列 要加上此區(qū)的每個(gè)item的上下間距
        if indexPath.item < interitemCount {
            itemY = itemY + sectionInset.top // y坐標(biāo)值 + section內(nèi)邊距top值(也就是第一行上方是否留白)
        } else {
            itemY = itemY + lineSpacing      // y坐標(biāo)值 + 行間距(item與item之間的行間距)
        }
        
        //  設(shè)置 item 的 attributes 的 frame
        layoutAttributes.frame = CGRect(x: itemX, y: itemY, width: itemWidth, height: itemHeight)
        
        /// 存儲(chǔ)所計(jì)算列的高度丰包。若已存在禁熏,則更新其值;若不存在邑彪,則直接賦值(y值 + height)
        self.interitemHeightArray[minimumInteritemNumber] = layoutAttributes.frame.maxY
    }
    
    /*
     *  橫向布局:計(jì)算 item 的布局瞧毙,并存儲(chǔ)每一行的寬度
     *
     *  @param layoutAttributes: 布局屬性
     *  @param indexPath: 索引
     */
    fileprivate func horizontalLayoutForItem(_ layoutAttributes: UICollectionViewLayoutAttributes, at indexPath: IndexPath) {
        /// 獲取 collectionView 的高度
        let collectionViewHeight = self.collectionView?.frame.size.height ?? (screenHeight - NavigationHeight)
        
        /// 計(jì)算 item 的 frame
        //  item 的高度【item的高度 = (collectionView的高度 - iphoneX底部的高度 - header的高度 - footer的高度 - 上下內(nèi)邊距 - 行之間的間距 * (行數(shù) - 1)) / 行數(shù)】
        var itemHeight = (collectionViewHeight - iPhoneXBottomHeight - headerReferenceSize.height - footerReferenceSize.height - sectionInset.top - sectionInset.bottom - lineSpacing * CGFloat(lineCount - 1)) / CGFloat(lineCount)
        //  item 的寬度【通過代理方法,并根據(jù) item 的高度計(jì)算出 item 的寬度】
        var itemWidth: CGFloat = 0
        if delegate != nil && ((delegate?.responds(to: #selector(JYWaterfallFlowLayoutDelegate.collectionView(_:layout:heightForItemAt:itemHeight:)))) ?? false) {
            itemWidth = delegate?.collectionView!(self.collectionView!, layout: self, heightForItemAt: indexPath, itemHeight: itemHeight) ?? 0
        }
        if delegate != nil && ((delegate?.responds(to: #selector(JYWaterfallFlowLayoutDelegate.collectionView(_:layout:sizeForItemAt:)))) ?? false) {
            let size = delegate?.collectionView!(self.collectionView!, layout: self, sizeForItemAt: indexPath) ?? CGSize.zero
            itemWidth = size.width
            itemHeight = size.height
        }
        //  獲取寬度最小的那一行的行號(hào)和寬度值
        let (minimumLineNumber, minimumLineWidth) = minimumInteritemForSection(heightArray: lineWidthArray)  // minimumLineForSection(widthArray: lineWidthArray)
        //  item 的 y 坐標(biāo)
        // 【y的坐標(biāo) = 上內(nèi)邊距 + 行號(hào) * (item的高度 + 行之間的間距)】
        let itemY = headerReferenceSize.height + sectionInset.top + CGFloat(minimumLineNumber) * (itemHeight + lineSpacing)
        //  item 的 x 坐標(biāo)寄症,初始位置為最小行的寬度
        var itemX: CGFloat = minimumLineWidth
        //  如果item的x值不等于上個(gè)區(qū)域的最高的高度 既不是此區(qū)的第一列 要加上此區(qū)的每個(gè)item的左右間距
        if indexPath.item < lineCount {
            itemX = itemX + sectionInset.left // x坐標(biāo)值 + section內(nèi)邊距l(xiāng)eft值(也就是第一列左方是否留白)
        } else {
            itemX = itemX + interitemSpacing     // x坐標(biāo)值 + 列間距(item與item之間的列間距)
        }
        
        //  設(shè)置 item 的 attributes 的 frame
        layoutAttributes.frame = CGRect(x: itemX, y: itemY, width: itemWidth, height: itemHeight)
        
        /// 存儲(chǔ)所計(jì)算列的高度(若已存在宙彪,則更新其值;若不存在有巧,則直接賦值)
        self.lineWidthArray[minimumLineNumber] = layoutAttributes.frame.maxX
    }
    
    /*
     *  豎向布局: 計(jì)算高度最小的是哪一列                 / 橫向布局:計(jì)算寬度最小的是哪一行
     *
     *  @param  heightArray: 緩存 section 高度的數(shù)組  / 緩存 section 寬度的數(shù)組
     *  return  返回最小列的列號(hào)和高度值                / 返回最小行的行號(hào)和高度值
     */
    fileprivate func minimumInteritemForSection(heightArray: [CGFloat]) -> (Int, CGFloat) {
        if heightArray.count <= 0 {
            return (0, 0.0)
        }
        // 默認(rèn)第0列的高度最小
        var minimumInteritemNumber = 0
        // 從緩存高度數(shù)組中取出第一個(gè)元素释漆,作為最小的那一列的高度
        var minimumInteritemHeight = heightArray[0]
        // 遍歷數(shù)組,查找出最小的列號(hào)和最小列的高度值
        for i in 1..<heightArray.count {
            let tempMinimumInteritemHeight = heightArray[i]
            if minimumInteritemHeight > tempMinimumInteritemHeight {
                minimumInteritemHeight = tempMinimumInteritemHeight
                minimumInteritemNumber = i
            }
        }
        return (minimumInteritemNumber, minimumInteritemHeight)
    }
    
    /*
     *  豎向布局: 計(jì)算高度最大的是哪一列                 / 橫向布局:計(jì)算寬度最大的是哪一行
     *
     *  @param  heightArray: 緩存 section 高度的數(shù)組  / 緩存 section 寬度的數(shù)組
     *  return  返回最大列的列號(hào)和高度值                / 返回最大行的行號(hào)和寬度值
     */
    fileprivate func maximumInteritemForSection(heightArray: [CGFloat]) -> (Int, CGFloat) {
        if heightArray.count <= 0 {
            return (0, 0.0)
        }
        // 默認(rèn)第0列的高度最小
        var maximumInteritemNumber = 0
        // 從緩存高度數(shù)組中取出第一個(gè)元素剪决,作為最小的那一列的高度
        var maximumInteritemHeight = heightArray[0]
        // 遍歷數(shù)組灵汪,查找出最小的列號(hào)和最小列的高度值
        for i in 1..<heightArray.count {
            let tempMaximumInteritemHeight = heightArray[i]
            if maximumInteritemHeight < tempMaximumInteritemHeight {
                maximumInteritemHeight = tempMaximumInteritemHeight
                maximumInteritemNumber = i
            }
        }
        return (maximumInteritemNumber, maximumInteritemHeight)
    }

重寫布局Header和Footer的函數(shù)

    /// 自定義 header/footer 的布局
    override func layoutAttributesForSupplementaryView(ofKind elementKind: String, at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
        // 通過 elementKind 和 indexPath,創(chuàng)建一個(gè) UICollectionViewLayoutAttributes
        let layoutAttributes = UICollectionViewLayoutAttributes(forSupplementaryViewOfKind: elementKind, with: indexPath)
        if elementKind == UICollectionElementKindSectionHeader {
            /// 通過代理方法柑潦,更新變量 headerReferenceSize 的值
            if delegate != nil && ((delegate?.responds(to: #selector(JYWaterfallFlowLayoutDelegate.collectionView(_:layout:referenceSizeForHeaderInSection:)))) ?? false) {
                self.headerReferenceSize = (delegate?.collectionView!(self.collectionView!, layout: self, referenceSizeForHeaderInSection: (indexPath.first ?? 0))) ?? headerReferenceSize
            }
            
            /// 計(jì)算 header 的布局
            self.layoutSupplementaryView(layoutAttributes, frame: CGRect(x: 0, y: contentScope, width: headerReferenceSize.width, height: headerReferenceSize.height), atTopWhiteSpace: false, atBottomWhiteSpace: false)
        } else if elementKind == UICollectionElementKindSectionFooter {
            /// 通過代理方法享言,更新變量 footerReferenceSize 的值
            if delegate != nil && ((delegate?.responds(to: #selector(JYWaterfallFlowLayoutDelegate.collectionView(_:layout:referenceSizeForFooterInSection:)))) ?? false) {
                self.footerReferenceSize = (delegate?.collectionView!(self.collectionView!, layout: self, referenceSizeForFooterInSection: (indexPath.first ?? 0))) ?? footerReferenceSize
            }
            
            /// 計(jì)算 footer 的布局
            self.layoutSupplementaryView(layoutAttributes, frame: CGRect(x: 0, y: contentScope, width: footerReferenceSize.width, height: footerReferenceSize.height), atTopWhiteSpace: false, atBottomWhiteSpace: false)
        } else {
            layoutAttributes.frame = CGRect.zero
        }
        return layoutAttributes
    }

以下是自定義函數(shù),分解layoutAttributesForSupplementaryView函數(shù)的功能

    /*
     *  豎向布局:計(jì)算 header/footer 布局渗鬼,并更新 collectionView 的 content 的滾動(dòng)范圍
     *
     *  @param layoutAttributes: 布局屬性
     *  @param frame           : header/footer 的frame
     *  @param top             : 是否 footer 的上方留白
     *  @param bottom          : 是否 header 的下方留白
     */
    fileprivate func layoutSupplementaryView(_ layoutAttributes: UICollectionViewLayoutAttributes, frame: CGRect, atTopWhiteSpace top: Bool, atBottomWhiteSpace bottom: Bool) {
        /// 計(jì)算 header/footer 的布局
        //  設(shè)置 header/footer 的 frame
        layoutAttributes.frame = frame
        
        ///  更新 collectionView 的 content 的值
        if isShowHeader {
            self.contentScope = self.contentScope + headerReferenceSize.height
        } else if isShowFooter {
            self.contentScope = self.contentScope + footerReferenceSize.height
        }
    }

重寫collectionViewContentSize屬性

    /// 設(shè)置 collectionVIew 的 content 的寬高(滾動(dòng)范圍)
    override var collectionViewContentSize: CGSize {
        get {
            let _ = super.collectionViewContentSize
            if scrollDirection == .horizontal && lineWidthArray.count <= 0 {
                return CGSize.zero
            } else if scrollDirection == .vertical && interitemHeightArray.count <= 0 {
                return CGSize.zero
            }
            
            // 計(jì)算 collectionView 的 content 的 size
            let getCollectionViewWidth = self.collectionView?.frame.size.width
            let getCollectionViewHeight = self.collectionView?.frame.size.height
            // 記錄豎向滾動(dòng)情況下 collectionView 固定的寬 / 橫向滾動(dòng)情況下 collectionView 固定的高
            var collectionViewWidth: CGFloat = 0.0
            var collectionViewHeight: CGFloat = 0.0
            if let width = getCollectionViewWidth, let height = getCollectionViewHeight {
                collectionViewWidth = width
                collectionViewHeight = height
            } else {
                collectionViewWidth = screenWidth
                collectionViewHeight = screenHeight - NavigationHeight
            }
            // 記錄豎向滑動(dòng)下的固定寬和動(dòng)態(tài)高/橫向滑動(dòng)下的動(dòng)態(tài)寬和固定高
            let tempContentWidth = (scrollDirection == .vertical ? collectionViewWidth : contentScope)
            let tempContentHeight = (scrollDirection == .vertical ? contentScope : collectionViewHeight)
            return CGSize(width: tempContentWidth, height: tempContentHeight)
        }
    }
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末览露,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子譬胎,更是在濱河造成了極大的恐慌差牛,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,734評(píng)論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件堰乔,死亡現(xiàn)場(chǎng)離奇詭異偏化,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)镐侯,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,931評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門侦讨,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人苟翻,你說我怎么就攤上這事韵卤。” “怎么了崇猫?”我有些...
    開封第一講書人閱讀 164,133評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵沈条,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我诅炉,道長(zhǎng)蜡歹,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,532評(píng)論 1 293
  • 正文 為了忘掉前任涕烧,我火速辦了婚禮季稳,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘澈魄。我一直安慰自己景鼠,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,585評(píng)論 6 392
  • 文/花漫 我一把揭開白布痹扇。 她就那樣靜靜地躺著铛漓,像睡著了一般。 火紅的嫁衣襯著肌膚如雪鲫构。 梳的紋絲不亂的頭發(fā)上浓恶,一...
    開封第一講書人閱讀 51,462評(píng)論 1 302
  • 那天,我揣著相機(jī)與錄音结笨,去河邊找鬼包晰。 笑死湿镀,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的伐憾。 我是一名探鬼主播勉痴,決...
    沈念sama閱讀 40,262評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼树肃!你這毒婦竟也來了蒸矛?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,153評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤胸嘴,失蹤者是張志新(化名)和其女友劉穎雏掠,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體劣像,經(jīng)...
    沈念sama閱讀 45,587評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡乡话,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,792評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了耳奕。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片蚊伞。...
    茶點(diǎn)故事閱讀 39,919評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖吮铭,靈堂內(nèi)的尸體忽然破棺而出时迫,到底是詐尸還是另有隱情,我是刑警寧澤谓晌,帶...
    沈念sama閱讀 35,635評(píng)論 5 345
  • 正文 年R本政府宣布掠拳,位于F島的核電站,受9級(jí)特大地震影響纸肉,放射性物質(zhì)發(fā)生泄漏溺欧。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,237評(píng)論 3 329
  • 文/蒙蒙 一柏肪、第九天 我趴在偏房一處隱蔽的房頂上張望姐刁。 院中可真熱鬧,春花似錦烦味、人聲如沸聂使。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,855評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽柏靶。三九已至,卻和暖如春溃论,著一層夾襖步出監(jiān)牢的瞬間屎蜓,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,983評(píng)論 1 269
  • 我被黑心中介騙來泰國(guó)打工钥勋, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留炬转,地道東北人辆苔。 一個(gè)月前我還...
    沈念sama閱讀 48,048評(píng)論 3 370
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像扼劈,于是被迫代替她去往敵國(guó)和親驻啤。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,864評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容

  • 1测僵、通過CocoaPods安裝項(xiàng)目名稱項(xiàng)目信息 AFNetworking網(wǎng)絡(luò)請(qǐng)求組件 FMDB本地?cái)?shù)據(jù)庫組件 SD...
    陽明先生_X自主閱讀 15,980評(píng)論 3 119
  • 給你寫一封情書 用七十年代的牛皮紙包住 字里行間不必用太多矯情的詞匯 也不用增加氣氛的排比句 沒有過多的修辭 只是...
    安陰閱讀 211評(píng)論 0 1
  • 那年是個(gè)四月,謝瓦里夫?yàn)榉磳?duì)有污染的工廠在巴圖姆鎮(zhèn)投建谢翎,與鎮(zhèn)上的人一起拒絕搬遷捍靠,被抓去關(guān)了兩個(gè)多月。 等到謝瓦里夫...
    俗眼閱讀 409評(píng)論 0 0