UITableView unexpectedly bounces with beginUpdates()/endUpdates()/performBatchUpdates()

烈酒焚心 提交于 2019-12-19 08:33:16

问题


I have a pretty straight forward UITableViewController /NSFetchedResultsController case here. It's from Xcode Master-Detail App sample code, So easy to reproduce.
I have a CoreData Model with 1 Entity with 1 String Attribute. It's displayed in a UITableViewController. I use .subtitle system cell type. By selecting a row, I simply update the String Attribute.
So my problem is, when I add just enough rows for the tableview to scroll (10-11 rows on a iPhone 5s with navbar), and I scroll down, and select any row, the tableview bounces up and down.
If there is less rows (less than 10 rows), or more rows (12 rows and more), the behaviour is normal.
So It seems to be at the limit of the scroll view the problem happens. If I don't use beginUpdates()/endUpdates(), the problem goes away, but I loose their advantages.
Here is a video link of what happens

class TableViewController: UITableViewController, NSFetchedResultsControllerDelegate {

  var managedObjectContext: NSManagedObjectContext? = nil

  @objc func insertNewObject(_ sender: Any) {
    let context = self.fetchedResultsController.managedObjectContext
    let newEvent = Event(context: context)
    newEvent.aString = "a String"
    try? context.save()
  }

  override func viewDidLoad() {
    super.viewDidLoad()

    let addButton = UIBarButtonItem(barButtonSystemItem: .add, target: self, action: #selector(insertNewObject(_:)))
    navigationItem.rightBarButtonItem = addButton
  }

  override func numberOfSections(in tableView: UITableView) -> Int {
    return fetchedResultsController.sections?.count ?? 0
  }

  override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    let sectionInfo = fetchedResultsController.sections![section]
    return sectionInfo.numberOfObjects
  }

  override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
    let event = fetchedResultsController.object(at: indexPath)
    configureCell(cell, withEvent: event)
    return cell
  }

  func configureCell(_ cell: UITableViewCell, withEvent event: Event) {
    cell.textLabel?.text = event.aString
    cell.detailTextLabel?.text = event.aString
  }

  override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
    tableView.deselectRow(at: indexPath, animated: true)
    let event: Event = self.fetchedResultsController.object(at: indexPath)
    event.aString = event.aString! + ""
  }

  override func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
    return true
  }

  override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {
    if editingStyle == .delete {
      let context = fetchedResultsController.managedObjectContext
      context.delete(fetchedResultsController.object(at: indexPath))
      try? context.save()
    }
  }

  var fetchedResultsController: NSFetchedResultsController<Event> {
    if _fetchedResultsController != nil {
      return _fetchedResultsController!
    }

    let fetchRequest: NSFetchRequest<Event> = Event.fetchRequest()
    fetchRequest.fetchBatchSize = 20
    let sortDescriptor = NSSortDescriptor(keyPath: \Event.aString, ascending: false)
    fetchRequest.sortDescriptors = [sortDescriptor]

    let aFetchedResultsController = NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext: self.managedObjectContext!, sectionNameKeyPath: nil, cacheName: nil)
    aFetchedResultsController.delegate = self
    _fetchedResultsController = aFetchedResultsController
    try? _fetchedResultsController!.performFetch()

    return _fetchedResultsController!
  }

  var _fetchedResultsController: NSFetchedResultsController<Event>? = nil

  func controllerWillChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
    tableView.beginUpdates()
  }

  func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChange sectionInfo: NSFetchedResultsSectionInfo, atSectionIndex sectionIndex: Int, for type: NSFetchedResultsChangeType) {
    switch type {
    case .insert:
      tableView.insertSections(IndexSet(integer: sectionIndex), with: .automatic)
    case .delete:
      tableView.deleteSections(IndexSet(integer: sectionIndex), with: .automatic)
    default:
      return
    }
  }

  func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChange anObject: Any, at indexPath: IndexPath?, for type: NSFetchedResultsChangeType, newIndexPath: IndexPath?) {
    switch type {
    case .insert:
      tableView.insertRows(at: [newIndexPath!], with: .automatic)
    case .delete:
      tableView.deleteRows(at: [indexPath!], with: .automatic)
    case .update:
      configureCell(tableView.cellForRow(at: indexPath!)!, withEvent: anObject as! Event)
    case .move:
      configureCell(tableView.cellForRow(at: indexPath!)!, withEvent: anObject as! Event)
      tableView.moveRow(at: indexPath!, to: newIndexPath!)
    }
  }

  func controllerDidChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
    tableView.endUpdates()
  }
}

回答1:


I was running into the same problem, and I found that implementing the estimatedHeightFor... methods fixed the issue for me. The issue seems to stem from using automatic cell heights in the table instead of explicitly defined heights per row.

The table I'm using has both rows and section header/footers, so I needed to define estimated heights for both rows and headers, and this solved the strange bouncing animation during batch updates.

Note that returning 0 for section header heights will use the table view's default value which is likely UITableViewAutomaticDimension, so return a positive number for the estimated heights.

My code in Obj-C:

-(CGFloat)tableView:(UITableView *)tableView estimatedHeightForRowAtIndexPath:(NSIndexPath *)indexPath{
    return 44; // anything except 0 or UITableViewAutomaticDimension
}

-(CGFloat)tableView:(UITableView *)tableView estimatedHeightForHeaderInSection:(NSInteger)section{
    return 18; // anything except 0 or UITableViewAutomaticDimension
}

-(CGFloat)tableView:(UITableView *)tableView estimatedHeightForFooterInSection:(NSInteger)section{
    return 18; // anything except 0 or UITableViewAutomaticDimension
}


来源:https://stackoverflow.com/questions/48658226/uitableview-unexpectedly-bounces-with-beginupdates-endupdates-performbatchup

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!