Search code examples
ioscore-graphicsfractals

drawRect performance when drawing many lines


I'm trying to draw a procedurally generated tree in a custom view's drawRect method. This is perfectly snappy up to about 20k lines. Beyond that point and drawing performance goes entirely into the crapper (i.e. nearly 1000ms to draw the tree).

The drawing code for drawing the tree is:

func drawBranch( branch: Branch, context :CGContext ) {        
    CGContextMoveToPoint(context,    (branch.originPoint().x), (branch.originPoint().y))
    CGContextAddLineToPoint(context, (branch.endPoint().x), (branch.endPoint().y))

    for b in branch.children {
        drawBranch(b, context: context)
    }
}

override func drawRect(rect: CGRect) {
    print("\(NSDate()) drawRect")

    // Setup graphics context
    let ctx = UIGraphicsGetCurrentContext()

    // clear context
    CGContextClearRect(ctx, rect)

    CGContextSetRGBFillColor(ctx, 0.0, 0.0, 0.0, 1.0)

    CGContextFillRect(ctx, rect)

    CGContextSaveGState(ctx);
    CGContextTranslateCTM(ctx, 0.0, rect.size.height);
    CGContextScaleCTM(ctx, 1.0, -1.0);
    CGContextSetShouldAntialias(ctx, true)


    CGContextSetRGBStrokeColor(ctx, 0.3, 0.3, 0.3, 1)

    if let tree = self.tree {
        drawBranch(tree, context: ctx)
    }

    CGContextStrokePath(ctx)
    CGContextRestoreGState(ctx);
}

Is there any hope for this ever being fast, or should I have given up a long time ago and moved on to some other rendering method/engine?

I read through this question here which seems to imply that it's never going to work, but that was 3 years ago and I'm wondering if anything has changed since then.


Solution

  • What you could do instead, is treat the path as data, i.e whenever something changes that affects the tree (such as a branch being added), recalculate the path and redraw, this way you only pay the 1sec penalty once.

    Basically, you would need to refactor your drawBranch function to instead of drawing into a context, return a CGPath object. This could then be kept on the view and redrawn in the drawRect: method.

    Another alternative would be to use CATiledLayer, but that assumes you have the ability of drawing only parts of the view at once (i.e you can split your drawing into tiles of 100x100 px for example). This wouldn't necessarily make it faster, but it would allow to avoid drawing parts of the tree not currently visible and only draw them once they are needed.

    In short, try separating the logic of determining the path of the tree (the model) from the presentation (drawing it into the CGContext). It is entirely possible that generating the tree path is an expensive operation, but at least by keeping it separate from the drawing, you can avoid blocking your main thread, as well as make sure the time consuming calculation is done as infrequently as possible.