Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Adding a HeaderView to CollectionView when using custom FlowLayout (Swift)

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
    }
}
like image 694
GarySabo Avatar asked Mar 13 '23 23:03

GarySabo


2 Answers

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.

like image 52
gabbler Avatar answered May 01 '23 08:05

gabbler


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)
like image 38
Bruno Paulino Avatar answered May 01 '23 09:05

Bruno Paulino