Prevent CALayer drawing from being scaled by superlayer transform

73 Views Asked by At

I'm writing a "TimelineView" that shows events on a horizontal timeline inside an NSScrollView. It's supposed to be pinch-zoomable, which works well when overriding NSScrollView's magnifyWithEvent: like this:

- (void)magnifyWithEvent:(NSEvent *)event {
    CGFloat relMag = 1.0 + [event magnification];
    
    NSRect contentViewBounds = self.contentView.bounds;
    contentViewBounds.size.width /= relMag; // smaller bounds = higher zoom
    self.contentView.bounds = contentViewBounds;
}

For the document view I'm passing a custom layer-backed NSView subclass that is set up like this:

- (instancetype)initWithFrame:(NSRect)frameRect {
    self = [super initWithFrame:frameRect];
    if (self) {
        [self setWantsLayer:YES]; // Ensure the view uses layer-backing
        self.layer.delegate = self;
        [self.layer setNeedsDisplay];
    }
    return self;
}

Drawing happens in the delegate method of that backing layer:

- (void)drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx
{
}

Here, I want to draw a horizontal ruler that displays time marks. This of course should change with magnification so that the time intervals get finer and new tick marks are added with higher zoom level.

But the problem is that all drawing I do inside drawLayer:inContext: is stretched horizontally and is pixelated when pinch-zoomed. So a tick mark point (small circle in my case) gets thicker, text gets stretched, etc.

This seems to happen because the affine transform of my superlayer (backing layer of NSClipView) is automatically adjusted to the altered frame-bounds-ratio. For example: magnifying horizontally by 2.0 means that the NSClipView with a frame of {100, 100} will get new bounds of {50, 100} so its backing layers affine transform will also be set to scale horizontally to 2.0, vertically to 1.0 - it makes sense.

Unfortunately, this is not reflected in the CTM of my "local" context in drawLayer:inContext:. Obviously, because it belongs to the superlayer.

But I need to handle the scaling differently and I have no use for auto-scaling like that. How can I suppress it?

I could "scale against" it like suggested here but that makes my image more blurry and pixelated with higher zoom.

There must be a way since CATiledLayer does exactly that, although not horizontal-only like I require.

0

There are 0 best solutions below