Why does a custom child view of a UIScrollView get redrawn entirely and not only partially and how to change this?

StackOverflow https://stackoverflow.com/questions/16776944

Question

I have a UIScrollView and its child, a custom view, which has way more width than the UIScrollView. At certain times, I enlarge the custom view (width only) and I want to redraw only the part by which it was enlarged. That's the whole problem. The rest is just details of how I try to achieve this.

After some searching, I found the property UIView.contentMode, which should be exactly for this purpose. I've played around with it (e.g. setting it to UIViewContentModeLeft) but I couldn't get any effect out of this. I've not used this property before, so it's possible that I didn't use it correctly.

My problem is that the drawRect:(CGRect)rect method of the custom view always gets called with the complete size of the custom view, not just with the rect by which it was enlarged. This causes performance problems, becasue the drawing cycles take longer and longer the more the custom view grows.

What I do in my updating code:

dispatch_async( dispatch_get_main_queue(), ^{
    uiScrollView.contentSize = ...; // increase width of contentSize
    customView.frame = ...; // increase width of customView accordingly

    CGRect visibleRect = [uiScrollView convertRect:uiScrollView.bounds toView:customView];

    printf("setNeedsDisplayInRect: %s\n", NSStringFromCGRect(visibleRect).UTF8String);

    [customView setNeedsDisplayInRect:visibleRect];
});

However, the rect I specify above does not reach the drawRect: method in the customView, where I also log the rect like this:

- (void) drawRect:(CGRect)rect {
    printf("drawRect: %s\n", NSStringFromCGRect(rect).UTF8String);

    // drawing code here
    // ...
}

Representative original output:

...
setNeedsDisplayInRect: {{426, 0}, {320, 192}}
setNeedsDisplayInRect: {{427, 0}, {320, 192}}
setNeedsDisplayInRect: {{427, 0}, {320, 192}}
setNeedsDisplayInRect: {{427, 0}, {320, 192}}
drawRect: {{0, 0}, {587, 192}}

Background: I am trying to create a scrolling waveform view. When recording, the latest recorded waveform should be displayed. I could achieve this with simply a round buffer and a custom view, but after recording, the waveform (track) shall be displayed as well and the user shall be able to scroll using standard gestures, so I would like to also use the UIScrollView+customView UI structure while recording as well to stay consistent in the UI structure between the two modes of operation.

Updates:

It just occured to me that I try to redraw the visible part of the customView and not the part by which it was enlarged. However, that does not change the overall problem. I've meanwhile tried it with the rect by which the custom view was enlarged, which is a 1-coordinate unit wide rect in this case, but with the exact same problem: drawRect: still gets called with the complete size of the custom view to be redrawn.

This also leads to the question: The biggest part of my custom view is usually invisible because it is scrolled out of the displayed area. Why does iOS tell my custom view to redraw this anyways invisible area?

Was it helpful?

Solution

I think the reason is that in iOS a call to -setNeedsDisplayInRect: will always mark the entire view for updates. See a similar question here and this Technical Note.

A solution could be to split up your waveform view in several subviews and call setNeedsDisplay only for those with changed contents.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top