How can I change NSPopover
background color include triangle part?
Unfortunately, this isn't really possible. NSPopover wasn't designed to be customizable. If you want a completely customized appearance, your best bet is probably to use a third-party, open-source NSPopover replacement like INPopoverController.
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
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.
NSView
subclass worked for me. It's a bit less code than the accepted answer (which is always nice). Thanks! –
Gumbotil 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)
}
}
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");
Here's the easiest solution to this issue that I've found. No need to subclass NSView or add any subviews.
NSView *popoverView = [[[[self popover] contentViewController] view] superview];
[popoverView setWantsLayer:YES];
[[popoverView layer] setBackgroundColor:[[NSColor colorWithWhite:0.8 alpha:1] CGColor]];
Get the superview of the root view of the popover, set its layer's background color.
NSPopover
? –
Gumbotil Stefanf & Mike Bedar's solution for Swift 4:
- Go to File->New File->Cocoa Class
- Name your class. eg. PopoverContentView. Make sure it is a subclass of NSView
- Set the contents of the file to:
import AppKit
final 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 = NSView.AutoresizingMask([.width, .height]);
frameView.addSubview(backgroundView!, positioned: NSWindow.OrderingMode.below, relativeTo: frameView)
}
}
}
}
final class PopoverBackgroundView:NSView {
override func draw(_ dirtyRect: NSRect) {
NSColor.green.set()
dirtyRect.fill(using: .copy)
}
}
In your storyboard, select the view which has your popover content and go to the Identity Inspector
Set the Class to PopoverContentView
Your popover and its triangle will now be green.
Unfortunately, this isn't really possible. NSPopover wasn't designed to be customizable. If you want a completely customized appearance, your best bet is probably to use a third-party, open-source NSPopover replacement like INPopoverController.
The solution by Mike Bedar is working, but with adding just one more thing (in order to get the triangle filled with background color), and that is to override the draw function inside the PopoverContentView as well:
override func draw(_ dirtyRect: NSRect) {
backColor.set() // backColor is NSColor
NSRectFill(self.bounds)
}
© 2022 - 2024 — McMap. All rights reserved.