Make App Pie

Training for Developers and Artists

Swift Swift: Implementing Picker Views

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

Screenshot 2014-09-17 06.45.53
Example of A UiPickerView In action

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:

Screenshot 2014-09-17 06.47.04

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:

Screenshot 2014-09-18 05.40.01

Dial an 18″ sausage pizza and you get this:

Screenshot 2014-09-18 05.39.49

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:

Photo Sep 14, 7 40 59 PM_small
Click the thumbnail to get the full size image.

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:

The bottom of the outline is the top of the view stack.
The bottom of the outline is the top of the view stack.

Drag the photo to the top of the outline in the view like this, so it is at the bottom of the view stack:

Screenshot 2014-09-18 06.44.44

Now we can see the controls — sort of. We need a background behind the labels, and need to see through the picker.

Screenshot 2014-09-18 06.44.53

Change the background of the picker controller to a white with a 50% alpha. I use the crayon mode and use Snow at 50%

Screenshot 2014-09-18 06.45.27

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.

Screenshot 2014-09-18 06.47.37

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.

Screenshot 2014-09-18 06.49.50

Drag the view, not the labels, up above the picker.Ā  Everything moves up to the new place.

Screenshot 2014-09-18 06.50.34

Build and Run:

Screenshot 2014-09-18 06.52.13

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()
    }
    
    
}

38 responses to “Swift Swift: Implementing Picker Views”

  1. Thanks for the Tuto.

    I have a easy question and a difficult one :

    1) What do you mean by :

    I use the crayon mode and use Snow at 50%

    2)

    I have a problem with adding a photo. I want the photo to be view for iPhone and iPad sized with the height of the device. So I added an image view located at the top left of the view and added a constraint “equal heights”. The results is ok for iPad, but not the iPhone.

    Thanks!

    1. 1)What I mean is the color picker crayon mode snow (pretty much white lower right corner) at 50% alpha.
      crayon color picker

      2) This question came up earlier today. I’m working on it and will have an answer soon.

      1. Thanks a lot for your answer. 1) I did not know about the Colors crayon mode. I learned something. :-) I believe 2) is doable through the storyboard, but I did not still succeed. In fact, it is not working for iPhone and iPad either. The image is shrinked or distorted. I thought the constraint “Equal Heights” will work, but this is not the case. There is something missing.

  2. I could “solve” the shrink and the distortion of the image by selecting “Top Left” in View Mode. The image is not shrinked or distorted for iPhone or IPad anymore, but, even with the constraint ā€œEqual Heightsā€, the image is not displayed fully in height.

    1. I worked on this for about an hour with no better luck than you had. I think we might want to look into size classes, and I think this is a big enough issue that it will become the next blog entry next week, after I do a bit more research.

      1. Ok. Thanks. It should be simple but this is not the case.
        I am now struggle with how to change font, color, size for the text in the picker view and with and height for the picker view it self.

      2. Those are all found on the UIPickerViewDelegate. You’ll need to use some attributed text. You can check the docs for more information. I could write a blog entry on attributed text (and I probably will too) You can’t change the size of the picker view as a whole but rows and components sizes. get changed in the delegate as well.

        Check here for more information: https://developer.apple.com/library/IOS/documentation/UIKit/Reference/UIPickerViewDelegate_Protocol/index.html

      3. You got me so intrigued by this I couldn’t resist. I’m writing up the next blog entry now on this topic, so I’ll show you example of all of that in a couple of days. If this gets written quickly, I’ll maybe even post today.

      4. I mentioned attributed strings. The new blog post on that is now here:

    1. Finishing up part two now. These delegates are a pain.

    2. Okay the answers you seek are now posted here:

      Hope that helps

      1. Fantastic!

  3. […] posted on UIPickerViews before, but in the comments for that post, I got a question that needed an answer so big, it was worth a […]

  4. Did you get a chance to find a way to fit a background picture into a view that could work for all devices with the constraint “equal-weigth” without being distorted ?

    1. I’m afraid I haven’t had time. I probaly will exporre it in a while. You might want to explore size classes as a bit of a help.

      1. Ok. I will explore with size classes. If I found a solution, even programmatically, I will tell you.

  5. First off, THANK YOU! There is very little info about picker views and their components in Swift.
    I’ve been able to tweak your code so far to get what I want.

    Here is where I’m stuck:
    I want the user to be able to choose which components are loaded into the Picker view.

    For example: I have an array
    var myArray [
    [“a1”, “b1”, “c1”],
    [“a2”, “b2”, “c2”],
    [“a3”, “b3”, “c3”],
    [“a4”, “b4”, “c4”],
    [“a5”, “a5”, “a5”]
    ]
    And I want the user to be able to select 1, 2 up to 5 components. (eg. Have only the 1st, 2nd and 5th picker wheels showing, etc.)

    I have tried manipulatung the numberOfComponentsInPickerView but I can’t figure out the correct syntax.

    Or should I approach it by adding to the array based on user choices? In that case, I know how to append an individual section of the the array (myArray[2].append), but can’t figure out how to add/remove the sub-arrays.

    Hope this made sense. If you have any suggestions, please let me know. :)

    1. I’ve found that to be a popular question. Pickers views are notoriously static. I’ve never gotten that to work. Only thing I can think of trying is to completely unload the view or a subview in the viewcontroller that contains the picker, and reload that view.

      May have to try a subview myself someday. if you do. let me know how it works.

      1. Argh! Not good if you don’t know. :-o lol
        I’ll try the load/unload the view and see what I can get.
        I’ll defi post if I can get it working. ;-)
        Thanks!

  6. How would you go about setting up the Picker like a keyboard with swift for a textfield? This has been driving me crazy!! Basic use case would be a text field that when you tap it the picker comes up from the bottom instead of the keyboard. You select a value and it releases the picker. This should be simple, but I am pulling out my hair with this!!!

    1. hmmm. Good question. I haven’t thought of it before. probably needs a subview with the picker in it, but I’m not exactly sure how to implement it.

  7. Great tutorial, very helpful! Thank you!
    I just have one question, how would you go about setting up a second picker in the same view controller? When i tried doing the same procedure but with the new outlet and new data for the second picker, the data for the first picker keeps showing up in the second picker when I run my application. Any suggestions?

    1. They should be inline like the date picker. you would have another component to the same picker. Otherwise you need a completely seperate delegate.

  8. […] is doing. I’ll assume you know about UIPickerViewĀ and its delegates.Ā  If you don’t, read this first. The Objective C file is […]

  9. I got everything to work perfectly – thank you for the tutorial. I am trying to display two picker’s on one view with different data (one with height and one with weight). I’m new to swift but your tutorials have been so helpful and easy to follow. Any advice you have is much appreciated.

    1. You’ll need to make two delegates. Or make them two components of one picker.

  10. […] use a picker for our pie choices. If you are interested in UIPickerView, see my lesson on Implementing UIPickerView for details of what we are doing here. Add the delegate and data source to the class […]

  11. thank you, it helps me a lot!

  12. Hi, I’ve got a problem with de row selected, this is my code:

    func pickerView(pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
    mypicker.selectedRowInComponent(23)
    }

    But it’s not working, I donĀ“t know why, any help?

    1. What are you trying to do? This code makes no sense.

      1. I’ve got a pickerview and I want to keep selected one row, the row with the id number 23

      2. If you want a constant, Then I’d suggest not using a picker view.

  13. […] are are series of wheel like scrolling objects. For more on the basics, you can read this article on them. They are made of series of components which contain rows of strings. Picker views use delegates […]

  14. Thanks. Also for the 1-star remark after I struggled thru this. Ended up with a slight different updateLabel function though.
    let size = pickerData[0][myPicker.selectedRowInComponent(0)] didn’t work. Same for topping.
    This did the work for me: let size = pickerData[0][myPicker.selectedRow(inComponent: 0)] and let topping = pickerData[1][myPicker.selectedRow(inComponent: 1)]
    Used Xcode Version 8.3.2 (8E2002)

    1. I’ll look into that. Probably will have a playground demoing it working, however it works.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.

%d bloggers like this: