NSPopover color - objective-c

Is there any way to color NSPopover? Ive seen apps like facetab etc that have cool colors and resizeable popovers, how is this done?
Ay guides, hints?
Thanks.

Set popover.contentViewController.view as a subclass of NSView with a custom background drawing (i.e. override drawRect: and fill a rect with your custom background color).
Then set the popover.appearance = NSPopoverAppearanceHUD to remove the default border around the view.
Note that there will still be a very thin border around the view, so if you want to remove it completely, you may want to use MAAttachedWindow or a similar solution.

In Swift 4:
Go to File->New File->Cocoa Class
Name your class. eg. PopColor. Make sure it is a subclass of NSView
Set the contents of the file to:
import Cocoa
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)
}
}
}
}
class PopoverBackgroundView:NSView {
override func draw(_ dirtyRect: NSRect) {
NSColor.green.set()
self.bounds.fill()
}
}
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.

You can use MAAttachedWindow instead.

You can subclass NSView and set it as the NSPopover's view controller's view.

Yes and no. Unfortunately NSPopover isn't designed to be customisable. You can use some simple hacks for adding additional background view behind contentViewController's view and colorise or customise it as you want. In this case, you can get the customisable background that will be masked the same as generic NSPopover border and tail.
For more details you can take a look at the code of NSPopover+MISSINGBackgroundView category that implements this approach or just use this piece of code as CocoaPod library.

The complete code to change the color of NSPopover including the triangle is here:
I assume people have hooked the popover outlets and methods
#import "AppDelegate.h"
#interface MyPopoverBackgroundView : NSView
#end
#implementation MyPopoverBackgroundView
-(void)drawRect:(NSRect)dirtyRect
{
[[NSColor redColor] set];
NSRectFill(self.bounds);
}
#end
//===============================================================================================
#interface MyPopView : NSView
#end
#implementation MyPopView
-(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
//-----------------------------------------------------------------------------------------
#interface AppDelegate ()
#property (weak) IBOutlet NSWindow *window;
#end
#implementation AppDelegate
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
// Insert code here to initialize your application
//close when clicked outside
[self.popover setBehavior:NSPopoverBehaviorTransient];
//change its color
MyPopView *myPopview = [MyPopView new];
[self.popover.contentViewController.view addSubview:myPopview];
}
- (void)applicationWillTerminate:(NSNotification *)aNotification {
// Insert code here to tear down your application
}
- (IBAction)closePopover:(id)sender {
[self.popover close];
}
- (IBAction)showPopover:(id)sender {
[self.popover showRelativeToRect:[sender bounds]
ofView:sender
preferredEdge:NSMaxYEdge];
}
#end

This is what I did to change the popover color.
Assuming that you have properly defined your NSPopover:
//AppController.h
#import <Foundation/Foundation.h>
#interface AppController : NSObject
#property (weak) IBOutlet NSPopover errorPopover;
// whatever else you have ...
#end
//AppController.m
#import "AppController.h"
#implementation AppController
#synthesize errorPopover = _errorPopover;
// whatever else you have ...
-(IBAction)doSomethingThatCallsPopover:(id)sender {
_errorPopover.appearance = NSPopoverAppearanceHUD; //set color of error popup
[[self errorPopover] showRelativeToRect:[sender bounds] ofView:sender preferredEdge:NSMaxXEdge];
}
#end
NSPopover Class Reference - I really wish they would provide usage code in the developer docs.

Related

Unable to set content in NSPopover

I'm showing an NSPopover in an NSView, originating from a point on an NSBezierPath. I'm able to show the popover without a problem, but I can't seem to set the string value of the two text fields in it. The popover and the content view are both a custom subclass of NSPopover and NSViewController, respectively. The NSPopover subclass is also the NSPopover's delegate, although I don't implement any delegate methods, so I'm not sure I even need to do that.
Here is my subclass of NSViewController:
#import <Cocoa/Cocoa.h>
#interface WeightPopoverViewController : NSViewController
#end
#import "WeightPopoverViewController.h"
#interface WeightPopoverViewController ()
#end
#implementation WeightPopoverViewController
- (id)init {
self = [super initWithNibName:#"WeightPopoverViewController" bundle:nil];
if (self) {
}
return self;
}
#end
And my subclass of NSPopover:
#import <Cocoa/Cocoa.h>
#interface WeightPopoverController : NSPopover <NSPopoverDelegate> {
NSTextField *dateLabel;
NSTextField *weightLabel;
}
#property (strong) IBOutlet NSTextField *dateLabel;
#property (strong) IBOutlet NSTextField *weightLabel;
#end
#import "WeightPopoverController.h"
#implementation WeightPopoverController
#synthesize weightLabel;
#synthesize dateLabel;
#end
This is the code in my NSView subclass that opens up the popover:
#interface WeightGraphViewController () {
WeightPopoverController *popover;
WeightPopoverViewController *vc;
}
...
-(void)mouseEntered:(NSEvent *)theEvent {
// initialize the popover and its view controller
vc = [[WeightPopoverViewController alloc] init];
popover = [[WeightPopoverController alloc] init];
// configure popover
[popover setContentViewController:vc];
[popover setDelegate:popover];
[popover setAnimates:NO];
// set labels
for (id key in (id)[theEvent userData]) {
[popover.weightLabel setStringValue:[(NSDictionary*)[theEvent userData] objectForKey:key]];
[popover.dateLabel setStringValue:key];
}
// set the location
(redacted, irrelevant)
// show popover
[popover showRelativeToRect:rect ofView:[self window].contentView preferredEdge:NSMaxYEdge];
}
-(void)mouseExited:(NSEvent *)theEvent {
[popover close];
popover = nil;
}
In WeightPopoverViewController.xib, I've set the File's Owner to WeightPopoverViewController and connected the view to the custom NSView. In this xib I also have an Object set to WeightPopoverController with the dateLabel and weightLabel connected to their text fields and the contentViewController set to File's Owner.
I think where I am going wrong is likely related to how I have configured my class / instance variables for the NSPopover, but from the research I've done and documentation I've read I can't seem to crack where I've gone wrong. Any help would be appreciated.
UPDATE:
I removed the NSPopover subclass from code and from IB. I put my outlets in my NSViewController and connected them in IB. However, I'm still not able to set the string values. The following won't compile with the error "Property 'weightLabel' not found on object of type NSPopover*'".
#interface WeightGraphViewController () {
NSPopover *popover;
...
}
-(void)mouseEntered:(NSEvent *)theEvent {
vc = [[WeightPopoverViewController alloc] init];
popover = [[NSPopover alloc] init];
[popover setContentViewController:vc];
[popover.dateLabel setStringValue:#"test"];
}
I have the property definition exactly as I had it in my NSPopover subclass, but now in my NSViewController. This is actually what I had before, and since I wasn't able to set the properties from the NSViewController, I figured I needed to do it through a subclass of NSPopover. This is why I thought I am having an issue with how I have configured my class / instance variables.
You seem to be creating two popovers, one in code (popover = [[WeightPopoverController alloc] init]) and one in Interface Builder (In this xib I also have an Object set to WeightPopoverController). Have a think about what you’re trying to achieve.
I would also advise against subclassing NSPopover. I believe this is causing confusion and is unnecessary. Instead, put the outlets to your dateLabel and weightLabel in the popover’s content view controller.
I've experienced something that I think is similar. The root problem is that the "outlets" connecting your view (XIB) to your controller are not initialized until after the view has been displayed. If the controller tries to set properties on any UI controls in the view before the popover has been opened, those changes are ignored (since all the controls will be nil).
Luckily, there's an easy solution (as mentioned in this answer): just invoke the view getter on your controller, and it will force the view to initialize sooner.
In other words:
popover = [NSPopover new];
myController = [[MyViewController alloc] initWithNibName:#"MyView" bundle:nil];
popover.contentViewController = myController;
[myController view]; // force view to initialize
...set some values on myController... // works because view is now loaded
[popover showRelativeToRect: ...];

Method of one class not executing when called form a different class

I am working on a front end for a project in objective-c and I am having some trouble getting methods of my class Window which is a subclass of NSViewController to fully execute when called from a different class.
I have a method of the class Window that is called setColor which changes the color of my NSTableView variable which is linked to a bordered scroll view in my interface. I am able to successfully change the color by calling the setColor method like this from the init method in Window: [self setColor :self];
However when I do this [window1 setColor: window1] with window1 being an object of the class Window that I have declared in class Door, nothing seems to happen since the color of the boarded scroll view remains the same.
My Window.h file looks like this:
#interface Window : NSViewController {
#public
IBOutlet NSTableView *dataTableView;
}
#property (retain) IBOutlet NSTableView *tableView;
- (IBAction)SetColor:(id)sender;
#end
My Window.m looks like this:
#synthesize tableView;
- (void) awakeFromNib {
// [self SetColor :self];
}
- (IBAction)SetColor:(id)sender;
{
NSLog(#"changing the color");
[self->tableView setBackgroundColor: NSColor.blueColor];
}
Door.h looks like this
#interface Door : NSViewController {
Window* window1;
}
-(IBAction)buttonPress:(id)sender;
#property (retain) Window* window1;
#end
Door.m looks like this:
-(void) dealloc{
[window1 release];
}
-(id)init{
self = [super init];
if(self){
window1 = [Window alloc];
}
-(IBAction)buttonPress :(id)Sender;
{
[window1 setColor: window1];
}
I am using Xcode 3.2 so I cannot use ARC.
window1 = [Window alloc] will not load a Nib or storyboard and connect the outlet IBOutlet NSTableView *dataTableView to the table view inside it.
If "Window" is a view controller, you need to initialize it and the outlets in it a more standard way. View controllers need the proper initialization or the outlets are nil, and in Objective-C, if you send a method to nil, it just does nothing.

Custom drawRect: in UINavigationBar subclass

I want to subclass UINavigationBar so that I can do custom drawing in its drawRect:.
Here's my code:
Navigation Controller (.h)
#interface CORENavigationController : UINavigationController
#property (strong, nonatomic) CORENavigationBar *customNavigationBar;
#end
Navigation Controller (.m)
#implementation CORENavigationController
#synthesize customNavigationBar = _customNavigationBar;
- (UINavigationBar *)navigationBar {
if (![self customNavigationBar]) {
[self setCustomNavigationBar:[[CORENavigationBar alloc] init]];
}
return [self customNavigationBar];
}
Navigation Bar (.h)
#interface CORENavigationBar : UINavigationBar
#end
Navigation Bar (.m)
#implementation CORENavigationBar
- (void)drawRect:(CGRect)rect
{
[super drawRect:rect];
}
The issue is that the navigation bar has no title, e.g. all I can see is just the standard blue background.
As soon as I remove the following method, the title is back there:
- (UINavigationBar *)navigationBar {
if (![self customNavigationBar]) {
[self setCustomNavigationBar:[[CORENavigationBar alloc] init]];
}
return [self customNavigationBar];
}
Why does the title disappear? I call the super's drawRect: and do not change anything else.
Thanks!
Found it! Here's the answer:
https://stackoverflow.com/a/9610801/1306956
In this case, it'd be:
[navigationController setValue:[[CORENavigationBar alloc] init] forKeyPath:#"navigationBar"];
Tested on iOS 5.1.1 (9B206). Works like a charm.

Connect NSImageView to view using IB?

I've only programmed on the iPhone so far, so Cocoa is sort of confusing in certain ways for me. Here's where I've hit a snag. I wanted my window so that the background was invisible, and without a title-bar. Something like this:
Here's how I'm doing it:
I set my window's class to a custom window, which I've created like this:
CustomWindow.h
#import <Foundation/Foundation.h>
#import <Cocoa/Cocoa.h>
#interface CustomWindow : NSWindow {
#private
NSPoint initialLocation;
}
#property(assign)NSPoint initialLocation;
#end
CustomWindow.m
//trimmed to show important part
#import "CustomWindow.h"
#implementation CustomWindow
#synthesize initialLocation;
- (id)initWithContentRect:(NSRect)contentRect styleMask:(NSUInteger)aStyle backing:(NSBackingStoreType)bufferingType defer:(BOOL)flag {
// Removes the window title bar
self = [super initWithContentRect:contentRect styleMask:NSBorderlessWindowMask backing:NSBackingStoreBuffered defer:NO];
if (self != nil) {
[self setAlphaValue:1.0];
[self setOpaque:NO];
}
return self;
}
#end
Now, in my .xib file for this window I've added a custom view onto the window. I've set the view class to a custom class I've created that inherits from NSView. Here's how I'm setting that up:
MainView.h
#import <Cocoa/Cocoa.h>
#interface MainView : NSView {
#private
//nothing to see here, add later
}
#end
MainView.m
//trimmed greatly again to show important part
#import "MainView.h"
#implementation MainView
- (id)initWithFrame:(NSRect)frame
{
self = [super initWithFrame:frame];
if (self) {
}
return self;
}
- (void)drawRect:(NSRect)rect {
// Clear the drawing rect.
[[NSColor clearColor] set];
NSRectFill([self frame]);
}
#end
So here's my question. I've added a NSImageView to my custom view (MainView) in Interface Builder. However, for some reason I can't figure out how to connect this image view to an instance variable in my custom view. They seem like they can't be connected like I normally would if I was creating an iPhone app. Any ideas how this would be done?
You connect objects created in your XIB in Mac OS X the same way you do for iOS programs. Just add an NSImageView property to your main view, mark it as an IBOutlet and connect it up.
For example,
In MainView.h create a property for your NSImageView and make it an IBOutlet:
#import <Cocoa/Cocoa.h>
#interface MainView : NSView {
NSImageView *imageView;
}
#property(retain) IBOutlet NSImageView *imageView;
#end
In interface builder, make sure the class for the custom view is set to MainView, to do this click on the File's Owner object in the custom view XIB and then select the identity option in the inspector and enter MainView as the class type.
Next, CTRL+click File's owner and drag the arrow to the NSImageView and select the imageView outlet.
That's all there is to it. You should be able to reference the image view from code now.

Why isn't my UIButton responding to touches?

I'm sure I'm overlooking the obvious as I've got countless working buttons...but...for whatever reason this one is not cooperating...
I've added a UIButton (Rounded Rect) to a UIView subclass (DialogView) which is a subview of my view controller's view. This subview is created almost entirely in IB. I've wired up the button to (IBAction)okButtonPressed:(id)sender in IB to Touch Up Inside and created a corresponding method in DialogView. However when I "touch" this button it doesn't trigger the method. userInteractionEnabled is true for the VC's view, DialogView and the UIButton.
Thinking maybe initWithCoder had to do some frame manipulation or something I added the following which successfully logs to console.
- (id)initWithCoder:(NSCoder *)decoder {
if (self = [super initWithCoder:decoder]) {
NSLog(#"DialogView initWithCoder called");
}
return self;
}
In further exploration I wired up an IBOutlet to the button and then if I try to change the titleLabel from the view controller I notice that it get's severely truncated. Default text of say "Press Me!" set in IB displays fine when view is first drawn. But if I change the text...
self.DialogView.okButton.titleLabel.text = #"Not Working";
...it gets truncated to "N..."
Dunno if this is related. Probably...
Anyone see what I've screwed up here?
Edit (adding code related to showing UIButton):
From the View Controller:
self.DialogView = [[[NSBundle mainBundle] loadNibNamed:#"DialogView" owner:self options:nil] objectAtIndex:0];;
self.DialogView.myVC = self;
self.DialogView.backgroundColor = [UIColor clearColor];
self.DialogView.center = CGPointMake(self.view.frame.size.width / 2, self.view.frame.size.height / 2);
self.DialogView.nameLabel.text = loan.fullName;
self.DialogView.noteLabel.text = loan.summaryOfLoan;
self.DialogView.amountLabel.text = [currencyFormatter stringFromNumber:loan.originalAmount];
self.DialogView.alpha = 0.0;
[self.view addSubview:DialogView];
The UILabels all displaying as expected. As is the problem UIButton. I can see it I just can't interact with it!?!
DialogView's interface:
#class MyViewController;
#interface DialogView : UIView {
IBOutlet UILabel *nameLabel, *noteLabel, *amountLabel;
IBOutlet UIImageView *arrowView;
IBOutlet UIButton *okButton;
MyViewController *myVC;
}
#property (nonatomic, retain) UILabel *nameLabel, *noteLabel, *amountLabel;
#property (nonatomic, retain) UIImageView *arrowView;
#property (nonatomic, assign) MyViewController *myVC;
#property (nonatomic, retain) UIButton *okButton;
- (IBAction)okButtonPressed:(id)sender;
#end
And DialogView's implementation:
#import "DialogView.h"
#import "MyViewController.h"
#implementation DialogView
#synthesize nameLabel, noteLabel, amountLabel, arrowView, okButton;
#synthesize myVC;
- (void)dealloc {
[nameLabel release];
[noteLabel release];
[amountLabel release];
[arrowView release];
[okButton release];
[super dealloc];
}
- (id)initWithFrame:(CGRect)frame {
if (self = [super initWithFrame:frame]) {
// Initialization code
}
return self;
}
- (id)initWithCoder:(NSCoder *)decoder {
if (self = [super initWithCoder:decoder]) {
NSLog(#"DialogView initWithCoder called");
}
return self;
}
- (IBAction)okButtonPressed:(id)sender {
NSLog(#"pressed DialogView OK button");
[self.myVC.navigationController popViewControllerAnimated:YES];
}
- (void)drawRect:(CGRect)rect {
// Drawing code
}
#end
I thought that we should use -setTitle:forState: in order to set button's title ?
An other thought, did you check that the button's frame is not CGRectZero ? And by the way, all the frames for the view in the hierarchy ? And check that one superview in the hierarchy is not user interaction disabled ?
And, I think imageView does not respond to touches, do you have one in your code ?
I was just having more or less the same problem and I found that my containing view did not have "User Interaction Enabled".
Hope this helps.
Do you maybe have two buttons on top of one another? Change the IB project window to the detail view and see if your view has more buttons than you are expecting. Maybe you've wired up a button that's not actually getting the press you're expecting.