Profound UITableView rendering-performance problem, but only at certain positions?

  • I have a tableview that contains some graphic elements that rotate to
    reflect the phone's heading.  I only reload the visible rows, and I don't
    do it while the table is scrolling.  I filter the headings to 10-degree
    increments to cut down the amount of heading updates I get.

    Sometimes these work fine.  But other times, the rendering is extremely
    slow, with many overlapping images appearing before they're erased.  Even
    the labels in the cells are drawn offset and overlapping, although the
    table's not in motion.  In the video here, you can see numerous arrow
    images being drawn before being erased.  And in the middle of the third
    second of video, note the cell image flying up from the bottom of the
    screen and settling in the top row!

    http://www.youtube.com/watch?v=wL2EYAro-oM&feature=youtu.be

    Then if I scroll the table (keeping the same number of rows visible), the
    problem often mysteriously disappears.

    My theory is that the drawing performance suffers when the table is
    scrolled to particular pixel offsets.  So I'm logging the UITableView's
    contentOffset, but that's always zero.  Does anyone know how I can
    determine how far the tableview is scrolled?  Or does anyone have another
    theory?  I don't think there's a fundamental problem because again, the
    updates work fine in some table positions.

    Thanks!
  • I found the exact condition that results in this problem.  The corruption
    only happens when

    1. There's more than one section on the screen.
    2. The last row of the topmost section is only partially visible, and the
    visible portion is smaller than the section header.

    The part about the section header is just a guess, but that's the only
    explanation I see for the otherwise arbitrary point at which the corruption
    starts.

    Seems like a bug in the tableview.  To work around it, I'll obviously need
    to shift the scroll amount of the tableview whenever this condition is true.
  • I think the first problem you should concentrate on should be the multiply-overlaid drawings. It's hard to tell what's going on unless you share some code, or at least your design, so I can only ask general questions.

    I assume you're using the UIAccelerometer delegate method to receive your rotation events. How are you triggering redraws?

    How do you do the redraws? In particular, are you doing CG drawing directly in your delegate method, or are you using UIKit-gated drawing calls like -drawRect:? It's hard (I don't know if it's possible) to get in-line drawing right. Further, the drawRect: mechanism does your event coalescing for you, so you wouldn't have to worry about drawing code overlapping.

    Can you ask the UITableView for the visible cells and send them -setNeedsDisplay, rather than having the table reload everything?

    If it turns out that the speed of drawing the arrows is the issue, how are you drawing? Images? Core Animation? Core Graphics? UIBezierPath (are you caching the paths)? How do you handle rotation (that is, where and how do you apply a rotation transform to your drawing)?

    Have you watched your drawing code with the Time Profiler instrument?

    — F

    On 22 Jul 2012, at 11:45 PM, Gavin Stokes wrote:

    > Sometimes these work fine.  But other times, the rendering is extremely
    > slow, with many overlapping images appearing before they're erased.  Even
    > the labels in the cells are drawn offset and overlapping, although the
    > table's not in motion.  In the video here, you can see numerous arrow
    > images being drawn before being erased.  And in the middle of the third
    > second of video, note the cell image flying up from the bottom of the
    > screen and settling in the top row!
    >
    > http://www.youtube.com/watch?v=wL2EYAro-oM&feature=youtu.be
    >
    > Then if I scroll the table (keeping the same number of rows visible), the
    > problem often mysteriously disappears.
    >
    > My theory is that the drawing performance suffers when the table is
    > scrolled to particular pixel offsets.  So I'm logging the UITableView's
    > contentOffset, but that's always zero.  Does anyone know how I can
    > determine how far the tableview is scrolled?  Or does anyone have another
    > theory?  I don't think there's a fundamental problem because again, the
    > updates work fine in some table positions.

    --
    Fritz Anderson -- Xcode 4 Unleashed: Now in stores! -- <http://x4u.manoverboard.org/>
  • On Mon, Jul 23, 2012 at 8:16 AM, Fritz Anderson <fritza...>wrote:

    > I think the first problem you should concentrate on should be the
    > multiply-overlaid drawings. It's hard to tell what's going on unless you
    > share some code, or at least your design, so I can only ask general
    > questions.
    >

    Thanks for your response.  The code is quite simple, so I'm happy to
    provide it.  But, because the occurrence of the problem depends entirely on
    the existence of a partially exposed row in a previous section at the top
    of the screen, I don't think any of this processing has anything to do with
    the problem.  There's only about a 50-pixel danger zone of scroll position
    where it occurs, and that zone moves with row height, but not by any
    discernible pattern.

    I have Location Services set to give me heading updates every 10 degrees.
    When my view controller gets called with a heading update, I do:

    [self.stashTableView reloadRowsAtIndexPaths:[self.stashTableView
    indexPathsForVisibleRows] withRowAnimation:UITableViewRowAnimationNone];

    When the tableview calls me with willDisplayCell, and I do a bit of math to
    combine the current heading with the angle toward a point of interest.
    Then I tell the cell to rotate the arrow image, which is simply a
    UIImageView:

    CGAffineTransform rotation = CGAffineTransformMakeRotation(degrees * (M_PI
    / 180));

    [self.thumbImageView setTransform:rotation];

    That's it.  Works great except for a very limited range of scroll offsets.
  • On Jul 23, 2012, at 2:03 PM, Gavin Stokes wrote:

    On Mon, Jul 23, 2012 at 8:16 AM, Fritz Anderson <fritza...><mailto:<fritza...>>wrote:

    I think the first problem you should concentrate on should be the
    multiply-overlaid drawings. It's hard to tell what's going on unless you
    share some code, or at least your design, so I can only ask general
    questions.

    Thanks for your response.  The code is quite simple, so I'm happy to
    provide it.  But, because the occurrence of the problem depends entirely on
    the existence of a partially exposed row in a previous section at the top
    of the screen, I don't think any of this processing has anything to do with
    the problem.  There's only about a 50-pixel danger zone of scroll position
    where it occurs, and that zone moves with row height, but not by any
    discernible pattern.

    I have Location Services set to give me heading updates every 10 degrees.
    When my view controller gets called with a heading update, I do:

    [self.stashTableView reloadRowsAtIndexPaths:[self.stashTableView
    indexPathsForVisibleRows] withRowAnimation:UITableViewRowAnimationNone];

    Though probably off topic, this is unnecessary work. If you just want to change where your arrow is pointing, you should reach into the cells and change the position of the arrow - you shouldn't be doing a reload for that. Think something more like for (UITableViewCell* cell in [tableView visibleCells]) {cell.thumbImageView.transform = TRANSFORM_CALCULATION;}

    Luke

    When the tableview calls me with willDisplayCell, and I do a bit of math to
    combine the current heading with the angle toward a point of interest.
    Then I tell the cell to rotate the arrow image, which is simply a
    UIImageView:

    CGAffineTransform rotation = CGAffineTransformMakeRotation(degrees * (M_PI
    / 180));

    [self.thumbImageView setTransform:rotation];

    That's it.  Works great except for a very limited range of scroll offsets.
  • >
    > Though probably off topic, this is unnecessary work. If you just want to
    > change where your arrow is pointing, you should reach into the cells and
    > change the position of the arrow - you shouldn't be doing a reload for
    > that. Think something more like for (UITableViewCell* cell in [tableView
    > visibleCells]) {cell.thumbImageView.transform = TRANSFORM_CALCULATION;}
    >

    Thanks.  I thought about doing this, but I'd end up doing the same things
    that reloading the cells is (minus one short line of text).  The position
    of the arrow depends on data specific to each row; each row has an angle to
    a different target, which depends on the user's current position.  So every
    row's angle has to be calculated independently; it's not just an offset
    that changes with heading.  I could theoretically save some of the work if
    the user hasn't moved, but there's only a maximum of four rows involved.
  • On Jul 23, 2012, at 4:34 PM, Gavin Stokes wrote:

    Though probably off topic, this is unnecessary work. If you just want to change where your arrow is pointing, you should reach into the cells and change the position of the arrow - you shouldn't be doing a reload for that. Think something more like for (UITableViewCell* cell in [tableView visibleCells]) {cell.thumbImageView.transform = TRANSFORM_CALCULATION;}

    Thanks.  I thought about doing this, but I'd end up doing the same things that reloading the cells is (minus one short line of text).  The position of the arrow depends on data specific to each row; each row has an angle to a different target, which depends on the user's current position.  So every row's angle has to be calculated independently; it's not just an offset that changes with heading.  I could theoretically save some of the work if the user hasn't moved, but there's only a maximum of four rows involved.

    I meant you're putting more load on the system by doing this, not necessarily that you, the developer, are doing more work. Reaching inside the cells and changing something requires far less processing than issuing a reload which, at a minimum, must create/dequeue new cells.

    Luke
  • Thanks for that suggestion.  I'm only reloading visible cells, so I would
    expect them to already be instantiated and readily available in memory.
    However, if cellForRow... is called, I guess there is an attempt to
    dequeue.  I'd have to take a look at how much time is spent in that method.

    At any rate, it's clear that all of this can be done with adequate
    performance.  The problem only occurs when the table is scrolled within a
    50-pixel range of position, and that position varies (so far unpredictably)
    with row height.  If the table is scrolled more or less than that, it works
    fine.  The number of animating rows makes no difference (and there are only
    a few anyway).

    And performance isn't the only issue; the rendering is defective, with
    elements (like simple text labels) being rendered slightly out of position
    and cell images flying up from the bottom of the screen and settling into
    place.
  • On Jul 24, 2012, at 2:48 AM, Gavin Stokes <stokestack...> wrote:

    > And performance isn't the only issue; the rendering is defective, with
    > elements (like simple text labels) being rendered slightly out of position
    > and cell images flying up from the bottom of the screen and settling into
    > place.

    Typically if you see cells flying around like that, its an indication that you've left an old style animation block open (the begin/commitAnimations APIs).

    As for the rest, it doesn't make sense that, for example, the arrow renders multiple times over itself unless you are doing something relatively strange with -drawRect:'d drawing, but given what you've described implementing this functionality via -drawRect: would be a lot more work than is necessary.
    --
    David Duncan
previous month july 2012 next month
MTWTFSS
            1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29
30 31          
Go to today