Prevent save prompt when closing NSWindow - macos

I have a document-based Cocoa app that uses a secondary NSWindow for a preview mode (with shouldCloseDocument set to NO).
If the document is dirty (edited without saving) and I close the secondary NSWindow, a "Do you want to save the changes made to the document" prompt appears.
How can I avoid this prompt on the secondary NSWindow?

I couldn't find a way to do this. I expected to find a NSWindowDelegate or NSWindow method called before the save prompt but none of the obvious candidates (windowWillClose, close, performClose, windowShouldClose) are.
As a workaround, instead of setting NSWindowController.document I'm using a custom property to pass the document. With document set to NIL, the save prompt is not shown anymore.

specifically you can do this inside your viewController subclass:
make sure delegate is set from the window to the owner class:
//-------------------------------------------------------
- (void)windowControllerDidLoadNib:(NSWindowController *)aController
{
NSLog (#"windowControllerDidLoadNib");
aController.document = nil;
[super windowControllerDidLoadNib:aController];
............. etc.......

Related

Document-Based App autosave with storyboards

In Document-Based Apps, using XIB files, when a new window is created its behaviour is:
Be positioned and sized based on the position of the last active
window.
If the last active window is still visible then the new
window should be cascaded so it doesn't directly overlap.
However when using a storyboard this isn't done. See test project.
You can set shouldCascadeWindows on the window controller in your storyboard:
select the window controller in the storyboard
select the identity inspector
add a new User Defined Runtime Attribute with these values:
key path: shouldCascadeWindows
Type: boolean
Value: checked
Update:
If you move the first window, new windows cascade starting in the middle of the screen not under the first window. To fix it:
select the window
in the attributes inspector give it an autosave name
This should also persist window location and size on the next window load and application launch.
Cascaded Windows Problem
One of the problems is that storyboards unlike xibs can include the NSWindowController and Interface Builder doesn't serialize it right.
-initWithWindow:, -initWithWindowNibName: and friends set shouldCascadeWindows to YES.
When a NSWindowController is loaded from a storyboard via -initWithCoder:, shouldCascadeWindows is NO. (OS X 10.11)
Based on my tests, this property needs to be set in the initializer. Setting it in -[NSDocument addWindowController:] didn't work. (OS X 10.11)
- (instancetype)initWithCoder:(NSCoder *)coder
{
self = [super initWithCoder:coder];
if (self)
{
self.shouldCascadeWindows = YES;
}
return self;
}
See rdar://47350352
Window Position Problem
Using -[NSWindowController windowFrameAutosaveName] or -[NSWindow frameAutosaveName] seems only to work sometimes. Randomly it uses the initial window position.
Window Size Problem
Even if the cascaded window position is set right, it never set the size to the one saved for the frame. I verified the saved frame with defaults read window.autosavename.test1. Also before each test I run defaults delete window.autosavename.test1 for a clean state.
Workaround
Use a xib containing a empty NSWindow and add the view controllers from the storyboard in -[NSDocument windowControllerDidLoadNib: or -[NSDocument addWindowController:] to the window.
I think the answer might be that it's just not possible to have multiple windows share the same frameAutosaveName even though it is possible to have multiple NSSplitView share the same autosaveName.
I just tried creating another NSDocument based project, but this time I used xib's instead of a storyboard. The behavior is better (shouldCascadeWindows is on by default). But new window positioning still breaks down when multiple windows are involved.
I think this is more of a runtime constraint then it is storyboard vrs xib problem. Here's a test I just did in the default non storyboard NSDocument project generated by Xcode:
Set window autosave name in interface builder.
Modify windowControllerDidLoadNib to look like this:
- (void)windowControllerDidLoadNib:(NSWindowController *)aController {
[super windowControllerDidLoadNib:aController];
NSLog(#"Listing frameAutosaveName for all windows:");
for (NSWindow *each in [NSApp windows]) {
NSLog(#"%#: %#", each.title, each.frameAutosaveName);
}
}
And then (after creating a number of windows) this is the output that I see:
Listing frameAutosaveName for all windows:
Untitled: SaveMe
Untitled 2:
Untitled 3:
Untitled 4:
Untitled 5:
Window:
So only the first window created gets the "SaveMe" autosave name. For all the following windows the value is never set.
My conclusion is that you just can't use frameAutosaveName to replicate Safari's behavior. Instead you must do something manual. I'm not sure if the method used in the example project is the best way, but I think at least some manual work is needed for Safari's behavior no matter if you are using xibs or storyboards.

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

NSWindow tracking

I would like to track each time a certain window appears (becomes visible to the user) in a OS X app. Where would be the most adequate place to call the tracker?
windowWillLoad, maybe?
I expected to find something like windowWillAppear but it seems I'm thinking too much iOS.
How about getting notification such as NSWindowDidBecomeMainNotification, By main I guess the one which is top most on screen directly visible by user.
see : Apple Documentation
Yes, one would expect that a window would notify its delegate or its controller with a windowWillAppear or windowDidAppear message, or post a documented notification like NSWindowDidAppearNotification. But alas, none of those exist. I filed a bug report with Apple and was given the advice to use a storyboard and a view controller instead. This is unhelpful in legacy apps that already use a bunch of window controllers and xibs.
You could subclass NSWindow and override orderWindow:relativeTo: to send a notification. Most, but not quite all, of the messages that make a window show itself ultimately go through this method, including orderBack:, orderFront:, makeKeyAndOrderFront:, and -[NSWindowController showWindow:]. But orderFrontRegardless does not go through orderWindow:relativeTo:, so you would also want to override that for completeness.
Another way to be notified is to make a subclass of NSViewController that controls some view that's always visible in the window. The view controller will receive viewWillAppear and viewDidAppear.
If you're subclassing NSWindow or NSViewController already for some other reason, either of these is a reasonable solution.
If you're not subclassing NSWindow already, and don't have an NSViewController subclass for a view that's always visible in the window, then another way is to use Cocoa bindings to connect the window's visible binding to a property one of your objects. For example, I have a custom NSWindowController subclass. I gave it a windowIsVisible property:
#interface MyWindowController ()
#property (nonatomic) BOOL windowIsVisible;
#end
and I implemented the accessors like this:
- (BOOL)windowIsVisible { return self.window.visible; }
- (void)setWindowIsVisible:(BOOL)windowIsVisible {
NSLog(#"window %# became %s", self.window, windowIsVisible ? "visible" : "hidden");
}
and in awakeFromNib, I bind the window's visible binding to the property like this:
- (void)awakeFromNib {
[super awakeFromNib];
[self.window bind:NSVisibleBinding toObject:self withKeyPath:NSStringFromSelector(#selector(windowIsVisible)) options:nil];
}
When the window becomes visible, the setWindowIsVisible: setter is called with an argument of YES. Note that if the whole app is hidden and reappears, the setter is called again, even though it wasn't called with argument NO when the app was hidden. So be careful not to assume the window was previously hidden.
Also, the binding might create a retain cycle, so you should probably unbind it when the window is closed, unless you want to keep the window and controller around. Note that the window does post NSWindowWillCloseNotification when it's closing, so you don't need any special magic to detect that.

How to handle NSTextView preferences (spelling and grammar, substitutions,...)

The NSTextView class allows the user to dis-/enable features like "spelling while typing" with the context menu (right click). But when I use a NSTextView in my own app, those preferences are not saved automatically by the text view itself, which means that I have to save them separately - right?
Now I also want to allow the user to change those settings in my app preferences (like in TextEdit). What I do is to save the text view preferences in the user defaults, which means that every time the user changes the setting in the app preferences, I apply those settings and save them. It's pretty easy to accomplish that except the one case where the user changes the text view setting with the context menu and not through the app preferences.
My question now: How can I get notified when the settings of a NSTextView is changed, so I can save them?
I've done a project where I have subclassed NSTextView and I can easily catch any setting changes that have been made by the user.
So, for you to do this, simply create a new .h & .m file and declare it like this:
(in the .h file)
#interface BrutellaTextView : NSTextView
#end
(in the .m file)
#interface BrutellaTextView
- (void)setContinuousSpellCheckingEnabled:(BOOL)flag
{
// here I am just setting user defaults... you may choose
// to have some other place to save the settings
NSUserDefaults * userDefaults = [NSUserDefaults standardUserDefaults];
if(userDefaults)
{
[userDefaults setBool: flag forKey: #"continuousSpellCheckingEnabled"];
}
// and to get the functionality to actually happen,
// call the superclass
[super setContinuousSpellCheckingEnabled: flag];
}
#end
(and you can override other NSTextView methods to capture when other settings change, such as setRulerVisible:).
Now, when you are in your XIB file, make sure to set the CustomClass of your text view to be BrutellaTextView and you'll be all set!
There are no notifications that you can register to get NSTextView settings changes, so as far as I'm concerned, this is the best way to do what you're trying to do.
I hope this answer helps you out!
You could also use NSTextViews method
toggleAutomaticSpellingCorrection:
This method is only called, when the user changes the setting via menu/contextMenu and not if the programmer changes the setting programatically (e.g. temporarily disabling when inserting text).

-shortcutRecorder:keyComboDidChange: isn’t executed

I am writing a Cocoa application and I'd like to implement a global hotkey function.
I implemented the ShortcutRecorder.framework from Waffle Software and I added a customView to my xib. Then I subclassed the CustomView to SRRecorderControl. Now I see the Recorder in my Window, but how can I get the KeyCombo and how can I react on that?
I implemented the keyComboDidChange method with no luck to get the keycode. What am I doing wrong?
Here is my code for getting the keycode:
- (void)shortcutRecorder:(SRRecorderControl *)aRecorder keyComboDidChange:(KeyCombo)newKeyCombo
{
if (aRecorder == shortcutRecorder)
{
NSLog{"We got a new Key Combo");
}
}
shortcutrecorder is my IBOutlet btw.
Do I have to implement a protocol or setDelegate:self or something like that?
Edited to add
Actually I have declared my shortcutRecorder outlet in my Preferences.h. Then in the Identity Inspector I put "Preferences" as Custom Class for Files Owner in and I connect the delegate to my Shortcut Recorder... but the keyComboDidChange is never called ... I don't understand why.
Let me explain the steps I took to get it working:
Create a window xib
The file's owner of this class is, in my case, PreferencesWindowController
Create a referencing outlet from the window to the file's owner, by right clicking the window and dragging it to the file's owner
Add the custom view to your window
You have to connect the delegate of the ShortcutRecorder to the "File's owner". To do this, right-click the SRRecorderControl and drag the delegate to the "File's owner" on your left.
After this: the ShortcutRecorder only records the hotkey and leaves it to you what to do with it. You need to use the PTHotKeyCenter (which is shipped with ShortcutRecorder) or you could implement the shortcut handling yourself.
The ShortcutRecorder contains a great demo which demonstrates the use of the ShortcutRecorder in combination with the PTHotKeyCenter. It works like this:
Listen to events from the ShortcutRecorder (which you already do, but without setting the delegate)
Check if the globalHotKey variable is set
If so, unload the previous hotkey
Init a new hotkey with the settings from the ShortcutRecorder
Set the target and action to actually capture the hotkey, once pressed
Save the hotkey to the shared center (from this moment on, the hotkey will work)
Little sample, from their source:
if (globalHotKey != nil)
{
[[PTHotKeyCenter sharedCenter] unregisterHotKey: globalHotKey];
[globalHotKey release];
globalHotKey = nil;
}
globalHotKey = [[PTHotKey alloc] initWithIdentifier:#"SRTest"
keyCombo:[PTKeyCombo keyComboWithKeyCode:[shortcutRecorder keyCombo].code
modifiers:[shortcutRecorder cocoaToCarbonFlags: [shortcutRecorder keyCombo].flags]]];
[globalHotKey setTarget: self];
[globalHotKey setAction: #selector(hitHotKey:)];
[[PTHotKeyCenter sharedCenter] registerHotKey: globalHotKey];
The only thing left to do is the hotkey handler:
- (void)hitHotKey:(PTHotKey *)hotKey
{
NSLog(#"Hotkey pressed!");
}
You could easily save the hotkey settings to the UserDefaults to load them every time you application starts.
Actually I have declared my shortcutRecorder outlet in my Preferences.h. Then in the Identity Inspector I put "Preferences" as Custom Class for Files Owner in and I connect the delegate to my Shortcut Recorder... but the keyComboDidChange is never called ... I don't understand why -.-

Resources