I have done a View in CollectionView with CustomLayout. In iOS6 it worked great but iOS7 it throws an exception like this.
Terminating app due to uncaught exception
I'm not entirely certain how or why, but this appears to be fixed in iOS 12, supporting both supplementary view resizing and prefetching. The trick for me was to make sure things are happening in the correct order.
Here is a working implementation of a stretchable header view. Notice the implementation of the header resizing happening in layoutAttributesForElements(in rect: CGRect)
:
class StretchyHeaderLayout: UICollectionViewFlowLayout {
var cache = [UICollectionViewLayoutAttributes]()
override func prepare() {
super.prepare()
cache.removeAll()
guard let collectionView = collectionView else { return }
let sections = [Int](0.. Bool {
return true
}
override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
let visibleAttributes = cache.filter { rect.contains($0.frame) || rect.intersects($0.frame) }
guard let collectionView = collectionView else { return visibleAttributes }
// Find the header and stretch it while scrolling.
guard let header = visibleAttributes.filter({ $0.representedElementKind == StretchyCollectionHeaderKind }).first else { return visibleAttributes }
header.frame.origin.y = collectionView.contentOffset.y
header.frame.size.height = headerHeight.home - collectionView.contentOffset.y
header.frame.size.width = collectionView.frame.size.width
return visibleAttributes
}
override func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
let attributes = super.layoutAttributesForItem(at: indexPath as IndexPath)?.copy() as! UICollectionViewLayoutAttributes
guard collectionView != nil else { return attributes }
attributes.frame.origin.y = headerHeight.home + attributes.frame.origin.y
return attributes
}
override func layoutAttributesForSupplementaryView(ofKind elementKind: String, at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
return UICollectionViewLayoutAttributes(forSupplementaryViewOfKind: StretchyCollectionHeaderKind, with: indexPath)
}
override var collectionViewContentSize: CGSize {
get {
guard let collectionView = collectionView else { return .zero }
let numberOfSections = collectionView.numberOfSections
let lastSection = numberOfSections - 1
let numberOfItems = collectionView.numberOfItems(inSection: lastSection)
let lastItem = numberOfItems - 1
guard let lastCell = layoutAttributesForItem(at: IndexPath(item: lastItem, section: lastSection)) else { return .zero }
return CGSize(width: collectionView.frame.width, height: lastCell.frame.maxY + sectionInset.bottom)
}
}
}
P.S.: I'm aware the cache doesn't actually serve any purpose at this point :)