Ben Lime
Ben Lime

Reputation: 493

UICollectionView not calling intrinsicContentSize

I have a UICollectionViewController which generates cells with a random color for testing purposes. Now that the UICollectionViewController is embedded in a UIScrollView, I want the scrollView to be the same size as it's contentSize.

I made a subclass of UICollectionView, implemented intrinsicContentSize method, and set the UICollectionView's class to my custom class in IB. However intrinsicContentSize never gets called. I have the exact same setup with an UITableView and there it works flawlessly.

Any ideas on this?

- (CGSize)intrinsicContentSize {
    [self layoutIfNeeded];
    return CGSizeMake(UIViewNoIntrinsicMetric, self.contentSize.height);
}

Upvotes: 2

Views: 8671

Answers (3)

Marc Etcheverry
Marc Etcheverry

Reputation: 1089

The correct answer is to do something like this

- (CGSize)intrinsicContentSize
{
    return self.collectionViewLayout.collectionViewContentSize;
}

And call -invalidateContentSize whenever you think it needs to change (after reloadData for example).

In Interface Builder you may need to set placeholder intrinsic size constraints to avoid errors.

This subclassing and overriding of -intrinsicContentSize is useful if you want to grow a collection view's frame until it is constrained by a sibling or parent view

Upvotes: 14

MaxMedvedev
MaxMedvedev

Reputation: 59

Use this class to make UICollectionView update its intrinsic content size every time the content is changed.

class AutosizedCollectionView: UICollectionView {
    override init(frame: CGRect, collectionViewLayout layout: UICollectionViewLayout) {
        super.init(frame: frame, collectionViewLayout: layout)
        registerObserver()
    }

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        registerObserver()
    }

    deinit {
        unregisterObserver()
    }

    override var intrinsicContentSize: CGSize {
        return contentSize
    }

    private func registerObserver() {
        addObserver(self, forKeyPath: #keyPath(UICollectionView.contentSize), options: [], context: nil)
    }

    private func unregisterObserver() {
        removeObserver(self, forKeyPath: #keyPath(UICollectionView.contentSize))
    }

    override func observeValue(forKeyPath keyPath: String?,
                               of object: Any?,
                               change: [NSKeyValueChangeKey : Any]?,
                               context: UnsafeMutableRawPointer?)
    {
        if keyPath == #keyPath(UICollectionView.contentSize) {
            invalidateIntrinsicContentSize()
        }
    }
}

Though, you should understand that if you embed it into another scroll view, cell recycling will not work. The most appreciated way to deal with nested collection view is described here.

Upvotes: -1

Arek Holko
Arek Holko

Reputation: 9006

I'm not sure why it's happening. Here's another solution to this problem. Set up a height constraint on UICollectionView object. Then set its constant to be equal to self.collectionView.contentSize.height. I use a similar approach in my app, though I've got UITextView instead of UICollectionView.

UPDATE: I've found a way to do this with intrinsicContentSize: UITextView in UIScrollView using auto layout

Upvotes: 1

Related Questions