How to change NSPopover background color include triangle part?
Solution 1:
It's actually much simpler and you won't need private API.
Make the root view of your view controller a custom class
@implementation MyPopoverRootView
-(void)viewDidMoveToWindow
{
NSView * aFrameView = [[self.window contentView] superview];
MyPopoverBackgroundView * aBGView =[[MyPopoverBackgroundView alloc] initWithFrame:aFrameView.bounds];
aBGView.autoresizingMask = NSViewWidthSizable | NSViewHeightSizable;
[aFrameView addSubview:aBGView positioned:NSWindowBelow relativeTo:aFrameView];
[super viewDidMoveToWindow];
}
@end
Your background view just draws the desired color in its bounds.
@implementation MyPopoverBackgroundView
-(void)drawRect:(NSRect)dirtyRect
{
[[NSColor whiteColor] set];
NSRectFill(self.bounds);
}
@end
Solution 2:
Swift 3
override func viewDidMoveToWindow() {
guard let frameView = window?.contentView?.superview else {
return
}
let backgroundView = NSView(frame: frameView.bounds)
backgroundView.wantsLayer = true
backgroundView.layer?.backgroundColor = .white // colour of your choice
backgroundView.autoresizingMask = [.viewWidthSizable, .viewHeightSizable]
frameView.addSubview(backgroundView, positioned: .below, relativeTo: frameView)
}
If you want to change only the background colour of the popover (including the triangle/arrow), I figured that you don't need to create a subclass of NSView. A layer-backed NSView with a background colour should suffice.
Also, you don't need to call super.viewDidMoveToWindow()
because its default implementation does nothing.
Solution 3:
Thanks to Stefanf I got this working. Here is a Swift version of the View code. As noted, this should be the class for the View set as your NSPopOver contentView.
class PopoverContentView:NSView {
var backgroundView:PopoverBackgroundView?
override func viewDidMoveToWindow() {
super.viewDidMoveToWindow()
if let frameView = self.window?.contentView?.superview {
if backgroundView == nil {
backgroundView = PopoverBackgroundView(frame: frameView.bounds)
backgroundView!.autoresizingMask = NSAutoresizingMaskOptions([.ViewWidthSizable, .ViewHeightSizable]);
frameView.addSubview(backgroundView!, positioned: NSWindowOrderingMode.Below, relativeTo: frameView)
}
}
}
}
class PopoverBackgroundView:NSView {
override func drawRect(dirtyRect: NSRect) {
NSColor.redColor().set()
NSRectFill(self.bounds)
}
}
Solution 4:
I faced the same problem, but I'm trying to avoid adding third party UI elements to my project, so I looked further. It seems if you override drawRect:
in the view of your popover's contentViewController
with setting a color like:
[[NSColor whiteColor] setFill];
NSRectFill([self bounds]);
then you'll end up having a popover with white background, except the triangle/arrow that connects it to the rect it is relative to. For solving that, you have to access the popover's border view which happened to contain the arrow:
NSView* borderView = [self.view.window valueForKeyPath:@"_borderView"];
I know, it is a private API, but if your goals not include submitting your app to the App Store, this is the easiest way to go. Now you can override the drawRect:
for this view as well. To avoid problems like having the private _borderView
property renamed with an SDK update, I suggest to assert for the borderView's existence before referencing it.
NSAssert(borderView, @"_borderView does not exist");