Implementing Drop-Down Selection Behavior in iOS Applications

Like radio buttons and checkboxes, iOS does not provide a native “drop-down” selection control like those commonly found on other platforms. Historically, it has been possible to emulate this type of control using a popover controller; however, until recently, this option was only available on iPads and iPhone 6+ devices in landscape orientation. Beginning with iOS 9, popovers can now be used on any iOS-enabled device, allowing developers to take advantage of this popular abstraction regardless of form factor or device orientation.

This article illustrates the implementation of a simple drop-down list that allows a user to specify a background color for a view. The list is accessed by tapping on the “Color” button in the application’s navigation bar:

Implementation

The main view in the application is managed by a class named SelectionViewController, which is presented within a navigation controller. The controller’s right bar button item, labeled “Color”, provides access to the drop-down list. In the viewDidLoad() method, SelectionViewController creates the bar button item and assigns the action handler method that will be called when the item is tapped. It also initializes an instance of ColorPickerViewController, the controller class it will use to present the list of color options to the user:

class SelectionViewController: UIViewController, UITableViewDelegate, UIPopoverPresentationControllerDelegate {
    let colorPickerViewController = ColorPickerViewController()

    override func viewDidLoad() {
        super.viewDidLoad()

        view.backgroundColor = UIColor.white

        title = "Selection View"

        navigationItem.rightBarButtonItem = UIBarButtonItem(title: "Color", style: UIBarButtonItemStyle.plain,
            target: self, action: #selector(showColorPicker))

        colorPickerViewController.modalPresentationStyle = .popover
        colorPickerViewController.tableView.delegate = self
    }

    func showColorPicker() {
        ...
    }

    func adaptivePresentationStyle(for controller: UIPresentationController) -> UIModalPresentationStyle {
        ...
    }

    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        ...
    }
}    

The showColorPicker() method is called in response to a tap on the “Color” button. It is defined as follows:

func showColorPicker() {
    let colorPickerPresentationController = colorPickerViewController.presentationController as! UIPopoverPresentationController

    colorPickerPresentationController.barButtonItem = navigationItem.rightBarButtonItem
    colorPickerPresentationController.backgroundColor = UIColor.white
    colorPickerPresentationController.delegate = self

    present(colorPickerViewController, animated: true, completion: nil)
}

The method first gets a reference to the color picker view controller’s presentation controller and casts it to an instance of UIPopoverPresentationController. Since the color picker view controller’s modal presentation style was set to .popover in viewDidLoad(), this is a valid cast.

Next, the method associates the color picker’s presentation controller with the selection view controller’s right bar button item (the “Color” button), ensuring that the popover’s arrow will point to the button. It then sets the presentation controller’s background color to white, so the arrow will match the table view when it is displayed in the popover. Lastly, it sets the selection view controller itself as the presentation controller’s delegate. This allows the controller to enforce that the color picker is presented as an actual popover, rather than the default presentation for the current device (which may be full-screen, if the user is running on a horizontally-constrained device such as an iPhone in portrait orientation, for example). It does this by returning .none from the adaptivePresentationStyle(for:) method of UIPopoverPresentationControllerDelegate:

func adaptivePresentationStyle(for controller: UIPresentationController) -> UIModalPresentationStyle {
    return .none
}

Finally, showColorPicker() presents the color picker view controller, causing the popover to appear on screen.

The ColorPickerViewController class provides the actual content for the drop-down selection view. It is implemented as follows:

class ColorPickerViewController: UITableViewController {
    override func loadView() {
        view = LMViewBuilder.view(withName: "ColorPickerViewController", owner: self, root: nil)
    }

    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)

        tableView.layoutIfNeeded()

        preferredContentSize = tableView.contentSize
    }
}

ColorPickerViewController uses the MarkupKit framework to help simplify its view construction. The markup for the view is defined as follows:

<LMTableView>
    <?sectionSelectionMode singleCheckmark?>

    <UITableViewCell textLabel.text="Red" value="#ff0000"/>
    <UITableViewCell textLabel.text="Yellow" value="#ffff00"/>
    <UITableViewCell textLabel.text="Green" value="#00ff00"/>
    <UITableViewCell textLabel.text="Blue" value="#0000ff"/>
    <UITableViewCell textLabel.text="Purple" value="#ff00ff"/>
</LMTableView>

It first declares an instance of LMTableView, a MarkupKit-provided subclass of UITableView that facilitates the definition of static table view content. It sets the selection mode of the table view’s first (and only) section to “singleCheckmark”, which ensures that only a single row will be selected at a time. Finally, it declares the cells that represent the color options. Using the value property added to UITableViewCell by MarkupKit, it associates a hex color value with each cell. This value will be used later to set the main view’s background color.

ColorPickerViewController overrides loadView() to load the view definition from markup. It also overrides viewWillAppear() to call layoutIfNeeded() on its table view, setting its own preferred content size to match the content size of the table view. This step is important, as it allows the popover to automatically resize itself to match the size of the table view’s content.

SelectionViewController implements tableView(_:didSelectRowAt:) to respond to user input on the color picker’s table view:

func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
    let cell = tableView.cellForRow(at: indexPath)

    view.backgroundColor = LMViewBuilder.colorValue(cell!.value as! String)

    dismiss(animated: true, completion: nil)
}

The code first obtains an instance to the selected cell and gets its associated hex-encoded color value. It then calls the colorValue() method of LMViewBuilder to translate the hex value to an instance of UIColor, which it sets as the value of the view’s background color. Finally, it dismisses the popover.

Summary

This article demonstrated the implementation of a simple drop-down list using a popover presentation controller. The complete source code for this example can be found here:

For more information, see the MarkupKit README.

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s