NSAlert: Don’t show this again
How to let the user choose to not see an alert
Do you really want to do this? Are you sure? Are you really sure?
Annoying.
So, how do you let the user stop this crazy thing?
(And also, how do you prevent a window from being closed?)
((And) (also “Also how do you ask if they really want to quit the program?”))
(hey “I like Lots of Irritating Stupid Parentheses”)
Introduction
Let’s say you don’t want the user to close the application window by clicking the red dot in the title bar. You’re a control freak, right? I said use the Quit menu item!
Give the window a delegate, and implement windowShouldClose to return false. Easy.
In this example, I created a NSWindowController (and set it on the window controller in the storyboard) as a window delegate – and did other window controller frobs that I’m not showing here.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
import Cocoa class MainWindowController: NSWindowController { override func windowDidLoad() { super.windowDidLoad() window?.delegate = self } } extension MainWindowController: NSWindowDelegate { func windowShouldClose(_ sender: NSWindow) -> Bool { print("not closing the window!") return false } } |
So the user keeps mashing the red button. “Why isn’t this #$(*^#ing window closing?!?”
Show them an alert to tell them you’re not playing their game.
Now, every time they hit the red button they get the alert. You may want that just to be annoying. If you’re from Brooklyn, that’s a definite yes, amirite?
Well, the NSAlert class has this gem:
1 2 |
let alert = NSAlert() alert.showsSuppressionButton = true |
You set that property, and a check box with a default message will appear in the alert.
What if the user checks it? How do you handle this?
After runModal() returns, get the suppressionButton. It’s a control, so it has a state property. Simply inspect that value and save it for the next time. UserDefaults is a good place. Then, the next time the alert is about to be shown, check UserDefaults to get the user’s preference.
1 2 3 4 5 6 7 8 |
if let supress = alert.suppressionButton { let state = supress.state switch state { case NSControl.StateValue.on: UserDefaults.standard.set(true, forKey: "supress") default: break } } |
Easy once you see it.
The whole thing:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
func windowShouldClose(_ sender: NSWindow) -> Bool { print("not closing the window!") if UserDefaults.standard.bool(forKey: "supress") { return false } let alert = NSAlert() alert.messageText = NSLocalizedString("Hey, Yo!", comment: "exit alert title") alert.informativeText = NSLocalizedString("You ain't doin' that", comment: "ask the user if they want to exit") alert.alertStyle = .critical alert.showsSuppressionButton = true alert.addButton(withTitle: NSLocalizedString("Allright already", comment: "OK button title")) alert.runModal() if let supress = alert.suppressionButton { let state = supress.state switch state { case NSControl.StateValue.on: UserDefaults.standard.set(true, forKey: "supress") default: break } } return false } } |
Bonus! Do you really want to quit?
The user chooses Quit from the file menu or types ⌘-Q.
Some developers are courteous enough to ask if they really want to quit.
And then there are users who don’t want to be asked.
You know what to do now with the alert!
But, you will find a lot of wrong answers on StackOverblown for how to catch this event.
Look at the quit button in IB. It’s connected to terminate: on the responder chain. NSApplication has this method, so you probably don’t want to write your own. Take a look at the documentation (link below). When its terminate: is called it does a lot before terminating. One thing it does is call the application delegate’s applicationShouldTerminate: method.
So that’s what I’m doing here.
Also, as a bonus bonus, I’m changing the suppression message just to show how.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
func applicationShouldTerminate(_ sender: NSApplication) -> NSApplication.TerminateReply { let userDefaultsKey = "dontAskToQuit" if UserDefaults.standard.bool(forKey: userDefaultsKey) == false { let alert = NSAlert() alert.messageText = NSLocalizedString("Yo!", comment: "exit alert title") alert.informativeText = NSLocalizedString("You really want to exit this bundle of joy?", comment: "ask the user if they want to exit") alert.alertStyle = .critical alert.showsSuppressionButton = true alert.suppressionButton?.title = "Don't bother me again with this nonsense!" alert.addButton(withTitle: NSLocalizedString("Get me outta here", comment: "OK button title")) alert.addButton(withTitle: NSLocalizedString("No, I'm stayin'", comment: "Cancel button title")) let response = alert.runModal() if let supress = alert.suppressionButton { let state = supress.state switch state { case NSControl.StateValue.on: UserDefaults.standard.set(true, forKey: userDefaultsKey) default: break } } if response == .alertFirstButtonReturn { return .terminateNow } else { return .terminateCancel } } return .terminateNow } |
Summary
You smart now.
Ask for a raise.