以编程方式创建自定义 UICollectionFlowLayout

Posted

技术标签:

【中文标题】以编程方式创建自定义 UICollectionFlowLayout【英文标题】:Creating a custom UICollectionFlowLayout Programmatically 【发布时间】:2018-05-18 18:29:22 【问题描述】:

我正在尝试以编程方式创建一个带有自定义 FlowLayout 的 UICollectionView。

我按照本教程学习了如何通过故事板设置自定义 FlowLayout:https://octodev.net/custom-collectionviewlayout/

但是我想避免使用故事板,而是通过我的代码中的纯逻辑来实现它。

目前我在主 VC 中的 collectionView 设置如下:

/** Collection View **/
        let flowLayout : UICollectionViewFlowLayout = UICollectionViewFlowLayout()
        flowLayout.sectionInset = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)
        flowLayout.scrollDirection = .vertical
        self.collectionView = UICollectionView(frame: CGRect(x: self.frame.width * 0, y: self.frame.height * 0, width: self.frame.width, height: self.frame.height), collectionViewLayout: flowLayout)
        collectionView.register(DiscoverCVC.self, forCellWithReuseIdentifier: cellId)
        collectionView.delegate = self
        collectionView.dataSource = self
        collectionView.backgroundColor = UIColor.clear
        self.addSubview(collectionView)

我需要在 StoryBoard 上以编程方式注册的内容如下

布局需要是“自定义”并且类需要是特殊类。

这是课程:

import UIKit

private let kReducedHeightColumnIndex = 1
private let kItemHeightAspect: CGFloat = 2

class DiscoverCollection: BaseCollectionViewLayout 

    private var _itemSize : CGSize!
    private var _columnsXoffset : [CGFloat]!

    required init?(coder aDecoder: NSCoder) 
        super.init(coder: aDecoder)
        self.totalColumns = 3
    

    private func isLastItemSingleInRow(_ indexPath: IndexPath) -> Bool 
        return indexPath.item == (totalItemsInSection - 1) && indexPath.item % totalColumns == 0
    

    override func calculateItemSize() 
        let contentWidthWithoutIndents = collectionView!.bounds.width - contentInsets.left - contentInsets.right
        let itemWidth = (contentWidthWithoutIndents - (CGFloat(totalColumns) - 1) * interItemsSpacing) / CGFloat(totalColumns)

        let itemHeight = itemWidth * kItemHeightAspect
        _itemSize = CGSize(width: itemWidth, height: itemWidth)

        _columnsXoffset = []

        for columnIndex in 0...(totalColumns - 1) 
            _columnsXoffset.append(CGFloat(columnIndex) * (_itemSize.width + interItemsSpacing))
        
    

    override func columnIndexForItemAt(indexPath: IndexPath) -> Int 
        let columnIndex = indexPath.item % totalColumns
        return self.isLastItemSingleInRow(indexPath) ? kReducedHeightColumnIndex : columnIndex
    

    override func calculateItemFrame(indexPath: IndexPath, columnIndex: Int, columnYoffset: CGFloat) -> CGRect 
        let rowIndex = indexPath.item / totalColumns
        let halfItemHeight = (_itemSize.height - interItemsSpacing) / 2

        var itemHeight = _itemSize.height

        if (rowIndex == 0 && columnIndex == kReducedHeightColumnIndex) || self.isLastItemSingleInRow(indexPath) 
            itemHeight = halfItemHeight
        
        return CGRect(x: _columnsXoffset[columnIndex], y: columnYoffset, width: _itemSize.width, height: itemHeight)
    

这里是基本的collectionViewFlowLayout

import UIKit

class BaseCollectionViewLayout: UICollectionViewLayout 

    private var _layoutMap = [IndexPath : UICollectionViewLayoutAttributes]()
    private var _columnsYoffset : [CGFloat]!
    private var _contentSize : CGSize!

    private(set) var totalItemsInSection = 0

    var totalColumns = 0
    var interItemsSpacing : CGFloat = 8

    var contentInsets : UIEdgeInsets 
        return collectionView!.contentInset
    

    override var collectionViewContentSize: CGSize 
        return _contentSize
    

    override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? 
        var layoutAttributesArray = [UICollectionViewLayoutAttributes]()

        for(_, layoutAttributes) in _layoutMap 
            if rect.intersects(layoutAttributes.frame) 
                layoutAttributesArray.append(layoutAttributes)
            
        

        return layoutAttributesArray
    

    func columnIndexForItemAt(indexPath: IndexPath) -> Int 
        return indexPath.item % totalColumns
    

    func calculateItemFrame(indexPath: IndexPath, columnIndex: Int, columnYoffset: CGFloat) -> CGRect 
        return CGRect.zero
    

    func calculateItemSize()

    override func prepare() 
        _layoutMap.removeAll()
        _columnsYoffset = Array(repeating: 0, count: totalColumns)

        totalItemsInSection = collectionView!.numberOfItems(inSection: 0)

        if totalItemsInSection > 0 && totalColumns > 0 
            self.calculateItemSize()
            var itemIndex = 0
            var contentSizeHeight : CGFloat = 0

            while itemIndex < totalItemsInSection 

                let indexPath = IndexPath(item: itemIndex, section: 0)
                let columnIndex = self.columnIndexForItemAt(indexPath: indexPath)

                let attributeRect = calculateItemFrame(indexPath: indexPath, columnIndex: columnIndex, columnYoffset: _columnsYoffset[columnIndex])
                let targetLayoutAttributes = UICollectionViewLayoutAttributes.init(forCellWith: indexPath)
                targetLayoutAttributes.frame = attributeRect

                contentSizeHeight = max(attributeRect.maxY, contentSizeHeight)
                _columnsYoffset[columnIndex] = attributeRect.maxY + interItemsSpacing
                _layoutMap[indexPath] = targetLayoutAttributes

                itemIndex += 1
            

            _contentSize = CGSize(width: collectionView!.bounds.width - contentInsets.left - contentInsets.right, height: contentSizeHeight)
        
    

我该怎么做?

【问题讨论】:

介意共享 BaseCollectionViewLayout 类吗? 已添加!谢谢贾里德 【参考方案1】:

将此附加方法添加到DiscoverCollection

override init() 
    super.init()
    self.totalColumns = 3

然后将 CollectionView 的布局设置为您的布局实例:

let customLayout = DiscoverCollection()
collectionView.collectionViewLayout = customLayout

【讨论】:

我以为是这样,但它告诉我我缺少参数“coder”我已经用我尝试使用的类更新了 OP,我是新来理解 flowlayout真的,对不起,如果它看起来像一个简单的答案 好的,这与集合视图或布局无关。问题在于您的类(及其基类)提供的各种init 方法,而现在只有init(coder:)。由于我们不知道BaseCollectionViewLayout 是什么样的,所以很难说您需要更改什么。 我明白了,我已经用 BaseCollectionViewLayout 更新了 OP。 更新了我的答案

以上是关于以编程方式创建自定义 UICollectionFlowLayout的主要内容,如果未能解决你的问题,请参考以下文章

如何以编程方式创建自定义导航栏?

以编程方式创建自定义绑定

iOS:使用 IB 为以编程方式创建的 UITableView 创建自定义单元格

如何以编程方式创建此自定义绑定?

自定义以编程方式创建的导航栏的背景图像

以编程方式创建 NSTableView 和自定义 NSViewController