What You’ll Learn in This Hour:
Different types of user notifications
How to create alert controllers
Methods for collecting input from alerts
How to use action sheet alert styles
How to implement short sounds and vibrations
iOS presents developers with many opportunities for creating unique user interfaces, but certain elements must be consistent across all applications. When users need to be notified of an application event or make a critical decision, it is important to present them with interface elements that immediately make sense. In this hour, we look at several different ways an application can notify a user that something has happened. It’s up to you to determine what that something is, but these are the tools you need to keep users of your apps “in the know.”
Applications on iOS are user centered, which means they typically don’t perform utility functions in the background or operate without an interface. They enable users to work with data, play games, communicate, or carry out dozens of other activities. Despite the variation in activities, when an application needs to show a warning, provide feedback, or ask the user to make a decision, it does so in a common way. Cocoa Touch leverages a variety of objects and methods to gain your attention, including alert controllers and System Sound Services. Unlike many of the other objects we’ve worked with, these require us to build them in code. So don’t start digging through the Interface Builder (IB) Object Library just yet.
Note
Did you notice that I said applications typically don’t operate in the background? That’s because, with iOS 4 or later, some do. Applications running in the background have a unique set of capabilities, including additional types of alerts and notifications. You learn more about these in Hour 22, “Building Background-Ready Applications.”
Alert controllers (UIAlertController
) are an addition to iOS 8 that unifies a few long-standing methods of getting user input. The UIAlertController
is used for creating a view and view controller that can display either a simple alert, or a multiple-choice list. These alert styles are known as alerts and action sheets, respectively, and were previously handled by the UIAlertView
and UIActionSheet
classes.
The actions (buttons) that users can access within an alert controller are unique objects of the type UIAlertAction
and are added to an alert controller using the addAction
method. UITextField
objects can also be added using the UIAlertController
method addTextFieldWithConfigurationHandler
.
Sometimes users need to be informed of changes when an application is running. More than just a change in the current view is required when an internal error event occurs (such as low-memory condition or a dropped network connection), for example, or upon completion of a long-running activity. In these cases, you want to display what we’ll call an alert.
A UIAlertController
with the Alert
style is used to present a translucent floating modal window with a message, a few option buttons, and text entry fields, as shown in Figure 10.1.
Implementing an alert takes little effort: You declare a UIAlertController
object, initialize it, and present it. Consider the code fragment in Listing 10.1.
1: let alertController = UIAlertController(title: "Email Address",
2: message: "Please enter your email address:",
3: preferredStyle: UIAlertControllerStyle.Alert)
4:
5: let defaultAction = UIAlertAction(title: "Ok",
6: style: UIAlertActionStyle.Default,
7: handler: nil)
8:
9: let destructiveAction = UIAlertAction(title: "Erase My Device",
10: style: UIAlertActionStyle.Destructive,
11: handler: nil)
12:
13:alertController.addTextFieldWithConfigurationHandler({(textField: UITextField!) in
14: textField.placeholder="Email Address"
15: textField.keyboardType=UIKeyboardType.EmailAddress
16: }
17:
18: alertController.addAction(defaultAction)
19: alertController.addAction(destructiveAction)
20:
21: presentViewController(alertController, animated: true, completion: nil)
In lines 1–3, I declare a constant alertController
to hold an instance of UIAlertController
. The initialization method of the alert view provides three parameters we can customize:
title: The title that is displayed at the top of the alert
message: Sets the string that will appear in the content area of the dialog box
preferredStyle: Defines whether the alert is a dialog box (UIAlertControllerStyle.Alert
) or an action sheet in appearance (UIAlertControllerStyle.ActionSheet
)
Lines 5–7 configure the first of two buttons that will be added to the alert. A constant, defaultAction
, of the type UIAlertAction
is created using an alert action convenience initialization method. This method uses a title
parameter to set the title on the button, a handler
(code to be executed when the button is touched—we’ll get to that in a minute or two), and a style
, which can be one of three values:
UIAlertActionStyle.Default
: The default button style—any general “actions” should use this style. You might see this used for an Ok button.
UIAlertActionStyle.Cancel
: An action that will cancel the current operation (usually labeled Cancel).
UIAlertActionStyle.Destructive
: This button uses red text in its label and is displayed when there is the potential to lose data by completing the action (such as erasing a form, for example).
This first button displays the label Ok, does nothing when it is touched (handler: nil
), and uses the default alert action style.
Lines 9–11 configure a second button, destroyAction
, using the destructive style, another nil
handler, and the label Erase My Device.
Lines 13–16 add a text field to the controller with the addTextFieldWithConfigurationHandler
. This method accepts a closure as its parameter, which, in turn, accepts a single textField
variable. The textfield
is automatically initialized for us, we just need to adjust its attributes as we see fit (lines 14–15). Any UITextField
variable properties can be set in this closure.
Lines 18–19 use the UIViewController
method addAction
to add the defaultAction
and destroyAction
buttons to the alert controller.
Finally, in line 21, the alertController
object that we’ve lovingly crafted is displayed to the user using the UIViewController
method presentViewController
.
So, what exactly does this alert do if we were to try to use it? Absolutely nothing. It will display, show the text field and buttons that we configured, and then just go away as soon as we touch a button. That’s exactly what we get if we set the handler
parameter to nil
when defining the alert’s actions.
Assuming the goal is to actually react to the alert, we’ll need to do a teensy bit more work.
When I constructed the alert example in Listing 10.1, I defined two actions, both with a handler of nil
. I also took the time to write a sidebar explaining (well, trying to explain) how handlers are often defined as blocks of code called closures. Putting two and two together, how do you think we’ll be able to react to the user touching one of the buttons? That’s right, you’ve got it: by defining a handler closure for each action! (That was your guess, yes?)
By using closures with actions, we can define what happens when the user touches any of the UIAlertAction
buttons that are added to a UIAlertController
. Each action is independent, so we don’t have to worry about figuring out which button was pressed and then write a big method to handle everything.
If the alert view is displaying text fields, we can access the fields through the alert controller’s textFields variable property (an array of AnyObject)
, where textFields[0]
is the first text field added to the controller, textFields[1]
is the second, and so on.
For example, if I were to revise the defaultAction
that I created in lines 5–7 of Listing 10.1 so that it would retrieve the contents of the alert’s Email Address field, the code would look at bit like the fragment in Listing 10.2.
1: let defaultAction = UIAlertAction(title: "Ok",
2: style: UIAlertActionStyle.Default,
3: handler: {(alertAction: UIAlertAction!) in
4: // The user touched Ok!
5: let emailAddress: String = (alertController.textFields![0] as UITextField).text
6: })
Line 3 starts the handler and its closure. This is the code that will be executed when the user invokes the action by touching the Ok button. Line 5 sets a constant emailAddress
to the text
variable property of the first text field within the alertController
’s textFields
array. Note that we have to both unwrap textFields
before we can access it, and we have to cast the member of the array as a UITextField
because (for reasons I can’t fathom) textFields
is defined by Cocoa as an array of AnyObject
rather than an array of UITextField
s.
While the syntax may look ugly, it should be pretty clear what it does. (If it isn’t, don’t worry; we’ll try it out ourselves in a few moments.)
Alerts are used to display messages that indicate a change in state or a condition within an application that a user should acknowledge, but are other instances where a different solution would be better. For example, if an application provides the option to share information with a friend, the user might be prompted for the method of sharing (such as sending an email, uploading a file, and so on). In this case, the iOS-way of alerting the user to making this decision is by way of an action sheet.
You can see this behavior when touching and holding a link in Safari, as shown in Figure 10.2.
In previous editions of this book, I stated that implementing action sheets were very similar to alerts. With iOS 8, that statement is now false. In fact, implementing action sheets is now identical to alerts—with the exception of not being able to use text fields within an action sheet.
For example, take a look at Listing 10.3.
1: let alertController = UIAlertController(title: "Do Something",
2: message: "Please choose an action:",
3: preferredStyle: UIAlertControllerStyle.ActionSheet)
4:
5: let defaultAction = UIAlertAction(title: "Send Memo",
6: style: UIAlertActionStyle.Default,
7: handler: {(alertAction: UIAlertAction!) in
8: // The user touched Send Memo
9: })
10:
11: let destructiveAction = UIAlertAction(title: "Erase My Device",
12: style: UIAlertActionStyle.Destructive,
13: handler: {(alertAction: UIAlertAction!) in
14: // The user wants us to erase the device. Oh well!
15: })
16:
17: let cancelAction = UIAlertAction(title: "Cancel",
18: style: UIAlertActionStyle.Cancel,
19: handler: nil)
20:
21: alertController.addAction(defaultAction)
22: alertController.addAction(destructiveAction)
23: alertController.addAction(cancelAction)
24:
25: presentViewController(alertController, animated: true, completion: nil)
Lines 1–3 declare and instantiate an instance of UIAlertController
called alertContoller
. The setup of the controller is the same as with an alert, but we set the preferredStyle
to UIAlertControllerStyle.ActionSheet
.
Lines 5–9, 10–15, and 17–19 define three actions (buttons) that will be displayed by the controller. Remember, the handler
for each action is where we can add code to react when the user touches the button. If the handler is set to nil
(as in the case of the cancel
action), touching the button simply dismisses the action sheet.
Lines 21–23 add the three actions to the alert controller, and line 25 presents the controller onscreen.
Tip
Action sheet and alert styles are identical in how they are initialized, modified, and ultimately, acted upon. On some devices, however, an action sheet can be associated with an onscreen control—like a toolbar icon. This changes the onscreen appearance slightly, but doesn’t affect how you create and populate the alert controller.
Visual notifications are great for providing feedback to a user and getting critical input. Other senses, however, can prove just as useful for getting a user’s attention. Sound, for example, plays an important role on nearly every computer system (regardless of platform or purpose). Sounds tell us when an error has occurred or an action has been completed. Sounds free a user’s visual focus and still provide feedback about what an application is doing.
Vibrations take alerts one step further. When a device has the ability to vibrate, it can communicate with users even if they can’t see or hear it. For the iPhone, vibration means that an app can notify users of events even when stowed in a pocket or resting on a nearby table. The best news of all? Both sounds and vibrations are handled through the same simple code, meaning that you can implement them relatively easily within your applications.
To enable sound playback and vibration, we take advantage of System Sound Services. System Sound Services provides an interface for playing back sounds that are 30 seconds or less in length. It supports a limited number of file formats (specifically CAF, AIF, and WAV files using PCM or IMA/ADPCM data). The functions provide no manipulation of the sound, nor control of the volume, so you do not want to use System Sound Services to create the soundtrack for your latest and greatest iOS game. In Hour 19, “Working with Rich Media,” we explore additional media playback features of iOS.
iOS supports three different notifications using this API:
Sound: A simple sound file is played back immediately. If the device is muted, the user hears nothing.
Alert: Again, a sound file is played, but if the device is muted and set to vibrate, the user is alerted through vibration.
Vibrate: The device is vibrated, regardless of any other settings.
To use System Sound Services from a project, you must add the AudioToolbox framework and any sound files you want to play. Because AudioToolbox is an Apple-provided framework and supports being imported as a module, we can prepare an application to use it by adding a single line to our code:
import AudioToolbox
Unlike most of the other development functionality discussed in this book, the System Sound Services functionality is not implemented as a class. Instead, you use more traditional C-style function calls to trigger playback.
To play audio, the two functions you use are AudioServicesCreateSystemSoundID
and AudioServicesPlaySystemSound
. You also need to declare a variable of the type SystemSoundID
. This represents the sound file that we are working with. To get an idea of how it all comes together, look at Listing 10.4.
1: var soundID: SystemSoundID = 0
2: let soundFile: String = NSBundle.mainBundle().pathForResource("mysound", ofType: "wav")!
3: let soundURL: NSURL = NSURL(fileURLWithPath: soundFile)!
4: AudioServicesCreateSystemSoundID(soundURL, &soundID)
5: AudioServicesPlaySystemSound(soundID)
This might seem a bit alien after all the objects we’ve been using. Let’s take a look at the functional pieces.
Line 1 starts things off by declaring a variable, soundID
, that will be an iOS-generated integer that references the sound file we want to play. Next, in line 2, we declare and assign a string (soundFile
) to the path of the sound file mysound.wav. This works by first using the NSBundle
class method mainBundle
to return an NSBundle
object that corresponds to the directory containing the current application’s executable binary. The NSBundle
object’s pathForResource:ofType
method is then used to identify the specific sound file by name and extension.
Once we have the path as a String
, we create an NSURL
object from it in line 3. This is required for the AudioServicesCreateSystemSoundID
function in line 4, which takes the location of the sound file as an NSURL
object, along with a pointer to the soundID
variable. The &
denotes that we’re sending a “pointer” to soundID
rather than the value of soundID
. The AudioServicesCreateSystemSoundID
function’s purpose is to update soundID
with whatever magical integer references our sound on iOS.
After soundID
has been properly set up, all that remains is playing it. Passing the soundID
variable to the AudioServicesPlaySystemSound
function, as shown in line 5, makes it happen.
The difference between an alert sound and a system sound is that an alert sound, if muted, automatically triggers a phone vibration. The setup and use of an alert sound is identical to a system sound. In fact, playing an alert is just a matter of substituting the function AudioServicesPlayAlertSound
in place of AudioServicesPlaySystemSound
.
Vibrations alone are even easier. To vibrate a compatible device (currently iPhones), you just provide the 32bit integer version of kSystemSoundID_Vibrate
to AudioServicesPlaySystemSound
:
AudioServicesPlaySystemSound(UInt32(kSystemSoundID_Vibrate))
Tip
The need to use UInt32()
to convert the kSystemSoundID_Vibrate
constant into something that AudioServicesPlaySystemSound
can work with seems to be an oversight on Apple’s part. I hope that in the future we can just use kSystemSoundID_Vibrate
directly.
Now that you understand the different alert styles we have to work with, it’s time to implement them for real. We’ll test several variations of alerts, action sheets, and sounds in this hour’s tutorial.
Tip
Attempting to vibrate a device without vibration capabilities (like an iPad) will fail silently. You can safely leave vibration code in your app regardless of the device you are targeting.
Because all the logic for implementing alerts, action sheets, or System Sound Services is contained in small, easy-to-understand chunks of code, this hour’s project differs a bit from what you’ve seen in other hours. We treat this hour’s project like a sandbox. We set it up, and then spend a good amount of time talking about the code that needs to be written to make it work, and we test it along the way.
You’ll generate alerts, alerts with multiple buttons, alerts with fields, action sheet alerts, sounds, and even vibrate your device (if it’s an iPhone, that is).
Unlike other projects where our UI design was intimately tied to the code, this tutorial’s interface is rather inconsequential. We’re simply interested in creating buttons to trigger actions that demonstrate the different alert methods and providing a single output area so we can see how the user responded. Everything to generate alerts, action sheets, sounds, and vibrations is handled entirely in code, so the sooner we get the framework set up for the project, the sooner we can get to the implementation logic.
To practice using these alert classes and methods, we need to create a new project with buttons for activating the different styles of notifications. Open Xcode and create a new project based on the Single View Application template. Name the project GettingAttention.
Several resources that we need in this project aren’t there by default, notably the sounds that we will be playing with System Sound Services. Let’s add these important resources now.
With your project open in Xcode, return to the Finder and navigate to the Sounds folder within this hour’s project folder. Drag the folder into your Xcode project folder, choosing to copy the files and create groups when prompted.
You should now see the files listed in your project group, as shown in Figure 10.3.
The last step before we can create our GettingAttention application is to figure out what outlets and actions we need to fully test everything that we want. As mentioned earlier, this is a barebones app, nothing flashy. The only outlet we need is for a single label (UILabel
) that provides some feedback to what the user has done. This will be named userOutput
.
In addition to the outlet, we need a total of seven different actions, all to be triggered by different buttons in the user interface: doAlert
, doMultiButtonAlert
, doAlertInput
, doActionSheet
, doSound
, doAlertSound
, and finally, doVibration
.
That’ll do it. Everything else is handled in code. Let’s create the interface and make our connections.
Open the Main.storyboard file in Interface Builder (IB), select the view controller, then use the Attributes Inspector to set the simulated size to the device that you’re using. This is, of course, optional, but it helps keep us focused on creating a quick and dirty UI rather than elegant design.
We need to add seven buttons and a text label to the empty view. You should be getting quite familiar with this process by now.
Add a button to the view by opening the Object Library (View Utilities, Show Object Library) and dragging a button (IUButton
) to the View window. Add six more buttons using the library or by copying and pasting the first button.
Change the button labels to correspond to the different notification types that we’ll be using. Specifically, name the buttons (top to bottom) as follows:
Alert Me!
Alert with Buttons!
I Need Input!
Lights, Camera, Action Sheet
Play Sound
Play Alert Sound
Vibrate Device
Drag a label (UILabel
) from the library to the bottom of the view. Remove the default label text and set the text to align center. The interface should resemble Figure 10.4. I’ve chosen to cluster my buttons based on their function. You can arrange yours however you want.
The interface itself is finished, but we still need to make the connection between the objects and our code. It’s probably self-explanatory, but the connections you will be building are listed here.
First the outlet:
User Output Label (UILabel): userOutput
And then the actions:
Alert Me! (UIButton): doAlert
Alert with Buttons! (UIButton): doMultiButtonAlert
I Need Input! (UIButton): doAlertInput
Lights, Camera, Action Sheet (UIButton): doActionSheet
Play Sound (UIButton): doSound
Play Alert Sound (UIButton): doAlertSound
Vibrate Device (UIButton): doVibration
With the Main.storyboard file selected, click the assistant editor button, and then hide the project navigator and document outline (Editor, Hide Document Outline) to make room for your connections. The ViewController.swift file should be visible to the right of your interface.
Control-drag from our single lonely label to just below the class
line in ViewController.swift. When prompted, choose to create a new outlet named userOutput, as shown in Figure 10.5.
Now, Control-drag from the Alert Me! button to just below the @IBOutlet
declaration in the ViewController.swift file, connecting to a new action named doAlert
, as shown in Figure 10.6.
Repeat this process for the other six buttons. Alert with Buttons! connects to doMultiButtonAlert
; I Need Input! should connect to the doAlertInput
method; Lights, Camera, Action Sheet to doActionSheet
; Play Sound to doSound
; Play Alert Sound to doAlertSound
; and Vibrate Device to doVibration
.
The framework for our test of notifications is finished, and we’re ready to jump into code. Switch back to the standard editor and display the project navigator (Command-1). Open the ViewController.swift file; we start by implementing a simple alert-style dialog box
The simplest case of an alert that a user can encounter (and that a developer can develop) is an alert that is displayed and dismissed without changing the flow of the application at all. In other words, the alert is simply that: an alert. When the user presses the button to dismiss it, nothing else changes.
Edit ViewController.swift and enter the code shown in Listing 10.5 for the doAlert
implementation.
1: @IBAction func doAlert(sender: AnyObject) {
2: let alertController = UIAlertController(title: "Alert Me Button Selected",
3: message: "I need your attention NOW!",
4: preferredStyle: UIAlertControllerStyle.Alert)
5:
6: let defaultAction = UIAlertAction(title: "Ok",
7: style: UIAlertActionStyle.Cancel,
8: handler: nil)
9:
10: alertController.addAction(defaultAction)
11: presentViewController(alertController, animated: true, completion: nil)
12: }
If you were paying attention at the start of this hour’s lesson, this method should look familiar.
In lines 2–4, we declare and configure our instance of UIAlertController
in a variable called alertController
. The alert is initialized with a title (Alert Me Button Selected
), a message (I need your attention NOW!
), and a preferred style of Alert
.
There is a single action, defaultAction
, defined in lines 6–8. This creates a button with the title Ok, styled with UIAlertActionStyle.Cancel
and a nil
handler. Why the Cancel
style? Because the purpose of the button is to just get rid of the alert; there is no handler to react to the action. You could also use the default style, but “cancel” just makes more sense to me.
Line 10 adds defaultAction
to alertController
, and line 11 displays it onscreen.
You can now run the project and test the first button, Alert Me!, Figure 10.7 shows the outcome of your first alert implementation.
Tip
An alert doesn’t have to be a single-use object. If you’re going to be using an alert repeatedly, create a variable property for the alert controller, set it up when your view is loaded, and show it as needed.
An alert with a single button is easy to implement because you do not have to program any additional logic. The user taps the button, the alert is dismissed, and execution continues as normal. In most cases, however, your application will display multiple button choices and react appropriately to whatever option the user chooses.
How difficult is this? Not at all. Each action that is added to an alert can include a handler closure to carry out specific tasks. Create actions, set handlers, add the actions to the alert controller—all done. To test this, write an updated multibutton version of the doAlert
method within the doMultiButtonAlert
method stub created earlier.
Listing 10.6 shows my implementation.
Tip
At most, an alert view can display five buttons (including the button designated as the cancel button) simultaneously. Attempting to add more may result in some unusual onscreen effects, such as display of clipped/partial buttons.
1: @IBAction func doMultiButtonAlert(sender: AnyObject) {
2: let alertController = UIAlertController(title: "Alert with Buttons Selected",
3: message: "Options are good for people!",
4: preferredStyle: UIAlertControllerStyle.Alert)
5:
6: let nowAction = UIAlertAction(title: "Do Something Now",
7: style: UIAlertActionStyle.Default,
8: handler: {(alertAction: UIAlertAction!) in
9: self.userOutput.text="Pressed Now"
10: })
11:
12: let laterAction = UIAlertAction(title: "Do Something Later",
13: style: UIAlertActionStyle.Default,
14: handler: {(alertAction: UIAlertAction!) in
15: self.userOutput.text="Pressed Later"
16: })
17:
18: let cancelAction = UIAlertAction(title: "Never Do It",
19: style: UIAlertActionStyle.Cancel,
20: handler: {(alertAction: UIAlertAction!) in
21: self.userOutput.text="Pressed Never"
22: })
23:
24: alertController.addAction(nowAction)
25: alertController.addAction(laterAction)
26: alertController.addAction(cancelAction)
27:
28: presentViewController(alertController, animated: true, completion: nil)
29: }
In this new implementation, three actions (buttons) are configured in lines 6–10, 12–16, and 18–22. Unlike the previous method, these actions all include a handler closure that sets the userOutput
label to a message corresponding to the button pressed.
Notice that in each closure (lines 9, 15, and 21), I prefix the userOutput
object with self
. Closures act like independent isolated pieces of code and can’t access variables and methods defined within your class without specifying self
. If you happen to leave this off, Xcode will warn you of the error and offer to fix it for you.
Lines 24–26 add the actions to the alert controller, while line 28 displays it.
Give the application another test run. Pressing Alert with Buttons! should now open the alert view displayed in Figure 10.8.
Try touching one of the alert buttons. The alert view is dismissed, but, more important, the text in the userOutput
label is set to a message identifying the button.
Caution: Always Active
Don’t assume that application processing stops when the alert window is on the screen. Your code continues to execute after you show the alert.
Although buttons can be used to generate user input from an alert, you might have noticed that some applications actually present text fields within an alert box. The App Store, for example, prompts for your Apple ID password before it starts downloading a new app.
To add fields to your alert dialogs, you use the alert controller’s addTextFieldWithConfigurationHandler
. The text field is added and can be configured using any of the variable properties supported by the UITextField
class.
As an example, let’s create an email entry alert that collects an email address, then displays it when the alert’s button is touched. Update the doAlertInput
method stub with the implementation shown in Listing 10.7.
1: @IBAction func doAlertInput(sender: AnyObject) {
2: let alertController = UIAlertController(title: "Email Address",
3: message: "Please enter your email address below:",
4: preferredStyle: UIAlertControllerStyle.Alert)
5:
6: alertController.addTextFieldWithConfigurationHandler({(textField: UITextField!) in
7: textField.placeholder="Email Address"
8: textField.keyboardType=UIKeyboardType.EmailAddress
9: })
10:
11: let defaultAction = UIAlertAction(title: "Ok",
12: style: UIAlertActionStyle.Default,
13: handler: {(alertAction: UIAlertAction!) in
14: let emailAddress: String = (alertController.textFields![0] as UITextField).text
15: self.userOutput.text="Entered '(emailAddress)'"
16: })
17:
18: alertController.addAction(defaultAction)
19: presentViewController(alertController, animated: true, completion: nil)
20: }
The text field is added in lines 6–9. Note that the UITextField
variable properties placeholder
and keyboardType
are used to configure the appearance of the field. You might also consider using the secureTextEntry
Boolean variable to make the field appear as a password entry.
Lines 11–16 create an action, defaultAction
, with a handler that updates the userOutput
text to show the email address the user entered. As described earlier this hour, the text field is accessed from the alertController
object’s textFields
array. This array is created for us automatically each time we add a text field to an alert.
Line 18 adds the defaultAction
to the alert controller. Line 19 displays the alert controller.
Run the application and touch the I Need Input! button. You should see the alert, as demonstrated in Figure 10.9. Enter an email address and touch Ok; the address is retrieved from the alert and displayed in the output label.
Now that you’ve implemented several types of alerts, action sheets will pose no difficulty at all. The setup and handling of an action sheet is more straightforward than an alert view because action sheets can do one thing and only one thing: show a list of actions (buttons).
Tip
Action sheets can take up to seven buttons on a 3.5-inch iPhone (including “cancel” and the “destructive” button) while maintaining a standard layout. If you exceed seven, however, the display automatically changes into a scrolling list. This gives you room to add as many options as you need.
To create our action sheet, we’ll implement the method stub doActionSheet
created within the ViewController.swift file. Recall that this method is triggered by pushing the Lights, Camera, Action Sheet button. It displays the title Available Actions and has a cancel button named Cancel, a destructive button named Destroy, and two other buttons named Negotiate and Compromise.
Add the code in Listing 10.8 to the doActionSheet
method.
1: @IBAction func doActionSheet(sender: AnyObject) {
2: let alertController = UIAlertController(title: "Available Actions",
3: message: "Choose something from this list",
4: preferredStyle: UIAlertControllerStyle.ActionSheet)
5:
6: let negotiateAction = UIAlertAction(title: "Negotiate",
7: style: UIAlertActionStyle.Default,
8: handler: {(alertAction: UIAlertAction!) in
9: self.userOutput.text="Pressed Negotiate"
10: })
11:
12: let compromiseAction = UIAlertAction(title: "Compromise",
13: style: UIAlertActionStyle.Default,
14: handler: {(alertAction: UIAlertAction!) in
15: self.userOutput.text="Pressed Compromise"
16: })
17:
18: let destroyAction = UIAlertAction(title: "Destroy",
19: style: UIAlertActionStyle.Destructive,
20: handler: {(alertAction: UIAlertAction!) in
21: self.userOutput.text="Pressed Destroy"
22: })
23:
24: let cancelAction = UIAlertAction(title: "Cancel",
25: style: UIAlertActionStyle.Cancel,
26: handler: {(alertAction: UIAlertAction!) in
27: self.userOutput.text="Pressed Cancel"
28: })
29:
30: alertController.addAction(negotiateAction)
31: alertController.addAction(compromiseAction)
32: alertController.addAction(destroyAction)
33: alertController.addAction(cancelAction)
34:
35:
36: if (alertController.popoverPresentationController != nil) {
37: alertController.popoverPresentationController!.sourceView =
38: sender as UIButton
39: alertController.popoverPresentationController!.sourceRect =
40: (sender as UIButton).bounds
41: }
42:
43: presentViewController(alertController, animated: true, completion: nil)
44: }
Lines 2–4 instantiate a new instance of UIAlertController
—the same are our other methods—but this time, using the preferred style of UIAlertControllerStyle.ActionSheet
.
The rest of the code should look exactly like what you’ve already seen this hour—with one major exception (lines 36–41). To understand why these lines are necessary, we have to know a bit about the action sheets and the iPad.
On the iPad, action sheets should not be displayed directly on top of a view. The Apple UI guidelines say that they must be displayed within a popover. A popover is a unique UI element that appears when an onscreen item is touched and usually disappears when you touch somewhere on the background. Popovers also incorporate a small arrow that points toward the UI element that invoked them. You learn more about popovers in the next hour’s lesson.
When we create an action sheet on an iPad, iOS automatically configures a popover controller for us and stores it in the alert controller’s variable property popoverPresentationController
. In order for the popover controller to work, however, we need to set two variable properties of the popover controller: sourceView
(the view that the popover is originating from) and sourceRect
(a rectangle defining the area that the popover should point to). Aren’t these the same thing? Yep. Apple’s documentation even states we need one or the other. But unless both are set, the popover doesn’t work. Lines 37–38 set the sourceView
to the button that was touched, whereas lines 39–40 set the sourceRect
to the bounds of the same button.
On devices that aren’t an iPad, the popoverPresentationController
is set to nil
, so these configuration lines aren’t executed. The result? When running on the iPad, the action sheet appears in a popover. When running on an iPhone, the action sheet is shown in exactly the same manner we’d expect.
Run the application and touch the Lights, Camera, Action Sheet button to see the results. Figure 10.10 demonstrates the display.
Note: In a Popover? No Canceling!
When iOS is displaying an action sheet within a popover, it automatically removes the Cancel
-styled action from the sheet. On devices that support popovers, the interface convention for “canceling” a popover is to touch outside of the popover display—in other words, no separate action is needed.
Recall that to use System Sound Services from a project, you need the AudioToolbox framework and any sound files you want to play. Because we already included the sound resources, we just need to add the framework. To import the AudioToolbox framework and make our code aware of its existence, add an import
line to ViewController.swift. Insert this line immediately following the existing import
directive:
import AudioToolbox
We’re now ready to play sounds and vibrate the device. Very little changes from the example code that we covered earlier this hour.
The first thing that we want to implement is the doSound
method for playing system sounds. These are short sounds that, when muted, will not result in an accompanying vibration. The Sounds folder that you added to the project during the setup contains a file soundeffect.wav that we will use to implement system sound playback.
Edit the ViewController.swift implementation file and complete doSound
, as shown in Listing 10.9.
1: @IBAction func doSound(sender: AnyObject) {
2: var soundID: SystemSoundID = 0
3: let soundFile: String = NSBundle.mainBundle().pathForResource("soundeffect",
4: ofType: "wav")!
5: let soundURL: NSURL = NSURL(fileURLWithPath: soundFile)!
6: AudioServicesCreateSystemSoundID(soundURL, &soundID)
7: AudioServicesPlaySystemSound(soundID)
8: }
Line 2 declares soundID
, a variable that refers to the sound file.
In lines 3–4, we declare and assign a string (soundFile
) to the path of the sound file soundeffect.wav. Line 5 turns that string into an NSURL
named soundURL
.
In line 6, we use the AudioServicesCreateSystemSoundID
function to create a SystemSoundID
that represents the sound file.
Line 7 uses the AudioServicesPlaySystemSound
function to play the sound.
Run and test the application. Pressing the Play Sound button should now play back the sound effect WAV file.
Caution: This Simulation May Be Broken!
Sometime during the development of Xcode 6.x, the ability for the simulator to play sounds via System Sound Services stopped working. By the time you read this, it might work again. Worst case, the code executes, but does nothing in the simulator. Try running it on a device and you should be fine.
As mentioned earlier this hour, the difference between an alert sound and a system sound
is that an alert sound, if muted, automatically triggers a vibration. The setup and use of an alert sound is identical to a system sound. In fact, to implement the doAlertSound
method stub in GettingAttentionViewController.swift, use the same code as the doSound
method in Listing 10.13, substituting the sound file alertsound.wav and using the function AudioServicesPlayAlertSound
rather than AudioServicesPlaySystemSound
:
AudioServicesPlayAlertSound(soundID)
After implementing the new method, run and test the application. Pressing the Play Alert Sound button plays the sound, and muting an iPhone causes the device to vibrate when the button is pressed.
For our grand finale, we implement the final method in our GettingAttention application: doVibration
. As you’ve already learned, the same System Sound Services that enabled us to play sounds and alert sounds also create vibrations. The magic we need here is the kSystemSoundID_Vibrate
constant. When this value is substituted for the SystemSoundID
and AudioServicesPlaySystemSound
is called, the device vibrates. It’s as simple as that! Implement the doVibration
method, as shown in Listing 10.10.
@IBAction func doVibration(sender: AnyObject) {
AudioServicesPlaySystemSound(UInt32(kSystemSoundID_Vibrate))
}
That’s all there is to it. You’ve now explored seven different ways of getting a user’s attention. These are techniques that you can use in any application to make sure that your user is alerted to changes that may require interaction and can respond if needed.
Your next step in making use of the notification methods discussed in this hour is to use them. These simple, but important, UI elements will help facilitate many of your critical user interactions. One topic that is beyond the scope of this book is the ability for a developer to push notifications to an iDevice.
Even without push notifications, you might want to add numeric badges to your applications. These badges are visible when the application isn’t running and can display any integer you want—most often, a count of items identified as “new” within the application (such as new news items, messages, events, and so on). To create application badges, look at the UIApplication
variable property applicationIconBadgeNumber
. Setting this to anything other than 0 will create and display the badge.
Another area that you might like to explore is how to work with rich media (Hour 19). The audio playback functions discussed in this hour are intended for alert-type sounds only. If you’re looking for more complete multimedia features, you need to tap into the AVFoundation framework, which gives you complete control over recording and playback features of iOS.
Finally, this hour covered notifications that occur when your application is running. For information about generating notifications when your app is stopped, check out Hour 22.
In this hour, you learned about two types of modal dialogs that can be used to communicate information to an application user and to enable the user to provide input at critical points in time. Alerts and action sheets have different appearances and uses but very similar implementations. Unlike many of the UI components we’ve used in this book, you cannot instantiate these with a simple drag and drop in IB.
We also explored two nonvisual means of communicating with a user: sounds and vibrations. Using the System Sound Services (by way of the AudioToolbox framework), you can easily add short sound effects and vibrate your iDevice. Again, you have to implement these in code, but in fewer than five lines, you can have your applications making noises and buzzing in your users’ hands.
Q. Can sounds be used in conjunction with alert views?
A. Yes. Because alerts are often displayed without warning, there is no guarantee that the user is looking at the screen. Using an alert sound provides the best chance for getting the user’s attention, either through an audible noise or an automatic vibration if the user’s sound is muted.
Q. Why don’t popovers work on the iPhone?
A. As of iOS 8, they do! That said, alert sheets, however, are still expected to be displayed in a certain way on both iPhone and iPad platforms. In other words, in this case, Apple has determined a UI standard that will automatically be enforced. It doesn’t mean that popovers are impossible on an iPhone; it just means that in this particular case (action sheets), they’ll only work on the iPad.
1. Both alerts and action sheets require the use of which of the following?
a. UISheetController
b. UIDialogController
c. UIActionController
d. UIAlertController
2. Buttons in an alert or an action sheet correspond to which of the following?
a. UIButtonAction
b. UIAlertAction
c. UIAlertObject
d. UIAlertButton
3. Before using the kSystemSoundID_Vibrate
constant, you must convert it to a what?
a. Boolean
b. String
c. UInt32
d. Float
4. Handlers are frequently provided in what form?
a. Structure
b. Block
c. Closet
d. Closure
5. What type of button is styled with red in an alert or action sheet?
a. Cancel
b. Default
c. Destructive
d. Erasure
6. After you’ve created an alert controller and configured it, you can display it with what method?
a. presentViewController
b. showViewController
c. displayViewController
d. useViewController
7. System Sound Services don’t play audio files directly. Instead, they require the use of what?
a. A sound ID
b. A sound path
c. A waveform
d. A sound descriptor
8. Alert controllers can display what to types of styles?
a. Alerts, warnings
b. Cautions, action sheets
c. Alerts, action sheets
d. Inputs, cautions
9. On some devices (just the iPad at the time of this writing), action sheets should be displayed with a what?
a. popoverController
b. popoverPresentationController
c. presentationController
d. presentationPopoverController
10. If you don’t want an alert action to do anything but dismiss the alert, what should you set the handler to?
a. AnyObject
b. null
c. nill
d. nil
1. D. Alert controllers (UIAlertController
) are required when creating alerts and action sheets.
2. B. Define UIAlertActions
to add functionality (and buttons!) to your alerts.
3. C. Unfortunately, you must convert the vibration constant to an unsigned 32-bit integer before you use it (UInt32
).
4. D. Handlers are often implemented using a closure.
5. C. Destructive buttons are highlighted in red and should be used with any action that may result in data loss.
6. A. Use the presentViewController
method to display alert controllers after they’ve been configured.
7. A. You must create a sound ID from an NSURL
before you can play a sound resource.
8. C. Alerts and action sheets are the two visual styles currently available in the alert controller (UIAlertController
).
9. B. When displaying an action sheet on the iPad, a popoverPresentationController
is automatically created and should be used to display the sheet.
10. D. Use nil
to set a handler that does nothing but dismiss the alert controller.
1. Practice adding text fields to a few alert controllers. Use a two-field alert to create a username and password dialog box.
2. Return to one or more of your earlier projects and add audio cues to the interface actions. Make switches click, buttons bing, and so on. Keep your sounds short, clear, and complementary to the actions that the users are performing.