Getting MAAttachedWindow (Really a view) to close when clicked outside of - cocoa

I have a MAAttachedWindow that shows itself when a status bar item is clicked. I need to have it close when its clicked outside of. I found some other directions that say to set it as a delegate and use - (void)windowDidResignKey:(NSNotification *)notification to detect when the user exits the window. I've tried it many times but can't seem to get it working which is probably because I didn't correctly set the delegate. Whats the best way to set the delegate so it will respond to the notification? The code is available here.
Thanks in advance

I found out to get the notification you have to register for it.
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(windowDidResignKey:)
name:NSWindowDidResignKeyNotification
object:self];

Related

Check if there are nspopover opened before creating new one

I have in my code some functionality to open a popover anytime an event happens. Problem is that if those events happen one after the other the popovers opened are overlapped.
I would like to close one popover when opening new one.
Is there any way to get from nswindow if there is an active popover?
Thanks in advance and regards
I think you need play with NSPopover's notification methods such as:
- (void)popoverDidShow:(NSNotification *)notification;
- (void)popoverWillClose:(NSNotification *)notification;
And add some logic which will hide not closed popover...
Finally I was able to figure it out by using notifications posted:
[[NSNotificationCenter defaultCenter]
addObserver:self
selector:#selector(popoverWillShow:)
name:NSPopoverWillShowNotification
object:nil];
And then in the selector I compare the objects: (popover is an NSPopover)
- (void)popoverWillShow:(NSNotification *)notification {
if (![popover isEqual:[notification object]])
[self close];
}

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;
}

OSX: Prevent an window from closing when user hits cmd+w key

In my MAC app, in one use case, I prompt an window to the user and give him 2 options (say buttons Save and Cancel). I want to force the user to select either of the 2 buttons to close the window.
But currently I find that if the user hits "Command + w" key when window has the focus, the window gets closed. In the .xib resource file, I uncheck the "close" option but that only disables the close option in the window UI.
How do I make sure that my window ignores the "Command+w" key and stays as is without closing.
Have also tried removing the notification by adding below code in awakeFromNib method but did not help.
[[NSNotificationCenter defaultCenter] removeObserver:NSWindowWillCloseNotification ];
Have also tried to implement "windowShouldClose" delegate method and return NO, but this method is never called. The documentation too says that this method is not reliable.
You should use an NSAlert for this sort of prompt, probably run as a sheet on the window. That would avoid the problem of closing it.
In any case, the window's delegate can implement -windowShouldClose: to control if a window is allowed to close. You can make an object (often the window controller) be its delegate by declaring that it adopts the NSWindowDelegate protocol and connecting the window's delegate outlet to that object.
I recently had to solve a similar problem. I'm not sure that this is the 'right' way to do it. But it worked for my purposes, and might work for you.
By default, I think, the 'Close Window' (CMD+W) menu item is bound to the action 'performClose' on first-responder. If you remove this binding and instead bind to a custom IBAction on your application delegate or main window controller, it allows you to conditionally call the close method of the current key-window if it is not matching the instance that you want to keep alive.
#property (strong, nonatomic) MyWindowController *unstoppable;
-(IBAction)killActiveWindow:(id)sender
{
NSWindow *keyWindow = [[NSApplication sharedApplication]keyWindow];
if ([keyWindow isNotEqualTo: unstoppable.window]){
NSLog(#" CMD+W Closing Window %#",keyWindow.title);
[keyWindow close];
}
}

Cocoa ScriptingBridge Input Polling

I am trying to use ScriptingBridge to write a small iTunes controller. The problem is to find an efficient way of getting notifyed whenever any changes occur. My first approch was to poll the input in a loop and just keep checking for differences. But I think there must be a more efficient way of getting notifyed about input!
Thanks in advance!
iTunes sends out a notification when something changes so just register for it in your init method of AppDelegate. Here's an example...
[[NSDistributedNotificationCenter defaultCenter] addObserver:self selector:#selector(receivediTunesNotification:) name:#"com.apple.iTunes.playerInfo" object:nil];
The actual notifcation object in your method "receivediTunesNotification:" will contain information about the changes.

NSNotificationCenter Add?

I have two view that when you switch from one to another, they call a notification to the view that's about to get loaded to refresh the content. The weird thing is that the first time the view loads, it will call it once, the next time twice, and so on. I concluded that it's because they keep getting added every time the view loads. Since the dealloc never get's called it's still there and it will keep adding now.
So is there a way to check if the notification exists before getting added to fix this issue?
Here's what I have in my viewDidLoad
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(ReloadGridNotification:) name:#"ReloadOHGridView" object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(ReloadBadgeNotification:) name:#"reloadBadge" object:nil];
And my dealloc:
[[NSNotificationCenter defaultCenter] removeObserver:self];
Thanks!Coulton
EDIT 1:
I show my views in a UINavigationController and switch between them. Here's my code to refresh the different view:
- (void)viewWillDisappear:(BOOL)animated {
[[NSNotificationCenter defaultCenter] postNotificationName:#"reloadBadge" object:self];
}
Every time you call addObserver, the notification center will add a entry to its internal structures. This means the notification center will call your observer once more every time you call viewDidLoad.
If your view is unloaded for any reason and then reloaded, then viewDidLoad will get called again. Your removeobserver will not get called until the object is destroyed, which may explain why your removeobserver did not work.
You should either check whether you have already called addObserver with a flag, or manually remove the observer with removeObserver when you unload your view in the viewDidUnload method.
Edit1: Alternatively, can you add the observers somewhere else, like in the App Delegate?
When memory gets tight the OS will dump your view, but will first call viewDidUnload; when it has to reload them it calls viewDidLoad. dealloc, however, is only called when all references to your view have been released, which likely doesn't happen until your app quits.
As a result, as #futureelite7 noted, you're adding a new observer every time your view is reloaded but, effectively, are never removing it.
All you need to do is ensure that the observer is added in viewDidLoad and removed in viewDidUnload you won't have the multiple notification problem. No need for a flag or for putting the observer anywhere else.
From your comments it sounds like you might have tried it, but I suggest going through your code and making absolutely certain you're only adding them in DidLoad and are always removing them in DidUnload. It works like a charm in app after app.
Edited to Add
Because your view keeps getting unloaded, which only happens if you do it manually, if all references to it are lost, or if memory gets tight, I suggest looking at all three to help ensure you're doing what you can to keep your view around.

Resources