2
votes

I have a horizontal UICollectionView that's contained inside a UICollectionView header of a vertical UICollectionView. The header of the vertical UICollectionView has a dynamic height that changes due to user interaction (dragging the outer UICollectionView down expands it, scrolling up collapses it). The horizontal UICollectionView is constrained to the size of the header and will also shrink in height when the header is reduced in height. When this happens I get this error

the behavior of the UICollectionViewFlowLayout is not defined because: 2018-07-19 13:42:09.959 IDAGIO[81891:2239798] the item height must be less than the height of the UICollectionView minus the section insets top and bottom values, minus the content insets top and bottom values. 2018-07-19 13:42:09.959 IDAGIO[81891:2239798] Please check the values return by the delegate.

because the UICollectionView shrinks in height, but the cells of it keep their old size and are therefore higher than allowed (I only get this error while scrolling up, so it's not related to any insets, the heights are fine, they just don't match exactly at that point in time).

I already tried to call collectionView.collectionViewLayout.invalidateLayout() every time I get a scroll event of the vertical collection view and then return the collection view size in
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize { but the layout update then is always a bit too late, so I still get the above error. Also it looks weird as the cell height also visually is a bit behind.

My question: is there a way to dynamically respond to height changes of a horizontal UICollectionView so the cell height is updated accordingly and in time? (Maybe setup some height constraint that automatically keeps the cell height equal to the collection view height?)

I know about dynamic cell sizing, but that usually means to constrain a cell to the size of it's content. What I want to achieve is to always constrain the height of the cell (and it's content) to the collection view height.

3
In sizeForItemAtIndexPath return CGSizeMake(customWidth, collectionView.height); and reload your collection each time it gets new height.TheTiger
@TheTiger As I wrote above: I already tried that, but the layout takes too long so it's not updated in time.Micky
You can use global height variable and pass it to item size instead of collectionView height. I mean before animating the height of collection view just reload it with desired height using a global variable.TheTiger

3 Answers

4
votes

I found an easy solution myself, without having to pass or store the scroll offset at all:
Just subclass UICollectionViewFlowLayout, override shouldInvalidateLayout and set the itemSize to the size of the new bounds.

override func shouldInvalidateLayout(forBoundsChange: CGRect) -> Bool {
    if !forBoundsChange.size.equalTo(collectionView!.bounds.size) {
        itemSize = forBoundsChange.size
        return true
    }
    return false
}

In Swift 4.2

override func shouldInvalidateLayout(forBoundsChange newBounds: CGRect) -> Bool {
    if !newBounds.size.equalTo(collectionView!.bounds.size) {
        itemSize = newBounds.size
        return true
    }
    return false
}
4
votes

UICollectionView has a property derived from UIScrollView called contentinsetadjustmentbehavior, setting that to false should fix your issue if you are setting the size of the items on the collectionView to the size of the bounds of the collectionView.

Because the scrollView can be adjusting the insets which in turns changes the collectionView.adjustedContentInset

https://developer.apple.com/documentation/uikit/uiscrollview/2902261-contentinsetadjustmentbehavior

So basically setting:

collectionView.contentInsetAdjustmentBehavior = .never

1
votes

Reloading data on scroll event is one of the options but it is definitely not the best one because of all the calculations involved in reloading data again and again. You'll probably find a better solution by subclassing UICollectionViewLayout and do all the calculations on prepare(). Then you can just use delegation to pass the scroll offset to your custom layout and make the cell sizing accordingly.