UIAlertController
Did you know that UIAlert
and UIAction
(as well as their respective delegate protocols) are deprecated in iOS 8?
It’s true. ⌘-click on UIAlert
or UIAction
in your code, and check out the top-level comment:
UIAlert
is deprecated. UseView UIAlert
with aController preferred
ofStyle UIAlert
instead.Controller Style Alert
Wondering why Xcode didn’t alert you to this change? Just read down to the next line:
@availability(i OS, introduced=2.0)
Although these classes are technically deprecated, this is not communicated in the @availability
attribute. This should be of little surprise, though; UIAlert
has always played it fast and loose.
From its very inception, UIAlert
has been laden with vulgar concessions, sacrificing formality and correctness for the whims of an eager developer audience. Its delegate
protocol conformance was commented out of its initializer (delegate:(id /* <UIAlert
). And what protocol methods that did exist triggered when a button
“clicked” rather than “tapped”. This, and trailing variable-length arguments for other
, awkward management of button indexes, a -show
method with no regard for the view hierarchy… the list goes on.
UIAction
was nearly as bad, though developers can’t be bothered to remember what the heck that control is called half the time, much less complain about its awkward parts.
As such, the introduction of UIAlert
should be met like an army liberating a city from occupation. Not only does it improve on the miserable APIs of its predecessors, but it carves a path forward to deal with the UIKit interface singularity brought on by the latest class of devices.
This week’s article takes a look at UIAlert
, showing first how to port existing alert behavior, and then how this behavior can be extended.
UIAlert
replaces both UIAlert
and UIAction
, thereby unifying the concept of alerts across the system, whether presented modally or in a popover.
Unlike the classes it replaces, UIAlert
is a subclass of UIView
. As such, alerts now benefit from the configurable functionality provided with view controller presentation.
UIAlert
is initialized with a title
, message
, and whether it prefers to be displayed as an alert or action sheet. Alert views are presented modally in the center of their presenting view controllers, whereas action sheets are anchored to the bottom. Alerts can have both buttons and text fields, while action sheets only support buttons.
Rather than specifying all of an alert’s buttons in an initializer, instances of a new class, UIAlert
, are added after the fact. Refactoring the API in this way allows for greater control over the number, type, and order of buttons. It also does away with the delegate pattern favored by UIAlert
& UIAction
in favor of much more convenient completion handlers.
Comparing the Old and New Ways to Alerts
A Standard Alert
The Old Way: UIAlertView
let alert View = UIAlert View(title: "Default Style", message: "A standard alert.", delegate: self, cancel Button Title: "Cancel", other Button Titles: "OK")
alert View.alert View Style = .Default
alert View.show()
// MARK: UIAlert View Delegate
func alert View(alert View: UIAlert View, clicked Button At Index button Index: Int) {
switch button Index {
…
}
}
The New Way: UIAlertController
let alert Controller = UIAlert Controller(title: "Default Style", message: "A standard alert.", preferred Style: .Alert)
let cancel Action = UIAlert Action(title: "Cancel", style: .Cancel) { (action) in
…
}
alert Controller.add Action(cancel Action)
let OKAction = UIAlert Action(title: "OK", style: .Default) { (action) in
…
}
alert Controller.add Action(OKAction)
self.present View Controller(alert Controller, animated: true) {
…
}
A Standard Action Sheet
UIActionSheet
let action Sheet = UIAction Sheet(title: "Takes the appearance of the bottom bar if specified; otherwise, same as UIAction Sheet Style Default.", delegate: self, cancel Button Title: "Cancel", destructive Button Title: "Destroy", other Button Titles: "OK")
action Sheet.action Sheet Style = .Default
action Sheet.show In View(self.view)
// MARK: UIAction Sheet Delegate
func action Sheet(action Sheet: UIAction Sheet, clicked Button At Index button Index: Int) {
switch button Index {
...
}
}
UIAlertController
let alert Controller = UIAlert Controller(title: nil, message: "Takes the appearance of the bottom bar if specified; otherwise, same as UIAction Sheet Style Default.", preferred Style: .Action Sheet)
let cancel Action = UIAlert Action(title: "Cancel", style: .Cancel) { (action) in
…
}
alert Controller.add Action(cancel Action)
let OKAction = UIAlert Action(title: "OK", style: .Default) { (action) in
…
}
alert Controller.add Action(OKAction)
let destroy Action = UIAlert Action(title: "Destroy", style: .Destructive) { (action) in
println(action)
}
alert Controller.add Action(destroy Action)
self.present View Controller(alert Controller, animated: true) {
…
}
New Functionality
UIAlert
is not just a cleanup of pre-existing APIs, it’s a generalization of them. Previously, one was constrained to whatever presets were provided (swizzling in additional functionality at their own risk). With UIAlert
, it’s possible to do a lot more out-of-the-box:
Alert with Destructive Button
The type of an action is specified by UIAlert
, which has three values:
.Default
: Apply the default style to the action’s button..Cancel
: Apply a style that indicates the action cancels the operation and leaves things unchanged..Destructive
: Apply a style that indicates the action might change or delete data.
So, to add a destructive action to a modal alert, just add a UIAlert
with style .Destructive
:
let alert Controller = UIAlert Controller(title: "Title", message: "Message", preferred Style: .Alert)
let cancel Action = UIAlert Action(title: "Cancel", style: .Cancel) { (action) in
println(action)
}
alert Controller.add Action(cancel Action)
let destroy Action = UIAlert Action(title: "Destroy", style: .Destructive) { (action) in
println(action)
}
alert Controller.add Action(destroy Action)
self.present View Controller(alert Controller, animated: true) {
…
}
Alert with >2 Buttons
With one or two actions, buttons in an alert are stacked horizontally. Any more than that, though, and it takes on a display characteristic closer to an action sheet:
let one Action = UIAlert Action(title: "One", style: .Default) { (_) in }
let two Action = UIAlert Action(title: "Two", style: .Default) { (_) in }
let three Action = UIAlert Action(title: "Three", style: .Default) { (_) in }
let cancel Action = UIAlert Action(title: "Cancel", style: .Cancel) { (_) in }
alert Controller.add Action(one Action)
alert Controller.add Action(two Action)
alert Controller.add Action(three Action)
alert Controller.add Action(cancel Action)
Creating a Login Form
iOS 5 added the alert
property to UIAlert
, which exposed much sought-after private APIs that allowed login and password fields to be displayed in an alert, as seen in several built-in system apps.
In iOS 8, UIAlert
can add text fields with the add
method:
let login Action = UIAlert Action(title: "Login", style: .Default) { (_) in
let login Text Field = alert Controller.text Fields![0] as UIText Field
let password Text Field = alert Controller.text Fields![1] as UIText Field
login(login Text Field.text, password Text Field.text)
}
login Action.enabled = false
let forgot Password Action = UIAlert Action(title: "Forgot Password", style: .Destructive) { (_) in }
let cancel Action = UIAlert Action(title: "Cancel", style: .Cancel) { (_) in }
alert Controller.add Text Field With Configuration Handler { (text Field) in
text Field.placeholder = "Login"
NSNotification Center.default Center().add Observer For Name(UIText Field Text Did Change Notification, object: text Field, queue: NSOperation Queue.main Queue()) { (notification) in
login Action.enabled = text Field.text != ""
}
}
alert Controller.add Text Field With Configuration Handler { (text Field) in
text Field.placeholder = "Password"
text Field.secure Text Entry = true
}
alert Controller.add Action(login Action)
alert Controller.add Action(forgot Password Action)
alert Controller.add Action(cancel Action)
Creating a Sign Up Form
UIAlert
goes even further to allow any number of text fields, each with the ability to be configured and customized as necessary. This makes it possible to create a fully-functional signup form in a single modal alert:
alert Controller.add Text Field With Configuration Handler { (text Field) in
text Field.placeholder = "Email"
text Field.keyboard Type = .Email Address
}
alert Controller.add Text Field With Configuration Handler { (text Field) in
text Field.placeholder = "Password"
text Field.secure Text Entry = true
}
alert Controller.add Text Field With Configuration Handler { (text Field) in
text Field.placeholder = "Password Confirmation"
text Field.secure Text Entry = true
}
Though, it must be said, caveat implementor. Just because you can implement a signup form in an alert doesn’t mean you should. Suck it up and use a view controller, like you’re supposed to.
Caveats
Attempting to add a text field to an alert controller with style .Action
will throw the following exception:
Terminating app due to uncaught exception
NSInternal
, reason: ‘Text fields can only be added to an alert controller of styleInconsistency Exception UIAlert
’Controller Style Alert
Likewise, attempting to add more than one .Cancel
action to either an alert or action sheet will raise:
Terminating app due to uncaught exception
NSInternal
, reason: ‘Inconsistency Exception UIAlert
can only have one action with a style ofController UIAlert
’Action Style Cancel