In iOS, I’ve seen a lot of examples of creating a UIPickerView with a hardcoded array of strings. This may be necessary if your data set is a list of strings (such as USA states). For numbers, there’s a much easier way to dynamically generate each row’s title.
To start off with, here’s a simple, programmatic UIPickerView implementation that includes the numbers (as strings) 0 through 9:
import UIKit
class ViewController: UIViewController, UIPickerViewDelegate, UIPickerViewDataSource {
override func viewDidLoad() {
super.viewDidLoad()
setupPicker()
}
var pickerView: UIPickerView = UIPickerView()
var pickerDataSource = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"]
func setupPicker() {
pickerView.dataSource = self
pickerView.delegate = self
pickerView.frame = CGRectMake(0, 0, view.frame.width, 300)
view.addSubview(pickerView)
}
func pickerView(pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
return pickerDataSource[row]
}
// columns count
func numberOfComponentsInPickerView(pickerView: UIPickerView) -> Int {
return 1
}
// rows count
func pickerView(pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
return pickerDataSource.count
}
}
The key piece is the pickerDataSource that gives us our data source. It is an array literal, and in a worst case programming scenario, one can imagine a very, very long hardcoded array of looping “0” – “9”. A long hardcoded array works, but is not optimal for many reasons.
The code above looks like this in the simulator:
We can improve and change this in many ways. We are going to update the code to operate without using a hardcoded array literal. Also, the user has to scroll up (or down) for a very, very long time before they hit the end of the picker. When their scrolling stops, we will move the position of the selected row back to the middle, so that when they scroll again, there is a long way to go before they get to the top (or bottom).
import UIKit
class ViewController: UIViewController, UIPickerViewDelegate, UIPickerViewDataSource {
var pickerView: UIPickerView = UIPickerView()
let pickerDataSize = 100_000
override func viewDidLoad() {
super.viewDidLoad()
setupPicker()
}
func setupPicker() {
pickerView.dataSource = self
pickerView.delegate = self
pickerView.frame = CGRectMake(0, 0, view.frame.width, 300)
view.addSubview(pickerView)
// set the picker to the middle of the long list
pickerView.selectRow(pickerDataSize/2, inComponent: 0, animated: false)
}
func pickerView(pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
return String(row % 10)
}
func pickerView(pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
// do something with the resulting selected row
// reset the picker to the middle of the long list
let position = pickerDataSize/2 + row
pickerView.selectRow(position, inComponent: 0, animated: false)
}
// columns count
func numberOfComponentsInPickerView(pickerView: UIPickerView) -> Int {
return 1
}
// rows count
func pickerView(pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
return pickerDataSize
}
}
In this 2nd code snippet for our ViewController, we have created a large number, pickerDataSize, that tells our device how many rows there are. The neat thing is that we don’t need to create an array of 100K items. When the UIPickerViewDelegate’s titleForRow method is called, it uses the row to dynamically return a string for that given row. Progress!
As a bonus, I went ahead and added a call to pickerView.selectRow in the didSelectRow function. When the user finishes selecting a row, this call will silently (animated: false) move the selected row back to the middle of the rows. This will maintain the illusion of infinite scrolling. Success!
Here we can see the updated, dynamic picker view: