[Updated to Swift3.0Ā 10/4/16 SJL]

This week, we’ll look at UIPickerView. Picker views are the spin-wheel like controls that often remind me of slot machines. Picker views, like table and collection views, are delegate based. You need to adopt a delegate and data source to get them to work properly. Picker views also take a lot of screen real estate that is not under developer control, which means your view can get crushed quickly. These two probably account for its lack of popularity. Most of what can be done in a picker view can be done far better in a table view. For those three reasons the fourth reason is also true: Nothing yells “newbie” like over-using picker views.
Professional developers do not use them as often as table views, but they work well for static multi-selection situations. When you have a few items to select from that will never change a picker view works well. We’ll set up a pizza order with a picker view today.
Setting Up the Basic Layout
Start with a new Swift single-view project in Xcode named SwiftPickerViewPizzaDemo. Go into the storyboard and make the following layout with two labels and a picker view:
You can also download the starterĀ fileĀ swiftpickerviewpizzademo_star.t.zipĀ which has the layout done for you.
We’ll need outlets for both the picker and the label. Open the assistant editor and control drag the lower label and the picker to the code. Name them myLabel and myPicker. I’m also adding //MARK
comments to my code to keep things organized. //MARK
is the objective-C equivalent of #pragma mark
. After cleaning up your code, it should look like this:
class ViewController: UIViewController { //MARK -Outlets and Properties @IBOutlet weak var myLabel: UILabel! @IBOutlet weak var myPicker: UIPickerView! //MARK - Instance Methods //MARK - Life Cycle override func viewDidLoad() { super.viewDidLoad() } //MARK - Delgates and Data Source }
Add Our Picker Data
The UIPickerView
uses an array of arrays to store the titles for the wheels. Each wheel is called a component. Each component gets it own array. I want to use both size and topping information so add the following just above the Outlets
mark.
let pickerData = [ ["10\"","14\"","18\"","24\""], ["Cheese","Pepperoni","Sausage","Veggie","BBQ Chicken"] ]
Adopt the Delegates and Data Source
Now we adopt the protocols UIPickerViewDataSource
and UIPickerViewDelegate
by listing them in the class declaration
class ViewController: UIViewController,UIPickerViewDataSource,UIPickerViewDelegate {
Xcode will give us an error about required methods we need to implement. Ignore it for now. Go down to viewDidLoad
and make sure we assign ourselves to the delegate:
//MARK - Life Cycle override func viewDidLoad() { super.viewDidLoad() myPicker.delegate = self myPicker.dataSource = self }
Implement the Picker Data Source Methods
Xcode is still complaining
Type 'ViewController' does not conform to protocol 'UIPickerViewDataSource'
Let’s get rid of this.Ā Add the data source required methods. These methods tell the picker the size of the array we set up in pickerData
. There are two methods. Add this below the Delegate and Data Source mark.
func numberOfComponents(in pickerView: UIPickerView) -> Int { return pickerData.count }
Line 2 specifies the number of components in the pickerData
array in the method. The second method looks at each component and gives the number of rows in the component:
func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int ) -> Int { return pickerData[component].count }
The method give us the specified component in component
. We access that element of the array, which is another array. We use the count
property on the second array, and return that value.
Implement the Required Delegate
Anyone who has ever used a UITableView
may find this vaguely familiar. In UITableView
we use sections and rows instead of components and rows of UIPickerView
, but it is the same two required methods. There is a presenting method similar to a table view’s cellForRowAtIndexPath
in a picker’s delegate:
func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int ) -> String? { return pickerData[component][row] }
Instead of a cell like its table view equivalent, the method returns the title string that goes in that row and component of the spinner.
Selecting a Row
We set up the required methods for the protocol. We have one more step. We need to get the selection. Add this to the delegate section of your code:
func pickerView( pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) { updateLabel() }
In our case all we want to do is to print out the results of the selection. We’ll need that twice once here and once in initialization. A method might be a good idea then. Add a method to update the label.
//MARK - Instance Methods func updateLabel(){ let size = pickerData[0][myPicker.selectedRowInComponent(0)] let topping = pickerData[1][myPicker.selectedRowInComponent(1)] pizzaLabel.text = "Pizza: " + size + " " + topping }
Line 2 and 3 use the selectedRowInComponent
method to access the selected array row.
Build and run. We start with this:
Dial an 18″ sausage pizza and you get this:
Selection and Cleaning up
While I wouldn’t call it a bug, there are three issues with his code I don’t like. It’s bad coding and documentation to use literal numbers for component
. What is better is to use constants. Add this under the pickerData
declaration:
let sizeComponent = 0 let toppingComponent = 1
In this enum
, we make the values literal integers that represent the components. This way if we change around our components while using them in multiple methods, we can control things from the one enum
. In Swift, the enumerations will need a little help though to get the value out. For aĀ enum
to give its raw value use the rawValue
property. Change updateLabel
to this:
func updateLabel(){ let size = pickerData[sizeComponent][myPicker.selectedRow(inComponent:sizeComponent)] let topping = pickerData[toppingComponent][myPicker.selectedRow(inComponent:toppingComponent)] myLabel.text = "Pizza: " + size + " " + topping }
While longer, this is much more readable and flexible code.
When we start, the pickerĀ does not reflect in the label. I’d like that to happen. I also want to start with a 18″ pizza, since a large pizza is the most ordered size. In viewDidLoad
, add the following code:
myPicker.selectRow(2, inComponent: sizeComponent, animated: false) updateLabel()
Line 2 uses another UIPickerView
method selectRow
to set the size component to 18″. I added the updateLabel
method after this. What shows on the picker at initialization shows up in the label.
Build and run, and we get the behavior we want.
Making the UI Pretty With Translucency.
That is all the code for making a UIPickerView
, however the storyboard still doesn’t look like much. I’m going to do this with Auto Layout off, but you can certainly leave it on. If you want it off, go to the file inspector and turn it off. For the background I used this graphic:

Click the image above and when you have the full size one, right-click or save the image. Drag the saved file into your project file list. Make sure copy item is checked in the dialog box. Once you add the image to your project, in the lower right of the utilities pane you will find a little film clip icon, which is the media library. Click the film clip icon, and then drag the pizza image onto the storyboard view. It will cover everything else.Ā Look at the document outline. If it is not visible click the small icon on the lower left of the storyboard.Ā You should see this:

Drag the photo to the top of the outline in the view like this, so it is at the bottom of the view stack:
Now we can see the controls — sort of. We need a background behind the labels, and need to see through the picker.
Change the background of the picker controller to a white with a 50% alpha. I use the crayon mode and use Snow at 50%
We’ll make a background layer for the two labels. Drag outĀ a view (not a view controller) onto the bottom of the photo and size it so it fits across the view. Set the backgroundĀ on the blank view once again to Snow at 50% alpha.
Drag the Labels into the view on the bottom and place the labels in the right spots. When you drop a view into another view it becomes a subview of that view.
Drag the view, not the labels, up above the picker.Ā Everything moves up to the new place.
Build and Run:
You have a better looking UI.
Picker Views: Fastest Way to a One Star Review
Picker views have one purpose: When you have a few items which never change to pick from. For the demo above with a few sizes and pizza toppings, they work fine. For more than 15 items per component, use a table view. For more than three components, these get ugly since there is not enough space. Picker views above these limits are bad user experiences. They take too long to select items quickly for users. They are not good at dynamic selection, and although there is some formatting you can do, they have severe limitations in formatting. My suggestion is for most places above the limits I just mentioned, use a table view. Nothing says you don’t know what you are doing quite like over using picker views. The few who buy your app will give you a one-star review, Everyone else will stay away.
The Whole Code:
You can find the finished project, whereĀ I did use auto layout, here: Ā swiftpickerviewpizzademoĀ below you can find the code.
// // ViewController.swift // SwiftPickerViewPizzaDemo // // Created by Steven Lipton on 10/4/16. // An update of the earlier 9/18/14 article to Swift 3.0 // Copyright Ā© 2016 Steven Lipton. All rights reserved. // import UIKit class ViewController: UIViewController,UIPickerViewDataSource,UIPickerViewDelegate { @IBOutlet weak var myPicker: UIPickerView! @IBOutlet weak var pizzaLabel: UILabel! let pickerData = [ ["10\"","14\"","18\"","24\""], ["Cheese","Pepperoni","Sausage","Veggie","BBQ Chicken"] ] let sizeComponent = 0 let toppingComponent = 1 //MARK: - Picker View Data Sources and Delegates func numberOfComponents(in pickerView: UIPickerView) -> Int { return pickerData.count } func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int { return pickerData[component].count } func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int ) -> String? { return pickerData[component][row] } func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) { updateLabel() } //MARK: - Instance Methods func updateLabel(){ let size = pickerData[sizeComponent][myPicker.selectedRow(inComponent: sizeComponent)] let topping = pickerData[toppingComponent][myPicker.selectedRow(inComponent: toppingComponent)] pizzaLabel.text = "Pizza: " + size + " " + topping } override func viewDidLoad() { super.viewDidLoad() myPicker.delegate = self myPicker.dataSource = self myPicker.selectRow(2, inComponent:sizeComponent, animated: false) updateLabel() } }
Leave a Reply