Prevent scroll position from resetting when perform UITextView typing in UICollectionView

Currently, we try to place multiple UITextViews in UICollectionView.

To ensure UICollectionView's cell height, will adjust based on the dynamic content of UITextView, this is what we have done.

  • Disable scrolling in UITextView.
  • Use .estimated(CGFloat(44)) for UICollectionViewCompositionalLayout
  • Whenever there is text change, call collectionView.collectionViewLayout.invalidateLayout(). This is a critical step to ensure cell height will adjust accordingly.

However, calling collectionView.collectionViewLayout.invalidateLayout() does come with a side effect.

The current scroll position of UICollectionView will be reset, after calling collectionView.collectionViewLayout.invalidateLayout().

Does anyone know how can I

  1. Prevent unwanted auto scroll position resetting?
  2. UICollectionView will auto scroll to current cursor position, so that what is current being typed is visible to user?

The code to demonstrate this problem is as follow - https://github.com/yccheok/checklist-demo

Here's the code snippet, on what was happening as typing goes on

func textViewDidChange(_ checklistCell: ChecklistCell) {
    //
    // Critical code to ensure cell will resize based on cell content.
    //
    // (But comes with a side effect which will reset scroll position.)
    self.collectionView.collectionViewLayout.invalidateLayout()

    //
    // Ensure our checklists data structure in sync with UI state.
    //
    guard let indexPath = collectionView.indexPath(for: checklistCell) else { return }
    let item = indexPath.item
    let text = checklistCell.textView.text
    self.checklists[item].text = text
}

Side Note

Note, the closest solution we have came across is posted at https://medium.com/@georgetsifrikas/embedding-uitextview-inside-uitableviewcell-9a28794daf01

In UITableViewController, during text change, the author is using

DispatchQueue.main.async {
    tableView?.beginUpdates()
    tableView?.endUpdates()
}

It works well. But, what is the equivalent solution for UICollectionView?

We can't try out with self.collectionView.performBatchUpdates, as our solution is built around Diffable Data Source.

I have tried

DispatchQueue.main.async {
    self.collectionView.collectionViewLayout.invalidateLayout()
}

That doesn't solve the problem either.

Thank you.

I understand you are not dealing with static sized cells, but for anyone else who stumbles upon this...

This may sound obvious, but in my experience the jumping only occurs when a cell size changes during a layout reset. So instead of using .estimated, I used .absolute with the same value (it was a static sized cell) and the jumping stopped.

Curious if you ever figured it out.

I think what's happening is that with a dynamic sized layout. The layout doesn't know height of each cell until it is dequeued and rendered. Therefore, the content offset is meaningless because it will require every cell to be rendered to accurately scroll to the right place.

Instead of invalidating the layout, I find that updating a cell using DiffableDataSource works (https://betterprogramming.pub/new-in-ios-16-self-resizing-uicollectionview-cells-f02fbb7b45b1). I think that DIffableDataSource just calls UICollectionVIew methods under the hood, so I'm curious if there's a lower level way to handle it.

Prevent scroll position from resetting when perform UITextView typing in UICollectionView
 
 
Q