问题
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