Quantcast
Channel: Recent Threads — Xamarin Community Forums
Viewing all articles
Browse latest Browse all 204402

UITableViewSource.GetHeightForRow : significantly improve performance

$
0
0

This is an information post for people who work with UITableView with a lot of cells of non-uniform height.

In my app I'm now working on chat that should look similar to Messages app - with bubbles. A message could have any amount of lines of text, so my UITableViewCells have different height. That's why I need to override GetHeightForRow().

I've noticed before that overriding GetHeightForRow() will make any table updates much slower and it is clear because when we override it, we tell the UITableView it should acquire every row's height to calculate the entire height (ContentSize.Height) of a table. It is much better not to override this method when you have rows of a uniform height.

But this time I had to override. As I kept in mind the problem with performance, I decided to make a chat table view initially have low amount of rows - and to expand the table only when user scrolls up and reaches the beginning. And I was planning not to tableView.ReloadAll(), but to tableView.InsertRows(). I had a hope Apple did this correct and UITableView will not recalculate the entire table height, but just add a sum of heights for newly inserted rows.

Unfortunately, I was wrong. After I implemented all the stuff and started testing on a device, I've noticed - the more I scroll up (and more rows inserted), more time is required to insert a new part of rows. Actually, I didn't check the progression of number of GetHeightForRow() calls, but it seems it is really do the full scan every time I insert a single row into the table.

Since I have no time to implement more optimal UITableView myself, I decided to find where can I save time and so I go to the Instruments to profile my app.

There I found the call to 'native_to_managed_trampoline_MyAppIOS_ChatTableSource_GetHeightForRow' takes too much time, and a large part of that time is spent to make some internal wrapping of NSIndexPath from native ptr to managed class. (You will find a report in the end of post.)

So what I do is rewrite ChatTableSource.GetHeightForRow() from this:

public override float GetHeightForRow (UITableView tableView, NSIndexPath indexPath)
{
  var someHeight = CalcHeightSomehow(indexPath.row);
  return someHeight;
}

To this:

private readonly static IntPtr c_rowSelector = MonoTouch.ObjCRuntime.Selector.GetHandle ("row");

[Export ("tableView:heightForRowAtIndexPath:")]
public float myGetHeightForRow (IntPtr tableView, IntPtr indexPath)
{
  var row = MonoTouch.ObjCRuntime.Messaging.int_objc_msgSend (indexPath, c_rowSelector);
  var someHeight = CalcHeightSomehow(row);
  return someHeight;
}

And it works! To test the difference I do this inside each version of GetHeightForRow:

numInvokes++;
if (numInvokes == 10000)
  Application.DebugBreak();

Here's the difference:

2520.0ms native_to_managed_trampoline_MyAppIOS_ChatTableSource_GetHeightForRow

656.0ms native_to_managed_trampoline_MyAppIOS_ChatTableSource_myGetHeightForRow

And the reports (you'll see original GetHeightForRow overriding causes a lot of useless stuff to wrap native objects to managed):

  1. UITableViewSource.GetHeightForRow() c#-overridden: http://pastebin.com/khxttmtg

  2. tableView:heightForRowAtIndexPath: obj-c-overridden: http://pastebin.com/QPVM7kD8

Thanks for your attention! Hope Xamarin developers will take it in account and optimize the things in future.


Viewing all articles
Browse latest Browse all 204402

Trending Articles



<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>