Search code examples
iosperformanceuiscrollviewcore-graphicszooming

Efficiently draw a large scrollable area


I'm working on a simple game which uses a hexagonal grid layout. The grid is very large (a few thousand pixels in width and height). I need to be able to scroll and zoom it within a scrollView, and there are a lot of individual hexagons. I have written the drawing code in CoreGraphics. The hexagons are drawn in the drawRect: method of their view. This drawing code is called for each of the hexagons:

- (void)drawInContext:(CGContextRef)context colour:(UIColor *)colour size:(CGSize)size {
    CGFloat width = size.width;
    CGFloat height = size.height;

    CGFloat x = self.offset.x;
    CGFloat y = self.offset.y;

    CGContextMoveToPoint(context, (width/2)+x, y);
    CGContextAddLineToPoint(context, width+x, (height / 4)+y);
    CGContextAddLineToPoint(context, width+x, (height * 3 / 4)+y);
    CGContextAddLineToPoint(context, (width / 2)+x, height+y);
    CGContextAddLineToPoint(context, x, (height * 3 / 4)+y);
    CGContextAddLineToPoint(context, x, (height / 4)+y);
    CGContextClosePath(context);

    CGContextSetFillColorWithColor(context, colour.CGColor);
    CGContextSetStrokeColorWithColor(context, [[UIColor whiteColor] CGColor]);
    CGContextDrawPath(context, kCGPathFillStroke);

    NSString *text = [NSString stringWithFormat:@"I:%ld\nR:%ld\nC:%ld", self.creationIndex, self.row, self.column];
    [text drawAtPoint:CGPointMake(self.offset.x+20, self.offset.y+20) withAttributes:@{NSForegroundColorAttributeName: [UIColor blackColor], NSFontAttributeName: [UIFont systemFontOfSize:[UIFont systemFontSize]]}];
}

I call setNeedsDisplay on the view when a change is needed (like a hexagon changing colour). The problem is that this seems very inefficient. It takes approximately half a second for the map to redraw, which makes everything feel sluggish.

I have tried the following:

  • Calculate the visible rect of the scrollView and only draw that part of it. This causes problems when zooming to a different rect, as only the destination rect is drawn, causing black space to be displayed in the part being scrolled across.
  • Set a flag on the hexagons to indicate that they require an update, and only drawing the hexagons which have changed. This resulted in only the changed hexagons being visible, since drawRect: seems to fill the view in black before carrying out the drawing operation, rather than leaving the previous image there and drawing the changed hexagons over the top.
  • Using UIKit to build the grid of hexagons. This was simply too slow, as there were hundreds of individual views.

To summarise, my question is if there is a way of optimising CoreGraphics drawing, or if there is an alternative way of drawing which is more efficient.


Solution

  • There should not be any need to calc the visible rect, this is done by UIScrollView.

    See Scrollview Programming Guide

    Furthermore from the class documentation : The object that manages the drawing of content displayed in a scroll view should tile the content’s subviews so that no view exceeds the size of the screen. As users scroll in the scroll view, this object should add and remove subviews as necessary.