Do you need help on a specific subject? Use the contact form (Request a blog entry) on the right hand side.

2015-03-05

Code sample: Swift & NSAlert

This sample shows the usage of NSAlert in Swift 3 (Xcode 8 beta 6).

As of OS X 10 Apple has depreciated the use of:

        [alert beginSheetModalForWindow:...
                          modalDelegate:...
                         didEndSelector:...
                            contextInfo:...];

instead we have to use the following:

            alert.beginSheetModal(for:completionHandler:)

Which is a shame as I used to use the first. However in Swift it makes sense to use the later.

The code sample:

let alert = NSAlert()
alert.messageText = "Warning"
alert.addButton(withTitle: "Yes")
alert.addButton(withTitle: "No")
alert.informativeText = "You have made changes, are you sure you want to discard them?"
            
alert.beginSheetModal(for: self, completionHandler: { [unowned self] (returnCode) -> Void in
   if returnCode == NSAlertFirstButtonReturn {
      self.dataModel.removeAll()
   }
})

The buttons are created from right to left. Thus the first "addButton" is the rightmost button, the second the one to the left of the first etc.

Identification of the buttons is though NSAlertFirstButtonReturnNSAlertSecondButtonReturn and NSAlertThirdButtonReturn. Any buttons after that are identified as NSAlertThirdButtonReturn + N, where N must be an integer starting at 1.

There is some intelligence under the hood, because the first button (i.e. rightmost) has the return key associated with it, and if any button is created with the title "Cancel" then that has the key "Escape" associated with it. Just as a button with the title "Don't Save" has the Command-D associated with it.

The above code sample show a common usage where a user has to acknowledge that he wants to delete his data without saving the changes first. The delete action is placed in the handler for the alert and is conditional on the user clicking the leftmost button. (Or pressing the return key)

Another common use is the case where there is no action associated with the alert. I.e. the user has to acknowledge the alert, but there is no associated action. In that case we want to put in an empty handler code instead, like this:

let alert = NSAlert()
alert.messageText = "Warning"
alert.addButton(withTitle: "OK")
alert.informativeText = "There are problems, please resolve these first"

alert.beginSheetModal(for: self, completionHandler: nil )

The following may not be the best design principle, but I find it extremely useful for quick and dirty development work:

func showErrorInKeyWindow(message: String) {
    
    if let window = NSApp.keyWindow {
        
        let alert = NSAlert()
        alert.messageText = "Error"
        alert.informativeText = message
        alert.addButton(withTitle: "Dismiss")
        alert.beginSheetModal(for: window, completionHandler: nil)
        
    }
    else {
        log.atLevelError(id: -1, source: #file.source(#function, #line), message: "Could not send error message '\(message)' to key window")
    }

}

I put this function in the AppDelegate.swift file at the top level. That way I can always call this function -where ever I am in my code- and be sure that the error message shows up in the currently active window. To be sure, I also capture the error if there is no key-window so the information is not lost. For that I use my own logger framework (SwifterLog which you can get for free from github).

Happy coding

Did this help?, then please help out a small independent.
If you decide that you want to make a small donation, you can do so by clicking this
link: a cup of coffee ($2) or use the popup on the right hand side for different amounts.
Payments will be processed by PayPal, receiver will be sales at balancingrock dot nl
Bitcoins will be gladly accepted at: 1GacSREBxPy1yskLMc9de2nofNv2SNdwqH

We don't get the world we wish for... we get the world we pay for.

No comments:

Post a Comment