Skip to content

UIPickerView

UIPickerView is a UIKit control that displays a spinning wheel interface for selecting one or more values from a set of options. It is a subclass of UIView and is commonly used for selecting items like numbers, categories, or custom data. This document covers the key properties, methods, and usage of UIPickerView, including a section on using it as an input method for a UITextField, along with examples and best practices.

Overview of UIPickerView

UIPickerView presents a drum-like interface with one or more columns (components), each containing rows of selectable items. It is highly customizable, allowing developers to provide data and handle selections through delegate and data source protocols. It is ideal for scenarios where users need to choose from a predefined list, such as selecting a country or setting a value.

Creating a UIPickerView

You can create a UIPickerView programmatically or via Interface Builder (storyboards/xibs).

Programmatic Example:

swift
import UIKit

let pickerView = UIPickerView()
pickerView.delegate = self
pickerView.dataSource = self
view.addSubview(pickerView)

Key Properties of UIPickerView

Below are the most commonly used properties of UIPickerView:

PropertyTypeDescription
delegateUIPickerViewDelegate?Delegate for handling selection and row customization.
dataSourceUIPickerViewDataSource?Data source for providing the number of components and rows.
numberOfComponentsIntThe number of columns in the picker (read-only).
showsSelectionIndicatorBoolWhether the selection indicator (highlight) is visible (deprecated in iOS 13+).

Example: Configuring Properties:

swift
pickerView.delegate = self
pickerView.dataSource = self

Key Methods of UIPickerView

UIPickerView provides methods to manage selections and reload data:

MethodDescription
numberOfRows(inComponent:)Returns the number of rows in a specific component (data source).
selectRow(_:inComponent:animated:)Selects a row in a component, optionally animating.
selectedRow(inComponent:)Returns the index of the selected row in a component.
reloadAllComponents()Reloads all components.
reloadComponent(_:)Reloads a specific component.

Example: Selecting a Row:

swift
pickerView.selectRow(2, inComponent: 0, animated: true)

UIPickerViewDataSource Protocol

The UIPickerViewDataSource protocol provides data for the picker view:

MethodDescription
numberOfComponents(in:)Returns the number of components (columns).
pickerView(_:numberOfRowsInComponent:)Returns the number of rows in a component.

Example:

swift
extension ViewController: UIPickerViewDataSource {
    func numberOfComponents(in pickerView: UIPickerView) -> Int {
        return 1
    }
    
    func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
        return data.count
    }
}

UIPickerViewDelegate Protocol

The UIPickerViewDelegate protocol handles row customization and selection:

MethodDescription
pickerView(_:titleForRow:forComponent:)Returns the title for a row.
pickerView(_:attributedTitleForRow:forComponent:)Returns an attributed string for a row.
pickerView(_:viewForRow:forComponent:reusing:)Returns a custom view for a row.
pickerView(_:didSelectRow:inComponent:)Called when a row is selected.
pickerView(_:widthForComponent:)Returns the width of a component.
pickerView(_:rowHeightForComponent:)Returns the height of rows in a component.

Example:

swift
extension ViewController: UIPickerViewDelegate {
    func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
        return data[row]
    }
    
    func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
        print("Selected: \(data[row])")
    }
}

Using a UIPickerView as an Input Method for UITextField

UIPickerView can be used as the inputAccessoryView for a UITextField, allowing users to select a value from the picker instead of typing. This is useful for ensuring valid input from a predefined set of options.

Steps

  1. Set the UITextField’s inputView to the UIPickerView.
  2. Add a UIToolbar as the inputAccessoryView with a "Done” button to dismiss the picker.
  3. Update the text field’s text when a row is selected.

Example:

swift
import UIKit

class ViewController: UIViewController, UIPickerViewDataSource, UIPickerViewDelegate {
    let textField = UITextField()
    let pickerView = UIPickerView()
    let data = ["Option 1", "Option 2", "Option 3", "Option 4"]

    override func viewDidLoad() {
        super.viewDidLoad()
        view.backgroundColor = .systemBackground
        
        // Configure text field
        textField.placeholder = "Select an option"
        textField.borderStyle = .roundedRect
        textField.inputView = pickerView
        
        // Configure picker view
        pickerView.delegate = self
        pickerView.dataSource = self
        
        // Add toolbar
        let toolbar = UIToolbar()
        toolbar.sizeToFit()
        let doneButton = UIBarButtonItem(barButtonSystemItem: .done, target: self, action: #selector(dismissPicker))
        toolbar.setItems([UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: nil, action: nil), doneButton], animated: false)
        textField.inputAccessoryView = toolbar
        
        view.addSubview(textField)
        
        // Auto Layout with SnapKit
        textField.snp.makeConstraints { make in
            make.center.equalToSuperview()
            make.width.equalTo(200)
        }
        
        // Set initial selection
        textField.text = data[0]
        pickerView.selectRow(0, inComponent: 0, animated: false)
    }
    
    @objc func dismissPicker() {
        textField.resignFirstResponder()
    }
    
    // UIPickerViewDataSource
    func numberOfComponents(in pickerView: UIPickerView) -> Int {
        return 1
    }
    
    func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
        return data.count
    }
    
    // UIPickerViewDelegate
    func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
        return data[row]
    }
    
    func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
        textField.text = data[row]
    }
}

Notes:

  • Ensure the picker’s delegate and data source are set.
  • Use the delegate’s didSelectRow method to update the text field’s text.
  • The toolbar improves usability by allowing users to dismiss the picker.
  • Test the picker’s accessibility with VoiceOver.

Customizing UIPickerView

Custom Row Views

Use pickerView(_:viewForRow:forComponent:reusing:) to provide custom views (e.g., labels with custom fonts or images).

Example:

swift
func pickerView(_ pickerView: UIPickerView, viewForRow row: Int, forComponent component: Int, reusing view: UIView?) -> UIView {
    let label = (view as? UILabel) ?? UILabel()
    label.text = data[row]
    label.textAlignment = .center
    label.font = .systemFont(ofSize: 18, weight: .bold)
    label.textColor = .systemBlue
    return label
}

Adjusting Component Width and Row Height

Customize the appearance using delegate methods.

Example:

swift
func pickerView(_ pickerView: UIPickerView, widthForComponent component: Int) -> CGFloat {
    return 150
}

func pickerView(_ pickerView: UIPickerView, rowHeightForComponent component: Int) -> CGFloat {
    return 40
}

Multiple Components

Support multiple components for complex selections (e.g., hours and minutes).

Example:

swift
let hours = Array(0...23)
let minutes = Array(0...59)

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

func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
    return component == 0 ? hours.count : minutes.count
}

func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
    return component == 0 ? "\(hours[row]) hr" : "\(minutes[row]) min"
}

Best Practices

  • Use Clear Data: Ensure options are concise and meaningful to users.

  • Support Accessibility: Set accessibilityLabel and accessibilityTraits for the picker and text field.

    swift
    pickerView.accessibilityLabel = "Option selector"
    pickerView.accessibilityTraits = .adjustable
  • Use Auto Layout: Set translatesAutoresizingMaskIntoConstraints = false and use constraints (e.g., with SnapKit) for the picker or text field.

  • Test Across Devices: Verify picker behavior and appearance on different screen sizes and iOS versions.

  • Consider Alternatives: For modern apps, consider UIDatePicker with .compact style or a custom UITableView for simpler selections.

  • Handle Dynamic Data: Use reloadAllComponents() or reloadComponent(_:) when data changes.

Troubleshooting

  • Picker Not Showing: Ensure delegate and dataSource are set, and the picker is added to the view hierarchy or set as inputView.
  • No Selection Feedback: Verify didSelectRow is implemented in the delegate.
  • Text Field Not Updating: Check the delegate’s didSelectRow logic and ensure the text field is accessible.
  • Layout Issues: Ensure constraints provide enough space for the picker or text field.
  • Accessibility Issues: Test with VoiceOver to ensure proper feedback.

Example: Complete UIPickerView Setup

swift
import UIKit
import SnapKit

class ViewController: UIViewController, UIPickerViewDataSource, UIPickerViewDelegate {
    let pickerView = UIPickerView()
    let textField = UITextField()
    let data = ["Red", "Green", "Blue", "Yellow"]

    override func viewDidLoad() {
        super.viewDidLoad()
        view.backgroundColor = .systemBackground
        
        // Configure text field
        textField.placeholder = "Select a color"
        textField.borderStyle = .roundedRect
        textField.inputView = pickerView
        
        // Configure picker view
        pickerView.delegate = self
        pickerView.dataSource = self
        pickerView.accessibilityLabel = "Color selector"
        
        // Add toolbar
        let toolbar = UIToolbar()
        toolbar.sizeToFit()
        let doneButton = UIBarButtonItem(barButtonSystemItem: .done, target: self, action: #selector(dismissPicker))
        toolbar.setItems([UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: nil, action: nil), doneButton], animated: false)
        textField.inputAccessoryView = toolbar
        
        view.addSubview(textField)
        
        // Auto Layout with SnapKit
        textField.snp.makeConstraints { make in
            make.center.equalToSuperview()
            make.width.equalTo(200)
        }
        
        // Set initial selection
        textField.text = data[0]
        pickerView.selectRow(0, inComponent: 0, animated: false)
    }
    
    @objc func dismissPicker() {
        textField.resignFirstResponder()
    }
    
    // UIPickerViewDataSource
    func numberOfComponents(in pickerView: UIPickerView) -> Int {
        return 1
    }
    
    func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
        return data.count
    }
    
    // UIPickerViewDelegate
    func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
        return data[row]
    }
    
    func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
        textField.text = data[row]
    }
    
    func pickerView(_ pickerView: UIPickerView, viewForRow row: Int, forComponent component: Int, reusing view: UIView?) -> UIView {
        let label = (view as? UILabel) ?? UILabel()
        label.text = data[row]
        label.textAlignment = .center
        label.font = .systemFont(ofSize: 18, weight: .medium)
        label.textColor = .systemBlue
        return label
    }
}

Resources

Released under the MIT License.