Cheok Yan Cheng
Cheok Yan Cheng

Reputation: 42660

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.

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().

enter image description here

Does anyone know how can I

  1. Prevent scroll position auto 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.

Upvotes: 1

Views: 865

Answers (1)

DonMag
DonMag

Reputation: 77433

Unless you are taking advantage of other layout features of UICollectionViewCompositionalLayout, I would think using a table view would be a better route.

However, if you make these 2 changes to your textViewDidChange(...) function, you may get the results you're after:

func textViewDidChange(_ checklistCell: ChecklistCell) {
    //
    // Critical code to ensure cell will resize based on cell content.
    //
    
    // 1. DO NOT call this here...
    //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
    
    // 2. update your diffable data source here
    applySnapshot(true)
}

Upvotes: 1

Related Questions