After trying all the approaches, the solution I am using now is a custom subclass of CALayer. I don't use CATextLayer at all.
I override the contentsScale property with this custom setter method:
- (void)setContentsScale:(CGFloat)cs
{
CGFloat scale = MAX(ceilf(cs), 1.); // never less than 1, always integer
if (scale != self.contentsScale) {
[super setContentsScale:scale];
[self setNeedsDisplay];
}
}
The value of the property is always rounded to the upper integer value. When the rounded value changes, then the layer must be redrawn.
The display method of my CALayer subclass creates a bitmap image of the size of the text multiplied by the contentsScale factor and by the screen scale factor.
- (void)display
{
CGFloat scale = self.contentsScale * [MyUtils screenScale];
CGFloat width = self.bounds.size.width * scale;
CGFloat height = self.bounds.size.height * scale;
CGContextRef bitmapContext = [MyUtils createBitmapContextWithSize:CGSizeMake(width, height)];
CGContextScaleCTM(bitmapContext, scale, scale);
CGContextSetShouldSmoothFonts(bitmapContext, 0);
CTLineRef line = CTLineCreateWithAttributedString((__bridge CFAttributedStringRef)(_text));
CGContextSetTextPosition(bitmapContext, 0., self.bounds.size.height-_ascender);
CTLineDraw(line, bitmapContext);
CFRelease(line);
CGImageRef image = CGBitmapContextCreateImage(bitmapContext);
self.contents = (__bridge id)(image);
CGImageRelease(image);
CGContextRelease(bitmapContext);
}
When I change the scale factor of the root layer of my hierarchy, I loop on all text layers and set the contentsScale property to the same factor. The display method is called only if the rounded value of the scale factor changes (i.e. if the previous value was 1.6 and now I set 1.7, nothing happens. But if the new value is 2.1, then the layer is redisplayed).
The cost in terms of speed of the redraw is little. My test is to change continuously the scale factor of a hierarchy of 40 text layers on an 3rd gen. iPad. It works like butter.