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:
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
:
Property | Type | Description |
---|---|---|
delegate | UIPickerViewDelegate? | Delegate for handling selection and row customization. |
dataSource | UIPickerViewDataSource? | Data source for providing the number of components and rows. |
numberOfComponents | Int | The number of columns in the picker (read-only). |
showsSelectionIndicator | Bool | Whether the selection indicator (highlight) is visible (deprecated in iOS 13+). |
Example: Configuring Properties:
pickerView.delegate = self
pickerView.dataSource = self
Key Methods of UIPickerView
UIPickerView
provides methods to manage selections and reload data:
Method | Description |
---|---|
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:
pickerView.selectRow(2, inComponent: 0, animated: true)
UIPickerViewDataSource Protocol
The UIPickerViewDataSource
protocol provides data for the picker view:
Method | Description |
---|---|
numberOfComponents(in:) | Returns the number of components (columns). |
pickerView(_:numberOfRowsInComponent:) | Returns the number of rows in a component. |
Example:
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:
Method | Description |
---|---|
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:
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
- Set the
UITextField
’sinputView
to theUIPickerView
. - Add a
UIToolbar
as theinputAccessoryView
with a "Done” button to dismiss the picker. - Update the text field’s text when a row is selected.
Example:
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:
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:
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:
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
andaccessibilityTraits
for the picker and text field.swiftpickerView.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 customUITableView
for simpler selections.Handle Dynamic Data: Use
reloadAllComponents()
orreloadComponent(_:)
when data changes.
Troubleshooting
- Picker Not Showing: Ensure
delegate
anddataSource
are set, and the picker is added to the view hierarchy or set asinputView
. - 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
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
}
}