objective-ccocoacalayerappkitnscontrol

Layer-Backed NSControl Still Calls NSCell Drawing Routines


Context:

Apple has "soft-deprecated" NSCell on macOS. I'm trying to reduce the use of it in my custom NSControl subclasses and, instead, use CALayers to handle my drawing. To do this, I make my NSButton subclass layer-backed:

self.wantsLayer = YES;

And then I attempt to handle my drawing using the "updateLayer" path instead of "drawRect":

- (BOOL) wantsUpdateLayer {
    return YES;
}

- (void) updateLayer { 
     // Change layer background/border colors if pressed, disabled, etc.
     // Change text color of a CATextLayer sublayer for button's title. 
}

The Problem:

If I use -updateLayer, the NSCell still receives drawing commands and draws its title, resulting in a "blurry" title string in my control because the string is being drawn twice—once in my textLayer and once by the Cell.

If, however, I do the exact same work as above in -drawRect: instead of -updateLayer, then the cell does NOT do any drawing (because I don't call down into it with [self.cell drawInteriorWithFrame:ControlView:])

I cannot, for the life of me, figure out what's firing the cell drawing when the associated controlView (my button subclass) has opted into -updateLayer instead of -drawRect:.

I have looked at all the methods on NSControl.h and NSView.h. I've overridden all of the -updateCell and associated methods. None of those are the culprit. I cannot simply set self.cell=nil because NSControl still relies on NSCell for event handling, etc.

Can someone tell me how to stop NSCell from drawing when its associated controlView is using -updateLayer instead of -drawRect:?


Solution

  • In -[NSButtonCell layoutLayerWithFrame:inView:], the cell adds a NSTextField to draw the title. Override titleRectForBounds: and return NSZeroRect to remove the title.