Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

UICollectionView (Automatic Size) .reloadData() resets contentOffset to 0 (top)

I have a UICollectionView using a standard UICollectionViewFlowLayout with an estimatedItemSizeset to UICollectionViewFlowLayoutAutomaticSize.

Once I call collectionView.reloadData() the scroll position of the view resets to CGPoint(x: 0, y: 0). I know that .reloadData() should only be used as a last resort, it is necessary in my scenario though. I have found out so far that it is probably caused by not returning an item size through collectionView(_:layout:sizeForItemAt:). (Any solutions for that analogous to UITableView’s tableView:estimatedHeightForRowAtIndexPath: like in this StackOverflow answer would be highly appreciated).

like image 284
Aaron Avatar asked Oct 19 '17 10:10

Aaron


1 Answers

I had the same issue and solved it by subclassing the collection view, override reloadData to temporary store the current contentOffset and then listening to changes to the contentOffset and changing it back if i have anything stored in my temporary offset variable

class MyCollectionView : UICollectionView {
    deinit {
        removeObserver(self, forKeyPath: #keyPath(UIScrollView.contentOffset))
    }

    override init(frame: CGRect, collectionViewLayout layout: UICollectionViewLayout) {
        super.init(frame: frame, collectionViewLayout: layout)
        setup()
    }

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

    private func setup() {
        self.addObserver(self, forKeyPath: #keyPath(UIScrollView.contentOffset), options: [.old, .new], context: nil)
    }

    private var temporaryOffsetOverride : CGPoint?
    override func reloadData() {
        if let flowLayout = collectionViewLayout as? UICollectionViewFlowLayout, flowLayout.estimatedItemSize == UICollectionViewFlowLayout.automaticSize {
            temporaryOffsetOverride = contentOffset
        }
        super.reloadData()
    }

    override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
        if keyPath == #keyPath(UIScrollView.contentOffset) {
            if let offset = temporaryOffsetOverride {
                temporaryOffsetOverride = nil
                self.setContentOffset(offset, animated: false)
            }
        }
    }
}
like image 95
SveinnV Avatar answered Oct 27 '22 10:10

SveinnV