Can we have popovers in Mac Cocoa apps? - cocoa

Popovers are heavily used in iPad apps and I really like them. Now I think about how this could be implemented in AppKit on the mac because I have a use case for it.
Do I need a NSWindow subclass to accomplish the overlay or could I also use a normal view?

According to Apple's Developer Documentation, you can use built in popovers on OS X with the built-in NSPopover class:
Starting in OS X v10.7, AppKit provides support for popovers by way of the NSPopover class. A popover provides a means to display additional content related to existing content on the screen. The view containing the existing content—from which the popover arises—is referred to in this context as a positioning view. You use an anchor to express the relation between a popover and its positioning view.
Here's a link to the NSPopover class. You can also see an example of NSPopovers used in the Calendar (10.7+) app, and the Safari app (10.8+). The image below depicts a popover in the Calendar app (left) and Safari (right):
Here's how to setup an NSPopover, it is very simple and can be done mostly in interface builder.
Add an NSPopover Item to your XIB in interface builder. This will create the NSPopover and its view controller.
Next, drag a custom NSView into your XIB through interface builder. This will be the view for the popover view controller
Customize your NSView with any controls you need in your popover
In your header file (.h) add the following two lines of code:
#property (assign) IBOutlet NSPopover *popover;
- (IBAction)showPopover:(id)sender;
Don't forget to connect both the outlet and the action to your interface.
In your implementation, synthesize the popover and add the method for showPopover
In the showPopover method, add this line to show the popover:
[[self popover] showRelativeToRect:[sender bounds] ofView:sender preferredEdge:NSMaxYEdge];
It's up to you to figure out how to dismiss the popover; because what fun it copy / pasting? You can either do it manually (hint: try using close) or change the behavior property and have the system do it (see the edit below).
Good luck, hope this helps!
Edit
As noted by David in his comment:
Another possibility for dismissing the popover is to set its behavior to Transient. This allows the user to click anywhere outside the popover to have it disappear
The behavior property of a popover sets how it appears and disappears. There are three behaviors:
NSPopoverBehaviorApplicationDefined - (Default) Your app must close the popover itself
NSPopoverBehaviorTransient - The popover is closed when any interface element is interacted with outside the popover
NSPopoverBehaviorSemitransient - The popover is closed when any interface element in the popover's presenting view is interacted with outside the popover.
Read more about this in Apple's Documentation.

If I understood you correctly, you want something like MAAttachedWindow (by Matt Gemmell), which is open source.

Alternatively, you can take a look at Popover example in the documentation. https://developer.apple.com/library/mac/samplecode/Popover/Introduction/Intro.html

Related

NSToolbar in Xcode 7 using Storyboards (NSWindowController -> NSSplitViewController)

Hi I've seen this question asked a few times already but with no definite answer yet so I created it for xcode 7 and swift2 (which may have changed things a bit anyway).
I created a project using Xcode 7 and Cocoa OSX Story boards + swift2, so my project started with a NSWindowController that Connects to a NSViewController (as expected!). I added a NSToolbar to my window controller and added a NSButton to the toolbar. I changed my NSViewController to be one of the new NSSplitViewController that links to three NSViewControllers and displays their views horizontally - with vertical dividers - (similar to the layout you see in the photo app or pages in Yosemite +). My final goal will be that the button in My toolbar shows and hides the first split.
It is my understanding is, and I would expect that to achieve this I should create an action in the NSSplitViewController that changes the auto layout constrains more or less in the way they are working it out here: How to do collapse and expand view in mac application?.
And then somehow link this action to the NSButton that is in the Toolbar... which happens to be in the NSWindowController (far up and isolated in the hierarchy)...
I have already gone through other questions about NSToolbar and storyboards and failed to accomplish my goal:
The YouTube video: Cocoa Programming L17 - NSToolbar which is the closest I found to solve the problem, but his method does not work for storyboards, only creating your own xib file.
In this question: How to use NSToolBar in Xcode 6 and Storyboard? One person proposes to make the link using the first reponder and expecting everything to hook up at run-time (which looks a bit dodgy and not the way apple would implement it I think...). A second person suggested to create a view controller variable in the NSWindowController and manipulate its properties from there... but again, a bit dodgy too.
One latest comment I saw in that question which seems the best way to tackle the problem (but still not as good as I guess it could be) is to add a NSObjectController to the dock of each scene and when the scene loads, set the values of the objects to the other secene's controller. Is this really the best way to go ahead? If so, how could I achieve this one?
Apple did mention (again) in WWDC15 that they created storyboards for osx and the split-view controller that owns view-controllers so that you can move your logic and work to the specific view-controller, so I would be expecting to do everything from inside my split-view controller as this is the target that needs to change.
Does anyone know how to achieve this from the view controller itself? I really haven't been able to find a way to connect my ToolBarItem to it.
OK, I've created this question quite a few days ago and no answer so far so I've answer with what I recently did to overcome the problem.
After I created my Xcode project I did this:
Created a subclass MySplitViewController for the NSSplitViewController
Added an IBOutlet for each NSSplitViewItem. For example:
#IBOutlet weak var mySplitViewItem: NSSplitViewItem!
Created a subclass WindowController for the NSWindowController
Added an IBAction in the WindowController class that links to the NSToolbarItem (my button)
Added a property that gets the Window Controller's content as MySplitViewController
var mySplitViewController: MySplitViewController {
return self.window?.contentViewController as! MySplitViewController
}
Now I can access the split view controller's property from the Window Controller in the action I created:
mySplitViewController. mySplitViewItem.collapsed = true
I created some sample code that does this (but using a view controller and changing the text for a label here, just in case someone wants to see a working project with this behaviour. And a blog post about it too :)
One person proposes to make the link using the first reponder and expecting everything to hook up at run-time (which looks a bit dodgy and not the way apple would implement it I think...).
I think this first responder method is actually the proper way.
As an example:
Add something similar to the following, in whichever view controller makes sense.
#IBAction func doSomething(_ sender: AnyObject?) {
print("Do something.")
}
This will magically show up in the first responder:
In your storyboard, right-click the orange "first responder" icon above your window controller, and you should see doSomething in the very long list. You just need to connect that up to your toolbar button.
In the following screen capture, you can see my "Toggle Sidebar" button is connected to the toggleSidebar action in my first responder.
I didn't even have to write this method — it's provided by NSSplitViewController:
#IBAction open func toggleSidebar(_ sender: Any?)
So, I was working this same issue and finding no solution as you experienced. I read your post and was trying to figure how I would implement your solution when it occurred to me to use a notification. In about 30 seconds, I had a perfectly fine working solution:
In your windowController add an IBAction to post a notification like so
-(IBAction)toggleMasterViewClicked:(id)sender
{
[[NSNotificationCenter defaultCenter] postNotificationName:#"TestNotification" object:nil];
}
Hook up that action to your NSToolbarItem, then in the viewController add self as an observer for that notification like so
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(toggleMasterView:) name:#"TestNotification" object:nil];
In your case, selector would be updateMyLabelText
I don't really see any downside here. No reference to other objects needed, no dependancies. Works flawlessly for me
While connectiong IBActions works by using either the First Responder or by adding an "Object" to the scene, then changing its class to the window's view controller class, this doesn't help with IBOutlets and delegates that you'd like to point to the view controller.
Here's a work-around for that:
Add the Toolbar to the View Controller, not to its Window. That way, you can make all the IBOutlet connections in the View Controller Scene easily. I've done that for years and found no issues with it, even when using Tabs.
You'll have to assign the window's toolbar in code, then. E.g. like this:
#interface ViewController ()
#property (weak) IBOutlet NSToolbar *toolbar; // connect this in your storyboard to the Toolbar that you moved to the View Controller Scene
#end
- (void)viewWillAppear {
[super viewWillAppear];
self.view.window.toolbar = self.toolbar;
}

Change appearance of a popover created using a popover segue in OS X

I'm trying to use storyboards to build a simple app for Yosemite. Creating a popover segue is easy - the segue is created from a button click to an NSViewController in my storyboard, and the Style is set to Popover. This works great, but the trouble is, I'd like to change the appearance of this popover. It seems to be defaulting to a Vibrant Dark appearance, but I'd like it to be Vibrant Light or Aqua. I assume what is happening here is that behind the scenes, an NSPopover is being created to contain the view controller that I am displaying with my segue, but I can't figure out how to get access to this NSPopover object - the storyboard only gives me access to the view controller that I am displaying, and there isn't any NSPopover object available to use in interface builder.
All I want to do is change the appearance of this popover I'm creating in my storyboard...Any suggestions? Thanks!
Override viewWillAppear in your view controller and update the appearance of the view's window:
override func viewWillAppear() {
self.view.window?.appearance = NSAppearance(named: NSAppearanceNameVibrantDark)
}
your appearance can be any of the standard appearances

How to get notified when NSWindow opens?

How to take notice when an NSWindow is about to be opened or have just opened? That is, the opposite of windowWillClose: delegate method (likewise the opposite of NSWindowWillCloseNotification.)
This is related to this question, but from the other direction.
The background is, I'm looking to couple a window with a tickmark on the main menu (among other things). When the window is shown, the corresponding ̨ menu item should be checked and vice-versa.
It should not normally be a mystery when or how a window is made visible. It should only happen in response to something that your own code is doing. If the window is in a NIB and is marked Visible At Launch, then it shows when your code loads that NIB. Otherwise, it should only show if you call one of the -order... methods other than -orderOut: (e.g. -orderFront:) or -makeKeyAndOrderFront:. If the window is controlled by a window controller, then it would show if you invoke -[NSWindowController showWindow:]. Etc.
If you really feel the need to be notified, you can subclass NSWindow and override -orderWindow:relativeTo: and, if orderingMode is not NSWindowOut and the window was not already visible, post a notification of your own.
By macOS 10.10, this is somewhat solved by the call to NSViewController's viewWillAppear or viewDidAppear. Have an NSViewController subclass and set it as the contentViewController of the window. Then its viewWillAppear/ viewDidAppear implementation can post a notification that the window will (or did) open.
You can bind your NSMenuItem value to the NSWindows visible binding Zero lines of code if you do it in IB.
visible:
A Boolean value that specifies if the NSWindow is visible.
If visible evaluates to YES, the NSWindow is visible.
Availability:
Available in OS X v10.3 and later.
See the NSWindow Binding Documentation for more info.
You can either bind the NSMenuItem value binding to an existing NSWindow property on one of your existing classes, or add an NSObjectController to your nib and set its content to the NSWindow instance then bind to the controller.
Tested and confirmed on Mac OS 10.9. Works for window minimization and restoration too.

How to show different views in same window in cocoa?

Is it possible to do navigation within the same window in a mac application ?(Like it is possible in ios apps).I want to show each view in the same window instead of opening different windows on a button click.
e.g When a user clicks a button then the next page should be loaded in the same window.(The next page will have nothing in common with the current page.)
You may use Tab View for easy switching between views on a same window.
UPDATE:
You may also customize your tab view , make it tabless (In the attributes inspector set style to tabless) and use your buttons to switch between views.
You may take help from the following link : http://devcry.heiho.net/2012/01/nstabview-tutorial.html
OR
You may add or remove subviews from your window on button clicks, using
[[yourWindow contentView] addSubview: yourSubview]; // Add subview to window
[yourSubview removeFromSuperview]; //Remove subview
UPDATE:
Steps to swap between views using a tabless tab view.
Drag a NSTabView to your xib.
Set the no. of tabs in attribute inspector to no. of views you want.
Design each view of the tab as per your requirement.
Now in the attribute inspector of tabview, set style to tabless.
Now drag the buttons you want to use for swapping between views. Suppose Button0 and Button1 are for 1st and 2nd view of your tab view.
Create a IBOutlet for your NSTabView in your .h file. Bind it to the referencing outlet of you tabview.
IBOutLet NSTabView* tabview;
Set a IBAction for both your buttons in your .h class file.
In the button action method for button1, use
- (IBAction)button1clicked:(id)sender
{
[tab selectTabViewItemAtIndex:0];
}
Similarly in button2 action method use:
[tab selectTabViewItemAtIndex:1];
In this way you can have any no. of views and you may select any view on button click using
[tab selectTabViewItemAtIndex:(index of the view you want to load)];
In general you want to google for view swapping.
There are tons of examples out there. Some from Apple and lots elsewhere.
Much of it is very similar to iOS.
You need to read the docs a bit too.
Understand NSView and how to load views from nibs, how to create view objects in code, how to add a subview and how to remove a view.
There are many approaches to having different views for different reasons. The right approach is a combination of style, experience and what your app actually needs to do.
Cocoa includes NSBox, NSTabView, and lots of others. Those two can be configured to not display any visual indication that they are containers.
You will also need to understand at least a little about NSWindow to understand its content view (the root container of other views generally)

Adding a custom view to toolbar

I'm struggling with Cocoa for 2 hours now without success. I want to add a custom view to the toolbar. So, I added a NSToolbar to the window (with IB), and added my view (which works perfectly). IB automatically created a NSToolbarItem.
I followed the instructions from Apple here: http://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/Toolbars/Tasks/AddRemoveToolbarItems.html#//apple_ref/doc/uid/20000755-BBCGJCDJ
The problem is that I don't know what to do now, the view doesn't show although it's label is displayed in the window.
Here's the code I use to draw (very simple, it's for testing purpose)
- (void)drawRect:(NSRect)dirtyRect {
[[NSColor blackColor] set];
[[NSBezierPath bezierPathWithRect:self.bounds] fill];
}
Can someone help me?
Thanks in advance.
I solved the problem.
I put my custom view in the root of the nib. I added a classic NSToolbarItem and created two outlets: one for the custom view and one for the NSToolbarItem. On -(void)awakeFromNib, I called setView: on the NSToolbarItem with the custom view.
According to some ressources on the internet, it is a bug with Interface Builder.
According to an Apple engineer in the discussion at http://www.mail-archive.com/cocoa-dev#lists.apple.com/msg35673.html, there is a bug in Interface Builder whereby "Custom Views" (NSViews created in IB) are not decoded properly when used as the view for an NSToolbarItem, and so do not appear in the toolbar. Other kinds of NSViews, such as NSButtons and NSBoxes, will work just fine as toolbar items: you can create these in Interface Builder and then drag them into the toolbar to make them into toolbar items.
(The discussion in the link above implies that the bug is down to how "Custom Views" are created from the XIB at runtime: using initWithFrame: instead of initWithCoder:. The discussion dates from 2009 but this still hasn't been fixed as of XCode 4.5/OS X 10.8.)
In my case I was using a regular NSView to wrap a set of controls (a volume slider and min/max buttons), rather than implementing a custom NSView subclass. I was able to avoid the problem by using an NSBox as the container instead of an NSView: I made the NSBox transparent, title-less and borderless, so it otherwise acted exactly like a plain NSView wrapper. This was a little more work in IB, but saved me the trouble of wiring up the view to the toolbar item programmatically.

Resources