1
votes

I have an animation using two views where I would call lockFocus and get at the graphics context of the second NSView with [[NSGraphicsContext currentContext] graphicsPort], and draw. That worked fine until macOS 10.14 (Mojave).

Found a reference here: https://developer.apple.com/videos/play/wwdc2018/209/

At 22:40 they talk about the "legacy" backing store that has changed. The above lockFocus and context pattern was big on the screen, saying that that won't work any more. And that is true. lockFocus() still works, and even gets you the correct NSView, but any drawing via the context does not work any more.

Of course the proper way to draw in a view is via Nsview's drawRect. So I rearranged everything to do just that. That works, but, drawRect has already automatically cleared the "dirty" area for you, prior to calling your drawRect. If you used setNeedsDisplayInRect: it will even clear only those areas for you. But, it will also clear areas made up of more than one dirty rectangle. And, it clears rectangular areas, while I draw roundish objects, so I end up with too much cleared away (black area):

black area is cleared

Is there a way to prevent drawRect to clear the background?

If not I will have switch to using the NSView's layer instead, and use updateLayer, or something.

Update: I am playing around with using the layers of NSView, returning TRUE for wantsLayer and wantsUpdateLayer, and implementing:

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

That is only called when I do:

- (BOOL) wantsLayer { return YES; }
- (BOOL) wantsUpdateLayer { return NO; } 

and use setNeedsDisplayInRect: or setNeedsDisplay:

Which indeed makes drawRect no longer called, but the same automatic background erase has already taken place by the time my drawLayer: is called. So I have not made any progress there. It really is the effect of setNeedsDisplayInRect, and not my drawing. Just calling setNeedsDisplayInRect causes these erases.

If you set:

- (BOOL) wantsLayer { return YES; }
- (BOOL) wantsUpdateLayer { return YES; } 

the only thing that is called is:

- (void) updateLayer

which does not provide me with a context to draw.

I had some hope for:

self.layerContentsRedrawPolicy = NSViewLayerContentsRedrawNever;

The NSView doc says:

Leave the layer's contents alone. Never mark the layer as needing display, or draw the view's contents to the layer. This is how developer created layers (layer-hosting views) are treated.

and it does exactly that. It doesn't notify you, or call delegates.

Is there a way to have complete control over the content of an NSView/layer?

UPDATE 2019-JAN-27: NSView has a method makeBackingLayer that is not there for nothing, I guess. Implemented that, and it seems to work, basically, but no output shows on screen. Hence the followup question: nsview-makebackinglayer-with-calayer-subclass-displays-no-output

2

2 Answers

1
votes

Using NSView lockFocus and unlockFocus, or trying to access the window's graphics contents directly not working in macOS 10.14 anymore.

You can create an NSBitmapImageRep object and update drawing in this context.

Sample code:

- (void)drawRect:(NSRect)rect {
    if (self.cachedDrawingRep) {
        [self.cachedDrawingRep drawInRect:self.bounds];
    }
}

- (void)drawOutsideDrawRect {
     NSBitmapImageRep *cmap = self.cachedDrawingRep;
    if (!cmap) {
        cmap = [self bitmapImageRepForCachingDisplayInRect:self.bounds];
        self.cachedDrawingRep = cmap;
    }
    NSGraphicsContext *ctx = [NSGraphicsContext graphicsContextWithBitmapImageRep:cmap];
    NSAssert(ctx, nil);
    [NSGraphicsContext setCurrentContext:ctx];

    // Draw code here

    self.needsDisplay = YES;
 }
0
votes

You can try overriding method isOpaque and return YES from there. This will tell OS that we draw all pixels ourselves and it do not need to draw the views/window at the back of our view.

- (BOOL)isOpaque {
    return YES;
}