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

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


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)            }        }    }}


in my case I fixed it so:here I detect if my cell is fully visible, and if it's not so - move to it. (without "jumping")

 let cellRect = collectionView.convert(cell.frame, to: collectionView.superview)      if !collectionView.frame.contains(cellRect) {        collectionView.selectItem(at: indexPath, animated: true, scrollPosition: .centeredHorizontally)        return collectionView.reloadData()      }

here, I hold my contentOffset:

   let currentContentOffSet = collectionView.contentOffset         if #available(iOS 12.0, *) {            UIView.animate(withDuration: 0) {               collectionView.reloadData()               collectionView.performBatchUpdates(nil, completion: { _ in                  collectionView.contentOffset = currentContentOffSet               })            }         } else {            UIView.animate(withDuration: 0) {               collectionView.reloadItems(at: [self.lastSelectedIndexPath, indexPath])               collectionView.contentOffset = currentContentOffSet            }         }

it helps move to cells if cells became visible recently (or already visible) and I tap to the cell. Also without "jumping" (without resetting contentOffset)

Also you can try this in collectionViewController:

  func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {      guard let cell = collectionView.cellForItem(at: indexPath) else { return }      if lastSelectedIndexPath != indexPath {         let cellRect = collectionView.convert(cell.frame, to: collectionView.superview)         if !collectionView.frame.contains(cellRect) {            collectionView.selectItem(at: indexPath, animated: true, scrollPosition: .centeredHorizontally)         } else {            collectionView.selectItem(at: indexPath, animated: true, scrollPosition: .init())         }         collectionView.deselectItem(at: lastSelectedIndexPath, animated: false)         lastSelectedIndexPath = indexPath      }   }

and this in collectionViewCellModel:

  override var isSelected: Bool {      didSet {         switch self.isSelected {         case false:         case true:         }      }   }


Reloading sections of UICollectionView worked for me.

UIView.performWithoutAnimation {self.collStages.reloadSections([0])}collStages.scrollToItem(at: IndexPath(row: index, section: 0), at: .centeredHorizontally, animated: true)