I am attempting to create a UICollectionView with a custom FlowLayout all programmatically.
I followed this tutorial on how to set-up a custom FlowLayout through a storyboard: https://octodev.net/custom-collectionviewlayout/
However I want to refrain from using the story board and implement this via pure logic in my code.
Currently my set-up of the collectionView is as follows in my main VC:
/** 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)
What I need to register programmatically is as follows on the StoryBoard
The Layout needs to be "Custom" and the class needs to be a special Class.
Here is the class:
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)
}
}
Here is the base 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)
}
}
}
How do I go about this?
Add this additional method to DiscoverCollection
:
override init() {
super.init()
self.totalColumns = 3
}
and then set your CollectionView's layout to your layout instance:
let customLayout = DiscoverCollection()
collectionView.collectionViewLayout = customLayout