6

Current set up:

TableView with automatically calculated heights:

self.tableView.sectionHeaderHeight = UITableViewAutomaticDimension;
self.tableView.rowHeight = UITableViewAutomaticDimension;
self.tableView.estimatedRowHeight = 152.0;
self.tableView.estimatedSectionHeaderHeight = 50.0;

Whenever the fetched results controller updates its data the tableview is reloaded:

 - (void)controllerDidChangeContent:(NSFetchedResultsController *)controller {
    [self.tableView reloadData]; 
}

The cell is configured using a Xib. The first label is pinned to the top of the cell, each following label is pinned to the top of the label above it and the label at the bottom is pinned to the bottom of the cell.

The Issue:

Each time i set a "Favourite" property on an item in the table view, the fetched results controller is fired to reload the table and the scroll position is changed. It is this change in the scroll position that i am trying to fix.

Additional Info

If i use fixed cell heights it resolves the issue BUT i require UITableViewAutomaticDimension because the first label can wrap over two lines and the remaining labels may or may not be present.

Example

Note - As i select the Fav button it sets the fav property in Core data and reloads the table. Why is the table jumping around?

enter image description here

Community
  • 1
  • 1
Tom G
  • 999
  • 2
  • 10
  • 23

1 Answers1

8

It happens because of the following sequence:

  1. UITableView initialized and showing 5 cells. Height of each of that cells is known to UITableView. It asks its delegate for exact height before displaying each cell by calling a method -tableView:heightForRowAtIndexPath:.
  2. UITableView scrolled exactly 3 cells from top. Heights of this cells are known to be exactly [60, 70, 90] = 220 summarily. UITableView's contentOffset.y is now 220.
  3. UITableView gets reloaded. It purges all its knowledge about cells. It now still knows its contentOffset.y which is 220.
  4. UITableView asking its data source about general metrics - number of sections and number of rows in each section.
  5. UITableView now beginning to fill its contents. First it needs to know size of its contents to correctly size and position its scroll indicators. It also needs to know which objects - table header, section headers, rows, section footers and table footer - it should display according to its current bounds, which position is also represented by contentOffset. To begin placing that visible objects it first needs to skip objects that falls in invisible vertical range of [0…220].

    1. If you haven't provided values for any of estimated… properties and haven't implemented any of tableViewController:estimated…methods then UITableView asks its delegate about exact height of headers, footers and rows by calling appropriate delegate methods such as -tableView:heightForRowAtIndexPath:. And if your delegate reports the same number of objects and the same heights for them as before reload, then you will not see any visual changes to position and size of any table elements. Downside of this "strait" behavior became obvious when your table should display large number of rows, lets say 50000. UITableView asks its delegate about height of each of this 50000 rows, and you have to calculate it yourself by measuring your text for each corresponding object, or when using UITableViewAutomaticDimension UITableView doing the same measuring itself, asking its delegate for cells filled with text. Believe me, it's slow. Each reload will cause a few seconds of interface freeze.
    2. If you have supplied UITableView with estimated heights, then it will ask its delegate only for heights of currently visible objects. Objects in vertical range of [0…220] are counted by using values provided in estimatedRowHeight or -tableView:estimatedHeightForRowAtIndexPath: for rows and by corresponding methods for section headers and footers. By setting estimatedRowHeight to 60, you telling UITableView to skip three rows (60 * 3 = 180) and to place row 4 with offset of -40 from top visible edge. Hence visual "jump" by 40 pixels up.

A "right" solution here would be not to call reloadData. Reload rows only for changed objects instead, use -reloadRowsAtIndexPaths:withRowAnimation:. In case of NSFetchedResultsController + UITableView use this classic scheme.

bteapot
  • 1,897
  • 16
  • 24
  • Excellent answer, thank you kindly. Very detailed, how did you discover these inner workings? as it is not obvious from the Docs. By reloading just the one cell it resolves the issue. It is actually a collapsable UITableView implementation so I have a method to configure the required cell as opposed to the system method of -reloadRowsAtIndexPaths:withRowAnimation: – Tom G May 11 '15 at 09:55
  • @bteapot : I have a similar issue. But it occurs when I paginate and then calls tableview reload. After this reload, when I scroll to to, the above jumping occurs. No issue when scrolling down. Please comment a solution. – krishnanunni Nov 24 '15 at 08:25
  • 1
    @krishnanunni You've got `tableView:estimatedHeightFor…`, or `estimatedRowHeight`/`estimatedSection…` implemented with your table view? Do you really need it with pagination? It worth implementing only with large row count, which should not be an issue with pagination. Try not to use estimated heights and see what's happens. – bteapot Nov 24 '15 at 09:14
  • @bteapot : Yes! I have tableView:estimatedHeight implemented. When I am loading for the first time, the cell heights are all faulty. After a small delay, it adjust itself. But this causes a very bad user experience. When I implemented tableview:estimatedHeight, that issue was resolved. My estimatedheight delegate method is as follows: - (CGFloat)tableView:(UITableView *)tableView estimatedHeightForRowAtIndexPath:(NSIndexPath *)indexPath { if (IOS8_OR_LATER) { return UITableViewAutomaticDimension; } else { return 710; } } – krishnanunni Nov 24 '15 at 09:31
  • @bteapot : Caching the calculated height in WillDisplayCell and using that in estimatedHeightForRow did the trick for me. Also found that is a bug in iOS 8. No issue in iOS 9. Found following : https://github.com/smileyborg/TableViewCellWithAutoLayoutiOS8/issues/17 , https://openradar.appspot.com/19581195 – krishnanunni Nov 24 '15 at 13:28