Sunday, March 20, 2016

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

Leave a Comment

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     } } 

2 Answers

Answers 1

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.

Answers 2

Why are you using PinterestLayoutAttributes(forCellWithIndexPath: indexPath) in method layoutAttributesForSupplementaryViewOfKind?

You should use PinterestLayoutAttributes(forSupplementaryViewOfKind elementKind: String, withIndexPath indexPath: NSIndexPath).

You are generating layout attributes for cell instead of suplementary view. As a result your layout attributes don't contain right category type and view kind. That's why even if you register class for suplementary view - collection view doesn't layout its instances.

If You Enjoyed This, Take 5 Seconds To Share It

0 comments:

Post a Comment