Tometoyou
Tometoyou

Reputation: 8376

Maintain scroll position in UICollectionView when scrolling up

I have a UICollectionView with a chat like view - messages are displayed with the newest at the bottom. As the user scrolls up it loads previous messages and updates the collection view. I am trying to maintain the content offset of the UICollectionView as new data is added but I can't get it to work.

Here's what I have at the moment:

// First find the top most visible cell.
if let topCellIndexPath = collectionView.indexPathsForVisibleItems.sorted().first,
   let topCell = collectionView.cellForItem(at: topCellIndexPath),
   let topCellLayout = collectionView.layoutAttributesForItem(at: topCellIndexPath) {

    // Save the y position of the top cell.
    let previousTopCellY = topCellLayout.frame.origin.y

    // Perform updates on the UICollectionView without animation (ignore the fact it says adapter)
    adapter.performUpdates(animated: false) { [weak self] completed in
        if let strongSelf = self,
           let topCellNewIndexPath = strongSelf.collectionView.indexPath(for: topCell),
           let newTopCellLayout = strongSelf.collectionView.layoutAttributesForItem(at: topCellNewIndexPath) {

            // Calculate difference between the previous cell y value and the current cell y value
            let delta = previousTopCellY - newTopCellLayout.frame.origin.y

            // Add this to the collection view content offset
            strongSelf.collectionView.contentOffset.y += delta
        }
    }
}

This doesn't seem to work, and sometimes can't get the indexPath of the cell after the update.

EDIT Based on @Arkku's answer this works. Although there is a small flicker.

let previousContentSize = collectionView.contentSize.height
adapter.performUpdates(animated: false) { [weak self] completed in
    if let strongSelf = self {
        let delta = strongSelf.collectionView.contentSize.height - previousContentSize
        strongSelf.collectionView.contentOffset.y += delta
    }
}

Upvotes: 1

Views: 3893

Answers (1)

Arkku
Arkku

Reputation: 42109

As I commented earlier, it may be better to get the delta from contentSize rather than origin of a specific cell. A suggestion based on your own version:

let previousContentHeight = collectionView.contentSize.height
adapter.performUpdates(animated: false) { [weak self] completed in
    guard let self = self else { return }
    let delta = self.collectionView.contentSize.height - previousContentHeight
    self.collectionView.bounds.origin.y += delta
}

Upvotes: 3

Related Questions