UIPickerView - Loop the data

ThomasGulli picture ThomasGulli · Sep 26, 2014 · Viewed 11.1k times · Source

I'm currently developing an app using Swift where I use a UIPickerView, see below for image. Currently the UIPickerView stops when the user has scrolled to the last data, but i want it no never stop. I want it to be possible to start from the first data by just keep on scrolling when you're at the bottom. Or scroll up when at the top. I want it to be like Apple's Countdown where you can drag up or down whenever you want.

How I want it to be:

enter image description here

How it currently is:

enter image description here

Answer

Nate Cook picture Nate Cook · Sep 26, 2014

There will be four steps here -- we'll set up some constants to hold the picker view data and a bit of configuration, then we'll add UIPickerViewDataSource and UIPickerViewDelegate methods, and we'll end with the viewDidLoad initialization.

First, the configuration:

private let pickerViewData = Array(0...59)     // contents will be 0, 1, 2, 3...59, change to whatever you want
private let pickerViewRows = 10_000            // any big number
private let pickerViewMiddle = ((pickerViewRows / pickerViewData.count) / 2) * pickerViewData.count

Note the pickerViewMiddle constant -- it's calculated to make it very easy to get our current value from the row offset. On to the data source -- we really only need to provide a title for each row, but we'll add a helper method to convert a row number to a value from the array:

extension ViewController : UIPickerViewDataSource {
    func valueForRow(row: Int) -> Int {
        // the rows repeat every `pickerViewData.count` items
        return pickerViewData[row % pickerViewData.count]
    }

    func rowForValue(value: Int) -> Int? {
        if let valueIndex = find(pickerViewData, value) {
            return pickerViewMiddle + value
        }
        return nil
    }

    func pickerView(pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String! {
        return "\(valueForRow(row))"
    }
}

And finally we'll set up the delegate:

extension ViewController : UIPickerViewDelegate {
    func numberOfComponentsInPickerView(pickerView: UIPickerView) -> Int {
        return 1
    }

    func pickerView(pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
        return pickerViewRows
    }

    // whenever the picker view comes to rest, we'll jump back to
    // the row with the current value that is closest to the middle
    func pickerView(pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
        let newRow = pickerViewMiddle + (row % pickerViewData.count)
        pickerView.selectRow(newRow, inComponent: 0, animated: false)
        println("Resetting row to \(newRow)")
    }

}

To initialize, in your viewDidLoad set the delegate and data source and then move to the correct row in the middle of your picker:

self.picker.delegate = self
self.picker.dataSource = self
let initialValue = 0
if let row = rowForValue(initialValue) {
    self.picker.selectRow(row, inComponent: 0, animated: false)
}
// or if you just want to start in the middle:
// self.picker.selectRow(pickerViewMiddle, inComponent: 0, animated: false)