Did you know that UIAlertView
and UIActionSheet
(as well as their respective delegate protocols) are deprecated in iOS 8?
It's true. ⌘-click on UIAlertView
or UIActionSheet
in your code, and check out the top-level comment:
UIAlertView
is deprecated. UseUIAlertController
with apreferredStyle
ofUIAlertControllerStyleAlert
instead.
Wondering why Xcode didn't alert you to this change? Just read down to the next line:
@availability(iOS,introduced=2.0)
Although these classes are technically deprecated, this is not communicated in the @availability
attribute. This should be of little surprise, though; UIAlertView
has always played it fast and loose.
From its very inception, UIAlertView
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 /* <UIAlertViewDelegate */)delegate
). And what protocol methods that could be implemented invoked the cringeworthy notion of having "clicked" rather than "tapped" a buttonAtIndex:
. This, and trailing variable-length arguments for otherButtonTitles
, awkward management of button indexes, a -show
method with no regard for the view hierarchy... the list goes on.
UIActionSheet
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 UIAlertController
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 UIAlertController
, showing first how to port existing alert behavior, and then how this behavior can be extended.
UIAlertController
replaces both UIAlertView
and UIActionSheet
, thereby unifying the concept of alerts across the system, whether presented modally or in a popover.
Unlike the classes it replaces, UIAlertController
is a subclass of UIViewController
. As such, alerts now benefit from the configurable functionality provided with view controller presentation.
UIAlertController
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, UIAlertAction
, 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 UIAlertView
& UIActionSheet
in favor or much more convenient completion handlers.
Comparing the Old and New Ways to Alerts
A Standard Alert
The Old Way: UIAlertView
letalertView=UIAlertView(title:"Default Style",message:"A standard alert.",delegate:self,cancelButtonTitle:"Cancel",otherButtonTitles:"OK")alertView.alertViewStyle=.DefaultalertView.show()
// MARK: UIAlertViewDelegatefuncalertView(alertView:UIAlertView,clickedButtonAtIndexbuttonIndex:Int){switchbuttonIndex{// ...}}
The New Way: UIAlertController
letalertController=UIAlertController(title:"Default Style",message:"A standard alert.",preferredStyle:.Alert)letcancelAction=UIAlertAction(title:"Cancel",style:.Cancel){(action)in// ...}alertController.addAction(cancelAction)letdestroyAction=UIAlertAction(title:"Destroy",style:.Destructive){(action)inprintln(action)}alertController.addAction(destroyAction)letOKAction=UIAlertAction(title:"OK",style:.Default){(action)in// ...}alertController.addAction(OKAction)self.presentViewController(alertController,animated:true){// ...}
A Standard Action Sheet
UIActionSheet
letactionSheet=UIActionSheet(title:"Takes the appearance of the bottom bar if specified; otherwise, same as UIActionSheetStyleDefault.",delegate:self,cancelButtonTitle:"Cancel",destructiveButtonTitle:"Destroy",otherButtonTitles:"OK")actionSheet.actionSheetStyle=.DefaultactionSheet.showInView(self.view)
// MARK: UIActionSheetDelegatefuncactionSheet(actionSheet:UIActionSheet,clickedButtonAtIndexbuttonIndex:Int){switchbuttonIndex{...}}
UIAlertController
letalertController=UIAlertController(title:nil,message:"Takes the appearance of the bottom bar if specified; otherwise, same as UIActionSheetStyleDefault.",preferredStyle:.ActionSheet)letcancelAction=UIAlertAction(title:"Cancel",style:.Cancel){(action)in// ...}alertController.addAction(cancelAction)letOKAction=UIAlertAction(title:"OK",style:.Default){(action)in// ...}alertController.addAction(OKAction)self.presentViewController(alertController,animated:true){// ...}
New Functionality
UIAlertController
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 UIAlertController
, it's possible to do a lot more out-of-the-box:
Alert with Destructive Button
The type of an action is specified UIAlertActionStyle
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 UIAlertAction
with style .Destructive
:
letalertController=UIAlertController(title:"Title",message:"Message",preferredStyle:.Alert)letcancelAction=UIAlertAction(title:"Cancel",style:.Cancel){(action)inprintln(action)}alertController.addAction(cancelAction)letdestroyAction=UIAlertAction(title:"Destroy",style:.Destructive){(action)inprintln(action)}alertController.addAction(destroyAction)self.presentViewController(alertController,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:
letoneAction=UIAlertAction(title:"One",style:.Default){(_)in}lettwoAction=UIAlertAction(title:"Two",style:.Default){(_)in}letthreeAction=UIAlertAction(title:"Three",style:.Default){(_)in}letcancelAction=UIAlertAction(title:"Cancel",style:.Cancel){(_)in}alertController.addAction(oneAction)alertController.addAction(twoAction)alertController.addAction(threeAction)alertController.addAction(cancelAction)
Creating a Login Form
iOS 5 added the alertViewStyle
property to UIAlertView
, 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, UIAlertController
can add text fields with the addTextFieldWithConfigurationHandler
method:
letloginAction=UIAlertAction(title:"Login",style:.Default){(_)inletloginTextField=alertController.textFields![0]asUITextFieldletpasswordTextField=alertController.textFields![1]asUITextFieldlogin(loginTextField.text,passwordTextField.text)}loginAction.enabled=falseletforgotPasswordAction=UIAlertAction(title:"Forgot Password",style:.Destructive){(_)in}letcancelAction=UIAlertAction(title:"Cancel",style:.Cancel){(_)in}alertController.addTextFieldWithConfigurationHandler{(textField)intextField.placeholder="Login"NSNotificationCenter.defaultCenter().addObserverForName(UITextFieldTextDidChangeNotification,object:textField,queue:NSOperationQueue.mainQueue()){(notification)inloginAction.enabled=textField.text!=""}}alertController.addTextFieldWithConfigurationHandler{(textField)intextField.placeholder="Password"textField.secureTextEntry=true}alertController.addAction(loginAction)alertController.addAction(forgotPasswordAction)alertController.addAction(cancelAction)
Creating a Sign Up Form
UIAlertController
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:
alertController.addTextFieldWithConfigurationHandler{(textField)intextField.placeholder="Email"textField.keyboardType=.EmailAddress}alertController.addTextFieldWithConfigurationHandler{(textField)intextField.placeholder="Password"textField.secureTextEntry=true}alertController.addTextFieldWithConfigurationHandler{(textField)intextField.placeholder="Password Confirmation"textField.secureTextEntry=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 .ActionSheet
will throw the following exception:
Terminating app due to uncaught exception
NSInternalInconsistencyException
, reason: 'Text fields can only be added to an alert controller of styleUIAlertControllerStyleAlert
'
Likewise, attempting to add more than one .Cancel
action to either an alert or action sheet will raise:
Terminating app due to uncaught exception
NSInternalInconsistencyException
, reason: 'UIAlertController
can only have one action with a style ofUIAlertActionStyleCancel
'