Building a picker view with UIKit
A UIPickerView is the spinning control used to choose a value from a list of options. It remains useful in iOS apps for settings, locations, quantities and compact forms where a standard menu would take up too much space. Understanding how to create a picker view in UIKit starts with recognising that the control contains vertical wheels, with each wheel representing a component and each selectable entry representing a row.
In my own UIKit projects, I treat a picker as three separate pieces: the data, the callbacks that supply the data, and the app state that stores the result. This separation makes the control easier to test and allows the same selection logic to appear in a storyboard-based app or one built entirely in Swift. It also prepares the picker for Dynamic Type, VoiceOver and the quality expectations of iPhone users in Australia.
Understanding the picker model
A picker view can contain one component or several. A single wheel works well for choosing a city, while multiple wheels can represent separate values such as an hour, minute and Australian time zone. Each component has its own row count, and every row belongs to exactly one component.
Two protocols manage the picker. UIPickerViewDataSource tells UIKit how many components and rows exist, while UIPickerViewDelegate supplies visible titles and receives selection events. A view controller can conform to both, as shown later, even when the data source and delegate need to live in different objects for a larger app.
For an Australian example, the available cities might include Adelaide, Brisbane, Darwin, Melbourne, Perth and Sydney. This would fit a delivery form for an Australian business choosing between metropolitan and regional areas. A smaller subset can then appear in the wheel rather than forcing the user to scroll through a long list.
Preparing the data and screen
Begin with a simple array of display values and give the picker view outlets in Interface Builder. The implementation below assumes a UIPickerView and a UILabel connected from a storyboard or XIB. The data source and delegate are assigned immediately, allowing UIKit to request the picker contents once the view has loaded.
import UIKit
final class CityPickerViewController: UIViewController,
UIPickerViewDataSource,
UIPickerViewDelegate {
@IBOutlet private weak var pickerView: UIPickerView!
@IBOutlet private weak var selectionLabel: UILabel!
private let cities = [
"Adelaide",
"Brisbane",
"Darwin",
"Melbourne",
"Perth",
"Sydney"
]
override func viewDidLoad() {
super.viewDidLoad()
pickerView.dataSource = self
pickerView.delegate = self
}
}
When I need another perspective on UIKit project structure, I revisit practical iOS development notes before changing the implementation. Keeping the displayed values in one array prevents the data source, delegate and interface from drifting apart.
The surrounding copy should also suit Australian users. “Choose suburb” is familiar local wording, dates can follow the 27/04/2026 format, and business schedules may use 24-hour time. Prices should remain in Australian dollars, with GST shown clearly where relevant. Australian English spellings such as “favourite” and “colour” should also appear in interface labels.
Implementing the data source
UIKit asks the data source for the picker’s structure before drawing it. Implementing numberOfComponents(in:) makes the number of wheels explicit, while numberOfRowsInComponent controls how many choices appear in each wheel. The title method then maps a row index to a value from the model.
func numberOfComponents(in pickerView: UIPickerView) -> Int {
1
}
func pickerView(
_ pickerView: UIPickerView,
numberOfRowsInComponent component: Int
) -> Int {
cities.count
}
func pickerView(
_ pickerView: UIPickerView,
titleForRow row: Int,
forComponent component: Int
) -> String? {
cities[row]
}
The component index starts at zero, so the first wheel is component 0. The same is true for rows. These methods should never return a title for an index outside the available data because UIKit may request rows while updating its layout.
For a two-wheel picker, return 2 from numberOfComponents(in:) and check the component inside each callback. Use separate arrays for each wheel, such as Australian states and available cities. If one array depends on another, call reloadAllComponents() after the dependent data changes and restore the previous selection.
Responding to selection changes
The delegate’s didSelectRow method runs when the user moves a wheel to a new row. Use it to update the visible selection and any model value required by the rest of the screen. The callback provides both the selected row and its component.
func pickerView(
_ pickerView: UIPickerView,
didSelectRow row: Int,
inComponent component: Int
) {
selectionLabel.text = cities[row]
}
The initial selection needs separate handling because didSelectRow is not guaranteed to run when the screen appears. Select the first row without animation and set the label directly in viewDidLoad(). This keeps the label and picker aligned from the first frame.
pickerView.selectRow(0, inComponent: 0, animated: false)
selectionLabel.text = cities[0]
Store an identifier with the selection when the display title is not enough. A city picker might save sydney rather than “Sydney”, making it safer to pass between services or compare with existing records. The label can still show the friendly name, while the model retains a stable value.
Testing and refining the experience
Test the picker on both compact and large iPhone models in the Xcode simulator. Check that the wheels have enough space, that neighbouring controls do not overlap and that the selected value remains visible with larger Dynamic Type sizes. Rotate the device as well, since a picker that works in portrait can behave differently beside a form in landscape.
Give the selection label a clear semantic style and let it scale with the user’s preferred text size.
selectionLabel.font = .preferredFont(forTextStyle: .headline)
selectionLabel.adjustsFontForContentSizeCategory = true
selectionLabel.numberOfLines = 0
selectionLabel.textColor = .label
pickerView.accessibilityLabel = "Choose an Australian city"
Avoid relying on undocumented subviews to change the picker’s internal text appearance. Those implementation details can change between iOS versions. Styling the surrounding label and providing a clear accessibility label produces a more stable result.
VoiceOver should announce the picker and its current value, and the final selection should not rely on colour alone. Since iPhone remains a mainstream device in the Australian market, these accessibility checks are part of delivering a dependable app rather than an optional refinement.
A picker that loads quickly, handles its initial state and survives changes to its data can now be integrated into a real form. It can also become the inputView of a text field, which turns the picker into a focused selection sheet and keeps the surrounding screen uncluttered.
Open a fresh UIKit project in Xcode, add a UIPickerView to the main screen and connect it using the city array and delegate methods above.