简体   繁体   中英

UIBezierPath drawing takes up 100% of CPU

I have a UIBezierPath is drawn on the screen every time the user's touch is moved. This works fine on my simulator on a Mac Pro, but as soon as I moved it to a physical device, the drawing began to lag a lot. Using instruments, I checked the CPU usage, and it hits 100% while the user is drawing.

This becomes quite the problem because I have an NSTimer that is supposed to fire at 30fps, but when the CPU is overloaded by the drawing, the timer fires at only around 10fps.

How can I optimize the drawing so that it doesn't take 100% of the CPU?

UITouch* lastTouch;

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
    if(lastTouch != nil)
        return;

    lastTouch = [touches anyObject];
    [path moveToPoint:[lastTouch locationInView:self]];
    [self drawDot];
}

- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
    if([touches anyObject] == lastTouch)
        [self drawDot];
}

- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
    if([touches anyObject] == lastTouch)
        lastTouch = nil;
}

- (void)drawDot
{
    if(!self.canDraw)
        return;

    [path addLineToPoint:[lastTouch locationInView:self]];
    [self setNeedsDisplayInRect:CGRectMake([lastTouch locationInView:self].x-30, [lastTouch locationInView:self].y-30, 60, 60)];
}

- (void)drawRect:(CGRect)rect
{    
    CGColorRef green = [UIColor green].CGColor;
    CGContextRef context = UIGraphicsGetCurrentContext();

    CGColorRef gray = [UIColor gray].CGColor;
    CGContextSetStrokeColor(context, CGColorGetComponents(gray));
    [shape stroke];

    CGContextSetStrokeColor(context, CGColorGetComponents(green));
    [path stroke];
}

You shouldn't really be using -drawRect in modern code - it's 30 years old and designed for very old hardware (25Mhz CPU with 16MB of RAM and no GPU) and has performance bottlenecks on modern hardware.

Instead you should be using Core Animation or OpenGL for all drawing. Core Animation can be very similar to drawRect.

Add this to your view's init method:

self.layer.contentsScale = [UIScreen mainScreen].scale;

And implement:

- (void)drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx
{
   // basically the same code as you've got inDrawRect, although I recommend
   // trying Core Graphics (CGContextMoveToPoint(), CGContextAddLineToPoint(), 
   // CGContextStrokePath(), etc)
}

And to make it re-draw (inside touchesMoved/etc):

[self.layer setNeedsDisplay];

I would also update your touch event code to just append to an NSMutableArray of points (perhaps encoded in in an [NSValue valueWithCGPoint:location] ). That way you aren't creating a graphics path while responding to touch events.

A good place to look for efficient drawRect: is the wwdc video from a few years ago: https://developer.apple.com/videos/wwdc/2012/?id=238 . about 26m in he starts debugging a very simple painting app which is very similar to what you've described. At minute 30 he starts optimizing after the first optimization of setNeedsDisplayInRect: (which you're already doing).

short story: he uses an image as a backing store for what's already drawn so that each drawRect: call he only draws 1 image + very short additional line segments instead of drawing extremely long paths every time.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM