I figured this out by experimenting. The transform requires 3 steps instead of 2 (or, if not required, at least it works this way):
static inline CGContextRef myCTX(CGRect rect, CGFloat xLeft, CGFloat xRight, CGFloat yBottom, CGFloat yTop) {
CGAffineTransform translate1 = CGAffineTransformMakeTranslation(-xLeft, -yBottom);
CGAffineTransform scale = CGAffineTransformMakeScale( rect.size.width / (xRight - xLeft), -rect.size.height / (yTop - yBottom) );
CGAffineTransform transform = CGAffineTransformConcat(translate1, scale);
CGAffineTransform translate2 = CGAffineTransformMakeTranslation(1, rect.size.height);
transform = CGAffineTransformConcat(transform, translate2);
CGContextRef c = UIGraphicsGetCurrentContext();
CGContextConcatCTM(c, transform);
return c;
}
You use this function inside drawRect. In my case the xLeft, xRight, etc. values are properties of a UIView subclass and are set by the viewController. So the view's drawRect looks like so:
- (void)drawRect:(CGRect)rect
{
CGContextRef c = UIGraphicsGetCurrentContext();
CGContextSaveGState(c);
CGContextRef ctx = myCTX(rect, self.xLeft, self.xRight, self.yBottom, self.yTop);
…
all of the CGContextMoveToPoint(), CGContextAddLineToPoint(), calls to
draw your desired lines, rectangles, curves, etc. but not stroke or fill them
…
CGContextRestoreGState(c);
CGContextSetLineWidth(c, 1);
CGContextStrokePath(c);
}
The CGContextSetLineWidth call isn't needed if you want a line width of 1. If you don't restore the graphics state before strokePath the path width is affected by the scaling.
Now I have to figure out how to draw text onto the view.