I've implemented a custom FlowLayout
subclass, which apparently has precluded me from adding a headerView
via the storyboard
(the check box is gone). Is there any other way to do so using storyboards
?
There are some answers about how to add a headerView
programmatically but they're in objective-C
, how can I add one using Swift
?
The below doesn't produce a header view and I can't figure out why?
CollectionViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Setup Header
self.collectionView!.registerClass(PinHeaderView.self, forSupplementaryViewOfKind:UICollectionElementKindSectionHeader, withReuseIdentifier: headerIdentifier)
}
override func collectionView(collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, atIndexPath indexPath: NSIndexPath) -> UICollectionReusableView {
//1
switch kind {
//2
case UICollectionElementKindSectionHeader:
//3
let headerView = collectionView.dequeueReusableSupplementaryViewOfKind(kind,withReuseIdentifier: "PinHeaderView",
forIndexPath: indexPath)
as! PinHeaderView
headerView.pinHeaderLabel.text = boardName
return headerView
default:
//4
fatalError("Unexpected element kind")
}
}
}
class PinHeaderView: UICollectionReusableView {
@IBOutlet weak var pinHeaderLabel: UILabel!
}
My Layout class:
import UIKit
protocol PinterestLayoutDelegate {
// 1. Method to ask the delegate for the height of the image
func collectionView(collectionView:UICollectionView, heightForPhotoAtIndexPath indexPath:NSIndexPath , withWidth:CGFloat) -> CGFloat
// 2. Method to ask the delegate for the height of the annotation text
func collectionView(collectionView: UICollectionView, heightForAnnotationAtIndexPath indexPath: NSIndexPath, withWidth width: CGFloat) -> CGFloat
func columnsForDevice() -> Int
}
class PinterestLayoutAttributes:UICollectionViewLayoutAttributes {
// 1. Custom attribute
var photoHeight: CGFloat = 0.0
var headerHeight: CGFloat = 0.0
// 2. Override copyWithZone to conform to NSCopying protocol
override func copyWithZone(zone: NSZone) -> AnyObject {
let copy = super.copyWithZone(zone) as! PinterestLayoutAttributes
copy.photoHeight = photoHeight
return copy
}
// 3. Override isEqual
override func isEqual(object: AnyObject?) -> Bool {
if let attributtes = object as? PinterestLayoutAttributes {
if( attributtes.photoHeight == photoHeight ) {
return super.isEqual(object)
}
}
return false
}
}
class PinterestLayout: UICollectionViewLayout {
//1. Pinterest Layout Delegate
var delegate:PinterestLayoutDelegate!
//2. Configurable properties
//moved numberOfColumns
var cellPadding: CGFloat = 6.0
//3. Array to keep a cache of attributes.
private var cache = [PinterestLayoutAttributes]()
//4. Content height and size
private var contentHeight:CGFloat = 0.0
private var contentWidth: CGFloat {
let insets = collectionView!.contentInset
return CGRectGetWidth(collectionView!.bounds) - (insets.left + insets.right)
}
override class func layoutAttributesClass() -> AnyClass {
return PinterestLayoutAttributes.self
}
override func prepareLayout() {
// 1. Only calculate once
//if cache.isEmpty {
// 2. Pre-Calculates the X Offset for every column and adds an array to increment the currently max Y Offset for each column
let numberOfColumns = delegate.columnsForDevice()
let columnWidth = contentWidth / CGFloat(numberOfColumns)
var xOffset = [CGFloat]()
for column in 0 ..< numberOfColumns {
xOffset.append(CGFloat(column) * columnWidth )
}
var column = 0
var yOffset = [CGFloat](count: numberOfColumns, repeatedValue: 0)
// 3. Iterates through the list of items in the first section
for item in 0 ..< collectionView!.numberOfItemsInSection(0) {
let indexPath = NSIndexPath(forItem: item, inSection: 0)
// 4. Asks the delegate for the height of the picture and the annotation and calculates the cell frame.
let width = columnWidth - cellPadding*2
let photoHeight = delegate.collectionView(collectionView!, heightForPhotoAtIndexPath: indexPath , withWidth:width)
let annotationHeight = delegate.collectionView(collectionView!, heightForAnnotationAtIndexPath: indexPath, withWidth: width)
let height = cellPadding + photoHeight + annotationHeight + cellPadding
let frame = CGRect(x: xOffset[column], y: yOffset[column], width: columnWidth, height: height)
let insetFrame = CGRectInset(frame, cellPadding, cellPadding)
// 5. Creates an UICollectionViewLayoutItem with the frame and add it to the cache
let attributes = PinterestLayoutAttributes(forCellWithIndexPath: indexPath)
attributes.photoHeight = photoHeight
attributes.frame = insetFrame
cache.append(attributes)
// 6. Updates the collection view content height
contentHeight = max(contentHeight, CGRectGetMaxY(frame))
yOffset[column] = yOffset[column] + height
column = column >= (numberOfColumns - 1) ? 0 : ++column
}
//}
}
override func layoutAttributesForSupplementaryViewOfKind(elementKind: String, atIndexPath indexPath: NSIndexPath) -> UICollectionViewLayoutAttributes? {
let attributes = PinterestLayoutAttributes(forSupplementaryViewOfKind: elementKind, withIndexPath: indexPath)
attributes.headerHeight = 100.0
attributes.frame = (self.collectionView?.frame)!
return attributes
}
override func collectionViewContentSize() -> CGSize {
return CGSize(width: contentWidth, height: contentHeight)
}
override func layoutAttributesForElementsInRect(rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
var layoutAttributes = [UICollectionViewLayoutAttributes]()
// Loop through the cache and look for items in the rect
for attributes in cache {
if CGRectIntersectsRect(attributes.frame, rect ) {
layoutAttributes.append(attributes)
}
}
return layoutAttributes
}
}
You can add header view in storyboard, drag a "Collection Reusable View" and drop it inside the collectionView, then set its class
and identifier
in storyboard. Or you can register your custom header class programmatically as shown in your code.
self.collectionView!.registerClass(PinHeaderView.self, forSupplementaryViewOfKind:UICollectionElementKindSectionHeader, withReuseIdentifier: headerIdentifier)
viewForSupplementaryElementOfKind
delegate method is incomplete, case UICollectionElementKindSectionFooter
isn't handled, do the same for footer view as what you did for header view. If you don't see it, set borderWidth
of header view's layer to a value greater than 0, it might show up.
inside the prepareForLayout
function add one more attributes
object for the header. You can append it into the cache at the end like that:
// Add Attributes for section header
let headerAtrributes = UICollectionViewLayoutAttributes(forSupplementaryViewOfKind: UICollectionElementKindSectionHeader, with: IndexPath(item: 0, section: 0))
headerAtrributes.frame = CGRect(x: 0, y: 0, width: self.collectionView!.bounds.size.width, height: 50)
cache.append(headerAtrributes)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With