Using Alerts and Pickers in iOS app

The simplest way to display data is through a label, but sometimes you need to display data and give the user a way to respond. In that case, you need to use an alert controller.

An alert controller can display a sheet that pops up on the screen, giving the user a chance to respond. Alert controllers appear over the user interface to force the user to respond by tapping a button to make a choice or dismiss the alert controller.

Besides displaying data, user interfaces also need to let people input data. If that data is text, then you can use a text field to let the user type anything in that text field such as a name.

A text field is perfect for accepting any type of data, but when you need to offer the user a list of valid choices, you’ll want to use a picker instead. Xcode offers two different types of pickers:

  • Date Picker – Displays dates and times
  • Picker View – Displays a spinning wheel of different options

Using an Alert Controller

Alerts and action sheets are both forms of presented view controller. They are managed through the UIAlertController class, a UIViewController subclass.

Alerts are minimal, and intentionally so: they are meant for simple, quick interactions or display of information.

An alert controller appears over an app’s user interface and can be customized by changing the following properties:

  • Title – Text that appears at the top of the alert controller, often in bold and a large font size.
  • Message – Text that appears underneath the title in a smaller font size.
  • Preferred style – Defines the appearance of the alert controller as an action sheet that appear at the bottom of the screen or as an alert that appear in the middle of the screen.
let alert = UIAlertController(title: "Warning", 
    message: "Zombies are loose!", preferredStyle: .alert)
let okAction = UIAlertAction(title: "OK", style: .default, 
    handler: { action -> Void in
    //Just dismiss the action sheet
})
alert.addAction(okAction)
self.present(alert, animated: true, completion: nil)

The simplest alert controller displays a single button that allows the user to dismiss it. However, you may want to give the user more than one option to choose and then respond differently depending on which button the user taps.

let alert = UIAlertController(title: "Warning", 
    message: "Zombies are loose!", preferredStyle: .alert)
let okAction = UIAlertAction(title: "OK", style: .default, 
    handler: { action -> Void in
        self.labelResult.text = "OK" 
})
let cancelAction = UIAlertAction(title: "Cancel", style: .cancel, 
    handler: { action -> Void in
        self.labelResult.text = "Cancel" 
})
let destroyAction = UIAlertAction(title: "Destroy", style: .destructive, 
    handler: { action -> Void in
        self.labelResult.text = "Destroy" 
})
alert.addAction(okAction)
alert.addAction(cancelAction)
alert.addAction(destroyAction)
self.present(alert, animated: true, completion: nil)

You can only add text fields on an alert controller that appears in the .alert preferred style. You cannot display text fields on an alert controller that appears as the .actionsheet preferred style.

let alert = UIAlertController(title: "Log In", 
    message: "Enter Password", preferredStyle: .alert)
alert.addTextField(configurationHandler: {(textField) in 
    textField.placeholder = "Password here" 
    textField.isSecureTextEntry = true
})

let okAction = UIAlertAction(title: "OK", style: .default, 
    handler: { action -> Void in
        let savedText = alert.textFields![0] as UITextField
        self.labelResult.text = savedText.text 
})
alert.addAction(okAction)
self.present(alert, animated: true, completion: nil)

Using an Action Sheet

An action sheet (UIAlertController style .actionSheet) may be considered the iOS equivalent of a menu; it consists primarily of buttons. On the iPhone, it slides up from the bottom of the screen; on the iPad, it appears as a popover.

let action = UIAlertController(
    title: "Choose New Layout", 
    message: nil, 
    preferredStyle: .actionSheet) 
action.addAction(UIAlertAction(title: "Cancel", style: .cancel))
func handler(_ act:UIAlertAction) {}

for s in ["3 by 3", "4 by 3", "4 by 4", "5 by 4", "5 by 5"] {
    action.addAction(UIAlertAction(title: s, style: .default, handler: handler))
}
self.present(action, animated: true)

On the iPad, an action sheet wants to be a popover. This means that a UIPopoverPresentationController will take charge of it. It will thus be incumbent upon you to provide something for the popover’s arrow to point to, or you’ll crash at runtime. For example:

self.present(action, animated: true)
if let pop = action.popoverPresentationController {
    let v = sender as! UIView 
    pop.sourceView = v 
    pop.sourceRect = v.bounds
}

Using a Date Picker

When an app needs the user to input a date and/or a time, it’s best to use the Date Picker to provide a list of valid options. By spinning different wheels on a Date Picker, users can pick days, dates, and times without typing a thing.

No matter what mode you choose for a Date Picker, its value is stored as an NSDate data type. To convert this NSDate data into text, you need a two-step process. First, you need to create a constant that represents a DateFormatter such as

let dateFormatter: DateFormatter = DateFormatter()

Second, you have to define the .dateStyle and .timeStyle properties to display the date and time. To define the date and time style, you need to set the .dateStyle and .timeStyle properties of the DateFormatter constant to one of the following options:

  • .none – Does not display the date or time
  • .short – Displays dates in this format "11/23/19" and times in this format "3:48 PM"
  • .medium – Displays dates in this format "Nov 23, 2019" and times in this format "3:48:21 PM"
  • .long – Displays dates in this format "November 23, 2019" and times in this format "3:48:21 PM EST"
  • .full – Displays dates in this format "Saturday, November 23, 2019 AD" and times in this format “3:48 PM Eastern Standard Time"
dateFormatter.dateStyle = .short
dateFormatter.timeStyle = .short

Finally, you have to retrieve the date/time currently displayed in the Date Picker. Since the date/time value stored in the Date Picker is in the NSDate data type, you have to convert its .date property to a string by using code such as this:

let selectedDate: String = dateFormatter.string(from: myDatePicker.date)

Creating a Custom Picker

The Date Picker is fine for letting the user choose dates and times, but many times you want to offer users a limited range of options that are not dates or times. In these situations, you need to use the standard picker object and also create the data to display in the picker.

To use a picker, you must define a delegate and data source. The delegate defines the file that contains functions that define how many columns of data to display (such as a day and month), the total number of options to display in the picker, and which data the user chose in the picker.

The data source defines the file that contains the data to display in the picker. This data can be an array or a database.

To see how to use a Picker View, follow these steps. Click the Main.storyboard file in the Navigator pane. Drag and drop a button and a Picker View anywhere on the view. Choose View > Assistant Editor > Show Assistant Editor. Xcode displays the Main.storyboard and ViewController.swift file side by side.

Edit the class ViewController line as follows:

class ViewController: UIViewController, UIPickerViewDataSource, 
    UIPickerViewDelegate {

Move the mouse pointer over the Picker View, hold down the Control key, and Ctrl-drag from the Picker View to the ViewController.swift file underneath the class ViewController line. Click in the Name text field, type myPicker, and click the Connect button.

Move the mouse pointer over the button, hold down the Control key, and Ctrl-drag from the button to the ViewController.swift file above the last curly bracket at the bottom of the file. Click in the Name text field, type buttonTapped, click in the Type popup menu and choose UIButton, and click the Connect button.

Edit the ViewController method as follows:

@IBOutlet var myPicker: UIDatePicker!
var pickerData: [String] = [String]()

override func viewDidLoad() {
    super.viewDidLoad()
    myPicker.delegate = self
    myPicker.dataSource = self
    pickerData = ["cat", "dog", "hamster", "lizard", "parrot", "goldfish"]
}

func numberOfComponents(in pickerView: UIPickerView) -> Int { 
    return 1
}

func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
    return pickerData.count 
}

func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component:
            Int) -> String? {
    return pickerData[row] 
}

@IBAction func buttonTapped(_ sender: UIButton) {
    let pickerIndex = myPicker.selectedRow(inComponent: 0) 
    let alert = UIAlertController(title: "Your Choice", 
        message: "\(pickerData[pickerIndex])", preferredStyle: .alert)
    let okAction = UIAlertAction(title: "OK", style: .default, 
        handler: { action -> Void in })
    alert.addAction(okAction)
    self.present(alert, animated: true, completion: nil) 
}

Displaying a Multiple-Component Picker View

If you look at the Date Picker, it displays separate wheels or components for the day, hour, minute, and whether it’s AM or PM.

For each component you want to display in a Picker View, you need to define a separate data source. Then you need to define how many total components you want the Picker View to display (such as two or three) along with displaying all components and storing the data in each component.

Click the Main.storyboard file in the Navigator pane. Xcode displays the single view. Click the Library icon to open the Object Library window. Drag and drop a button and a Picker View anywhere on the view.

Edit the class ViewController line as follows:

class ViewController: UIViewController, UIPickerViewDataSource, 
    UIPickerViewDelegate {

    @IBOutlet var myPicker: UIDatePicker!
    var componentOne: [String] = [String]() 
    var componentTwo: [String] = [String]() 
    var componentThree: [String] = [String]()

    override func viewDidLoad() {
        super.viewDidLoad()
        myPicker.delegate = self
        myPicker.dataSource = self
        componentOne = ["cat", "dog", "hamster", "lizard", 
            "parrot", "goldfish"]
        componentTwo = ["house", "apartment", "condo", "RV"]
        componentThree = ["indoor", "outdoor"]
    }

    func numberOfComponents(in pickerView: UIPickerView) -> Int { 
        return 3
    }

    func pickerView(_ pickerView: UIPickerView, 
        numberOfRowsInComponent component: Int) -> Int {
        switch component {
            case 0: return componentOne.count 
            case 1: return componentTwo.count
            default: return componentThree.count
        } 
    }

    func pickerView(_ pickerView: UIPickerView, 
            titleForRow row: Int, forComponent component: Int) -> String? { 
        switch component {
            case 0: return componentOne[row] 
            case 1: return componentTwo[row]
            default: return componentThree[row]
        } 
    }

    @IBAction func buttonTapped(_ sender: UIButton) {
        let petIndex = myPicker.selectedRow(inComponent: 0) 
        let homeIndex = myPicker.selectedRow(inComponent: 1) 
        let placeIndex = myPicker.selectedRow(inComponent: 2)
        let alert = UIAlertController(title: "Your Choice", 
            message: "\(componentOne[petIndex]) 
            \(componentTwo[homeIndex]) 
            \(componentThree[placeIndex])", preferredStyle: .alert)
        let okAction = UIAlertAction(title: "OK", style: .default, handler: { action -> Void in
        })
        alert.addAction(okAction)
        self.present(alert, animated: true, completion: nil) 
    }
}