6

From what I can tell by reading Apple's documentation, UI updates can only be run on the main thread.

My app does some rendering in the top half of the screen and has a table view in the bottom half. If the user is scrolling the table while the top half re-draws itself, the table locks up for half a second or so. Is there any way I can improve this situation?

Randall
  • 2,083
  • 2
  • 18
  • 20
  • Good question and the reason is the scroll animation blocks other events using the `NSDefaultRunLoop`. Although I know what it is, I do not have a solution. – Joe Aug 01 '11 at 19:44
  • minimize the time the top-half spends re-drawing and the time it takes to draw the cells. related question http://stackoverflow.com/questions/2266125/uitableview-slow-even-with-just-a-few-objects – falconcreek Aug 01 '11 at 22:26
  • 2
    If drawing a view takes a noticeable amount of time, I would suggest drawing it into an image in a background thread when updating, and having the view simply draw the image. This would prevent the main thread from blocking. – ughoavgfhw Aug 01 '11 at 22:31
  • @ughoavgfhw, you should list that in an answer rather than a comment so the poster can accept it. – Rob Napier Aug 02 '11 at 03:10

2 Answers2

1

If drawing a view takes a noticeable amount of time, I would suggest drawing it into an image in a background thread when updating, and having the view simply draw the image. This would prevent the main thread from blocking for as long.

The following code shows how to create and draw into a bitmap context from a background thread. When you call the updateInBackground method, it will create a new background thread which creates and draws into a context, then creates an image using that context. If you put this in a custom subclass of UIImageView, then the image will be drawn to the screen automatically.

- (void)updateInBackground {
    [self performSelectorInBackground:@selector(_drawInBackground:) withObject:[NSValue valueWithCGRect:self.bounds]];
}
- (void)_drawInBackground:(NSValue *)boundsValue {
    NSAutoreleasePool *pool = [NSAutoreleasePool new];
    CGRect bounds = [boundsValue CGRectValue];
    CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
    if(!colorSpace) {
        [pool drain];
        return;
    }
    CGContextRef context = CGBitmapContextCreate(NULL,bounds.size.width, bounds.size.height, 8, bounds.size.width * 32, colorSpace, kCGImageAlphaPremultipliedFirst);
    CGColorSpaceRelease(colorSpace);
    if(!context) {
        [pool drain];
        return;
    }
    CGContextConcatCTM(context, CGAffineTransformMake(1,0,0,-1,0,bounds.size.height));

    // do drawing here

    CGImageRef image = CGBitmapContextCreateImage(context);
    [self performSelectorOnMainThread:@selector(setImage:) withObject:[UIImage imageWithCGImage:image] waitUntilDone:YES];
    CGImageRelease(image);
    CGContextRelease(context);
    [pool drain];
}

When drawing in a background thread, you can NOT use UIKit objects and methods. All drawing must be done using Quartz functions. If you need to use UIKit, the background thread could call a method on the main thread to do that section of the drawing:

[self performSelectorOnMainThread:@selector(drawInMainThread:) withObject:[NSValue valueWithPointer:context] waitUntilDone:YES];

- (void)drawInMainThread:(NSValue *)value {
    UIGraphicsPushContext((CGContextRef)[value pointerValue]);
    // do drawing using UIKit
    UIGraphicsPopContext();
}
ughoavgfhw
  • 39,734
  • 6
  • 101
  • 123
0

Sounds like the drawing of the top half is taking a while. Have you run it through instruments?

Its important to remember that if you want a smooth 60FPS scrolling, your rendering(and any other cpu on the main thread) has to take less than 16 MS per loop otherwise you'll start dropping frames.

MayorJustin
  • 281
  • 2
  • 6