以编程方式创建自定义 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的主要内容,如果未能解决你的问题,请参考以下文章