2

I have been working on a NSStatusItem user agent that adds a NSView subclass as a subview of NSStatusItem.button to provide animated icons.

When left clicking on the icon, there is a noticeable opaque border around the view.

The view subclass's init reads:

- (instancetype)initWithFrame:(NSRect )rect
{
    self = [super initWithFrame:rect];
    if ( self ) {
        self.wantsLayer = YES;
        self.layer.opaque = NO;
        [self.layer addSublayer:self.background];
        [self.layer addSublayer:self.foreground];
        [self.layer addSublayer:self.symbol];
    }
    return self;
}

The view, StatusView, is added by a controller to the NSStatusBarItem's button:

    self.statusItem.highlightMode = YES;
    [self.statusItem.button addSubview:self.statusView];

Here is what it looked like after a left mouse down:

Opaque border around view in highlight mode

Not really the polished look I was hoping for.

Erik
  • 898
  • 8
  • 20

1 Answers1

1

I spent a lot of time reading the documentation trying to figure out how get rid of that border. I checked for opacity in my view, layer and sublayers. I set background colors to clear. I messed with alpha. I re-wrote my view without layers. Nothing could get rid of that opaque bound and I was about ready to throw in the towel and provide my content via a NSImage with setTemplate:YES.

As a last ditch effort to understand what was happening, I wrote a short method to traverse the view's hierarchy looking for the view that was opaque:

- (void)displayViewInfo:(NSView *)view
{
    NSLog(@"view %@\tisOpaque %d, vibrancy %d super %@",
          view,view.isOpaque,view.allowsVibrancy, view.superview);
    for (NSView *v in view.subviews){
         [self displayViewInfo:v];
    }
}

I found the property opaqueAncestor while poring over the documentation for NSView and figured that might be a good place to start looking.

Called from my NSView subclass's drawLayer:inContext: method thusly.

[self displayViewInfo:self.opaqueAncestor];

The output (with timestamp info trimmed) looked like :

view <NSNextStepFrame: 0x610000181380>   isOpaque 1, vibrancy 0 super (null)
view <NSVisualEffectView: 0x1006023e0>   isOpaque 0, vibrancy 1 super <NSNextStepFrame: 0x610000181380>
view <NSStatusBarButton: 0x600000161bc0> isOpaque 0, vibrancy 1 super <NSVisualEffectView: 0x1006023e0>
view <StatusView: 0x6000001a02a0>        isOpaque 0, vibrancy 0 super <NSStatusBarButton: 0x600000161bc0>

The opaqueAncestor of StatusView turned out to be the root view for the hierarchy and all the subviews were reporting not opaque. I added the allowVibrancy check after a few mutations of the method. I found it interesting that the superviews of StatusView were allowing vibrancy and my view was not. The documentation on the property allowVibrancy was vaguely promising, so I added this to my view:

- (BOOL)allowVibrancy { return YES; }

The result speaks for itself:

Highlight mode properly surrounding view

Hopefully this tale of woe and redemption will save somebody some time; it sure had me baffled.

Erik
  • 898
  • 8
  • 20