1

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

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.

Cheok Yan Cheng
  • 47,586
  • 132
  • 466
  • 875
  • You don't want to post lines of code here and want somebody to go there just in order to assist you, instead? – El Tomato Oct 02 '21 at 10:02
  • 1
    Sorry. As, the code snippet is not able to tell the whole story. Anyhow, I still post the most critical part of the code snippet, along with some additional resources I have found. – Cheok Yan Cheng Oct 02 '21 at 10:59
  • That sounds like a good idea. Thanks. – El Tomato Oct 02 '21 at 11:00
  • @CheokYanCheng - is there a reason you're using a collection view with `UICollectionViewCompositionalLayout` instead of a table view? – DonMag Oct 02 '21 at 13:13
  • @DonMag Major reason is we are comfortable & familiar with collection view + `UICollectionViewCompositionalLayout` + diffable data source. But, we do not familiar with table view. I am not sure whether table view is capable in producing the following https://i.imgur.com/ioa6izd.png (Android) & https://i.imgur.com/MXCApNt.png (Our half-way iOS implementation till we encounter this roadblock) – Cheok Yan Cheng Oct 02 '21 at 17:41
  • There is a tricky solution, that is you have to calculate the relative position of the first visible cell, and after reload you have to scroll to that cell and then same position it was. Or if you have limited list items ScrollView's have no issues like this. – ChanOnly123 Oct 02 '21 at 18:44
  • This might help: https://stackoverflow.com/q/4585718/14351818 – aheze Oct 03 '21 at 07:17

1 Answers1

1

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)
}
DonMag
  • 69,424
  • 5
  • 50
  • 86
  • Oh man! I recall I have some unknown issue when using applySnapshot during typing. Now the unknown issue gone! Thank you as this is a life saver code snippet. I spend entire day to re-implement using UITableView. Although `UITableView.automaticDimension` comes very handy, it yields some weird issue when having multiple image collage as header (Multiple image collage as header is fine in UICollectionView). Thanks. I can sleep now without having to think how I can resolve UITableView related issue. – Cheok Yan Cheng Oct 04 '21 at 17:43
  • Side note: The system only allows me to award bounty after 21 hours. – Cheok Yan Cheng Oct 04 '21 at 17:44