Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Error: UITableView jump to top with UITableViewAutomaticDimension

I am using UITableView with estimatedRowHeight and UITableViewAutomaticDimension. Also I am using NSFetchedResultsControllerDelegate to reflect the changes.

Everything is work fine. Now the problem is when ever I add some record to CoreData, NSFetchedResultsController called the it's delegate but an unexpected things happen. TableView suddenly scroll to Top every time.

NSFetchedResultsControllerDelegate

func controllerWillChangeContent(controller: NSFetchedResultsController) {
    tableView.beginUpdates()
}

func controllerDidChangeContent(controller: NSFetchedResultsController) {
    tableView.endUpdates()
}

func controller(controller: NSFetchedResultsController, didChangeObject anObject: AnyObject, atIndexPath indexPath: NSIndexPath?, forChangeType type: NSFetchedResultsChangeType, newIndexPath: NSIndexPath?) {
        switch type {
        case .Insert:
            tableView.insertRowsAtIndexPaths([newIndexPath!], withRowAnimation: .None)
            break

        case .Update:
            tableView.reloadRowsAtIndexPaths([indexPath!], withRowAnimation: .None)
            break

        case .Delete:
            tableView.deleteRowsAtIndexPaths([indexPath!], withRowAnimation: .None)
            break

        default: break;
        }
    }

By googling I found few answers where people suggested to use tableView: heightForRowAtIndexPath: but as my cell height is dynamic. So what should I do?

like image 213
Tapas Pal Avatar asked May 03 '16 10:05

Tapas Pal


5 Answers

This is default behaviour of tableview, inserting an row in tableview scroll it to top. I did faced the same issue with my app. Here is how i able to manage the content offset of tableview, follow the steps,

1)Store the content offset of UITableview before inserting row or section.

2)Insert objects in array.

3)Reload the tableview

4)Subtract the difference and set the content offset manually.

let oldOffset: CGFloat = tblTest.contentSize.height
tblTest.reloadData()
let newOffset: CGFloat = tblTest.contentSize.height
tblTest.setContentOffset(CGPointMake(0, (newOffset - oldOffset)), animated: false)

This is how i have done in my applications so far, and with dynamic cell to overcome the reinitialise cell everytime, it is working just awesome.

like image 165
Devang Tandel Avatar answered Oct 08 '22 19:10

Devang Tandel


Adding to @jayesh-miruliya 's answer, in your NSFetchedResultsControllerDelegate after the switch statement, put this in your code:

tableView.reloadData()
dispatch_async(dispatch_get_main_queue(), {
    let topCellIndexPath = NSIndexPath(forRow: 0, inSection: 0)
    self. tableView.scrollToRowAtIndexPath(topCellIndexPath, atScrollPosition: .Top, animated: true)
})
like image 40
Mehrdadmaskull Avatar answered Oct 08 '22 19:10

Mehrdadmaskull


To solve my problem I save the cell height in the willDisplay method and in estimatedHeightForRowAt I'm retrieving the value.

var cellHeights = NSMutableDictionary()

override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
    return UITableViewAutomaticDimension
}

override func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
    if let height = cellHeights.object(forKey: indexPath) {
        return height as! CGFloat
    }

    return UITableViewAutomaticDimension
}

override func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
    cellHeights.setObject(cell.frame.size.height, forKey: indexPath as NSCopying)
}
like image 20
guillaume Avatar answered Oct 08 '22 18:10

guillaume


tableView.reloadData()
if tableView.numberOfRowsInSection(0) > 0
{
      let indexPath = NSIndexPath(forRow: 0, inSection: 0)
      self. tableView.scrollToRowAtIndexPath(indexPath, atScrollPosition: .Top, animated: true)
}
like image 33
Jayesh Miruliya Avatar answered Oct 08 '22 20:10

Jayesh Miruliya


  func controller(controller: NSFetchedResultsController, didChangeObject anObject: AnyObject, atIndexPath indexPath: NSIndexPath?, forChangeType type: NSFetchedResultsChangeType, newIndexPath: NSIndexPath?) {
    switch type {
    case .Insert:

      tableView.insertRowsAtIndexPaths([newIndexPath!], withRowAnimation: .None)
        break

    case .Update:
        tableView.reloadRowsAtIndexPaths([indexPath!], withRowAnimation: .None)
        break

    case .Delete:
        tableView.deleteRowsAtIndexPaths([indexPath!], withRowAnimation: .None)
        break

    default: break;
    }
   tableView.reloadData()
  let indexPath = NSIndexPath(forRow: 0, inSection: 0)
  self. tableView.scrollToRowAtIndexPath(indexPath, atScrollPosition: .Top, animated: true)
   }

}
like image 27
Purushothaman Avatar answered Oct 08 '22 20:10

Purushothaman