Oct 14, 2018: Updated by Adrien Villez for the latest Xcode 10!
In this tutorial, we’ll walk though a basic implementation example of the UIPickerView which is a UI element that can be used to make a selection from multiple choices (similar to what a dropdown does for a webpage).
Steps:
- Creating the Xcode project
- Adding and Connecting the UIPickerView
- Creating the Data
- Connecting the Data
- Detecting UIPickerView selections
- Conclusion
We’ll start with creating an Xcode single view application project to demonstrate the UIPickerView.
For the project settings, you can enter any sort of details since this will just be for demo purposes. This is what I’ve got:
Since this article is not covering size classes and auto-layout, we want to make sure that our UIPickerView is well positioned in the Simulator. To accomplish this, go to the newly created Main.storyboard file and disable [√] Use Auto Layout and select iPhone
2. Adding and Connecting the UIPickerView
From the file navigator on the left, select Main.storyboard and the editor will change to a design view where you can see what your view will look like.
On the right hand side, you’ve got your Attribute Inspector View (top half) and your Library View (bottom half). If you don’t see this right hand pane, click on the icon in the upper right corner to toggle the pane.
Make sure you’ve selected the Objects library tab and type in “pickerview”.
The list of elements will filter down to the UIPickerView element.
Once you’ve found it, drag it onto your view.
Now that we’ve got the Picker View element on the view in the storyboard, we’ll need to expose this element to be accessible via code in the ViewController.
Click the Assistant Editor button and make sure that the storyboard is in the left pane and that ViewController.swift is in the right.
Then hold down control and click the UIPickerView element in the storyboard and drag your mouse over to the right side. Drop it in between the class ViewController and override func viewDidLoad.
A small dialog will pop up to ask you for a name for this IBOutlet property. Just name it “picker”.
After doing that, your ViewController.swift file will look like this:
class ViewController: UIViewController { @IBOutlet weak var picker: UIPickerView! override func viewDidLoad() { super.viewDidLoad() // Do any additional setup after loading the view, typically from a nib. } ...
Now we can reference that Picker View element from code in the ViewController using “self.picker”. Run your app now to make sure it doesn’t crash.
Keep in mind that if you delete this IBOutlet property, you also have to break the connection from the storyboard or else your app will crash.
You can break the connection by right-clicking the Picker View element from the storyboard and looking for the connection to the deleted property and just clicking the “x” next to that connection.
Let’s create the data that we’re going display in the Picker control.
In ViewController.swift, add the following code.
@IBOutlet weak var picker: UIPickerView! var pickerData: [String] = [String]() override func viewDidLoad() { super.viewDidLoad() // Do any additional setup after loading the view, typically from a nib. pickerData = ["Item 1", "Item 2", "Item 3", "Item 4", "Item 5", "Item 6"] } ...
In Line 3, we’re declaring a new Array instance variable to store the list of data. By declaring it here as an instance variable, we can access this variable from any method in this class and the variable will hold its value for the duration of the objects lifetime.
Now that the pickerData was initialized, we will add some data in Line 9, which is part of the viewDidLoad method.
Now that we’ve got the UIPickerView element in our storyboard and made it accessible from code, we can add the code to connect the data to it!
Go to ViewController.swift and make this class conform to the UIPickerViewDelegate and UIPickerViewDataSource protocols.
Line 1 below is what you want to modify. By doing this, we’re saying that the ViewController class conforms to the appropriate “rules” which allows it to be a data source for the UIPickerView class and allows it to handle events raised by the UIPickerView class. Also, this code actually sets this ViewController instance as the delegate (line 13) and datasource (line 13) of the Picker View we added to the storyboard.
class ViewController: UIViewController, UIPickerViewDelegate, UIPickerViewDataSource { @IBOutlet weak var picker: UIPickerView! var pickerData: [String] = [String]() override func viewDidLoad() { super.viewDidLoad() // Do any additional setup after loading the view, typically from a nib. // Connect data: self.picker.delegate = self self.picker.dataSource = self // Input the data into the array pickerData = ["Item 1", "Item 2", "Item 3", "Item 4", "Item 5", "Item 6"] } ...
Add the following methods to your ViewController.swift after the “didReceiveMemoryWarning” method.
override func didReceiveMemoryWarning() { super.didReceiveMemoryWarning() // Dispose of any resources that can be recreated. } // Number of columns of data func numberOfComponents(in pickerView: UIPickerView) -> Int { return 1 } // The number of rows of data func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int { return pickerData.count } // The data to return fopr the row and component (column) that's being passed in func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? { return pickerData[row] } ...
The numberOfComponentsInPickerView method asks for the number of columns in your picker element. For example, if you wanted to do a picker for selecting time, you might have 3 components; one for each of hour, minutes and seconds.
The numberOfRowsInComponent method asks for the number of rows of data in your UIPickerView element so we return the array count.
The pickerView:titleForRow:forComponent: method asks for the data for a specific row and specific component.
With this data set, we only have one column so we’re just considering which row its asking for and returning the data item that corresponds to that row.
Adding more components
Let’s add more components to our data!
In ViewController.swift, let’s change the line of code where we append our data. Instead of just an array of string items, let’s do an array of array items.
class ViewController: UIViewController, UIPickerViewDelegate, UIPickerViewDataSource { @IBOutlet weak var picker: UIPickerView! var pickerData: [[String]] = [[String]]() override func viewDidLoad() { super.viewDidLoad() // Do any additional setup after loading the view, typically from a nib. // Connect data: self.picker.delegate = self self.picker.dataSource = self // Input the data into the array pickerData = [["1", "2", "3", "4"], ["a", "b", "c", "d"], ["!", "#", "$", "#"], ["w", "x", "y", "z"]] } ...
Pay specific attention to line 5 where the array of string is held by another array: var pickerData: [[String]] = [[String]]()
We also have to change the numberOfComponentsInPickerView method:
// Number of columns of data func numberOfComponents(in pickerView: UIPickerView) -> Int { return 4 }
Let’s change the pickerView:titleForRow:forComponent: method too:
// The data to return fopr the row and component (column) that's being passed in func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? { return pickerData[component][row] }
5. Detecting UIPickerView selections
In order to detect what the user has selected with the UIPickerView, we only have to implement one more delegate method:
// Capture the picker view selection func pickerView(pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) { // This method is triggered whenever the user makes a change to the picker selection. // The parameter named row and component represents what was selected. }
The UIPickerView is the iOS standard for selecting from multiple options. As you can see from this simple UIPickerView example, it can be difficult for beginners to understand if they haven’t learned about delegation yet. However, once you get the hang of using delegates then it makes sense and you can take advantage of other UIElements which leverage delegation too.
I am needing to use a picker to select a number from 1 to 1000. I have added 2 pickers then set an array using
var pickerNumbers: [Int] = []
for i in 1…1000{
pickerNumbers.append(i)
}
I then output the selection using
func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
startingNumberLabel.text = String(pickerNumbers[row])
}
This works great for the 1st label. My problem is I am trying to save a starting number and an ending number so I have 2 labels and 2 pickers. These are my outlets I created.
@IBOutlet weak var endingNumberLabel: UILabel!
@IBOutlet weak var startingNumberLabel: UILabel!
@IBOutlet weak var startingNumberPicker: UIPickerView!
@IBOutlet weak var endingNumberPicker: UIPickerView!
Any advice?
hi I want to add a picker view or action sheet to select multiple values or item from the list which I am having from the api. can you help me to get out of it.
Hi Pawan! Are you having trouble exiting or dismissing a picker view/action sheet? If so, what have you tried doing so far?
Hi dear,
can I got the solutions to add 5 row and 3 column??/
Hi Jaymeen! What solution have you tried already?
Hi Adrien,
thanks for this. I can’t quite get how to get a value out of the picker?
I Made a picker of 4 item (normal array). Now I want to put the choice of the picker in a string.
How to do that?
thanks
Niels
Hi there Niels! UIPickerViews have this method called
selectedRow(inComponent:)
that you might find useful. You can get more information from the documentation here. If your picker just has one column, you would pass in 0 to the method. It will then return an index that you can use with your array to get the actual value that was picked. Hope that helps!This works fine for a 1D array, but there is a problem with the 2D, and like the earlier version mentioned previously it doesn’t show up since the example given is that of a square array.
The problem seems to lie here:
func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
return pickerData.count
}
If you increase the number of elements within the example, let’s say to 6 rows, then it will only show the first 4. However, if you return 6 above then it will of course display all 6 rows in the 4 columns. So the problem is likely with pickerData.count. Unfortunately I am not experienced enough to offer a solution, all I can say is it took me a few hours to locate the cause of the problem. Hopefully it will take someone a few seconds to furnish us with the solution.
Hey! I had this same problem. I changed return pickerData.count to return pickerData[component].count , and both of my columns turned up with the right options
Hi Chris. Thanks for the tutorial. It took me the longest time (2 days effort) because I was trying to use it to fix a previous picker that was implemented programmatically (i.e., without using storyboards). The problem with doing it from code was that it was inflexible when you consider all of the different screen sizes these days. So I knew I had to replace it and found your tutorial. For the life of me (after adapting your steps to my existing code base) I could not figure out why the picker wheel wasn’t showing up! Finally I tried your method using a fresh ViewController. It worked perfectly and by comparing it line-by-line with my other (broken) one, it became clear that it didn’t know where to get the pickerArray data from, so displayed nothing. I now realize that the only thing that “tells it” where the picker data is coming from are the required Interface functions (pickerView numberOfRowsInComponent and pickerView titleForRow). Since I was trying to adapt my old programmatic version, I thought assigning the data in viewDidLoad would make the connection (as it had done previously)…wrong. Hope this helps someone else troubleshooting. I find it interesting (and a bit bizarre) that there is no obvious connection between the pickerView and it’s data source, but oh well…frustrating but it works.
Thanks for this, great to see delegation and data source in action, as well as a 2-dimensional NSarray! Concerning the latter, it seems that declaration for such nested array stays the same NSArray *_pickerData; ?! So no double asterisk (C++ style) is needed, like NSArray **-pickerData, or NSArray _pickerData[][] ? Is such style allowed here (in objectiveC) at all ? .. Say I would like a fixed size 3-d array of floats, would this work (float mydata[6][2][5]) ? or would you suggest to use nested NSArray instead, similar to your example?
-(NSInteger)numberOfComponentsInPickerView:(UIPickerView *)pickerView
-(NSInteger)pickerView:(UIPickerView *)pickerView numberOfRowsInComponent:(NSInteger)component
i had to replace the return type “int” in those two functions with NSInteger. i was getting a data conflict error.
Hi Chris, I’ve a problem with implentation. Can you help me to solve this ?
Implement this.
– (int)numberOfComponentsInPickerView:(UIPickerView *)pickerView
{
return 4;
}
Thanks for this Chris, definitely helpful. I think, however, that you may have wanted return _pickerData[component][row]; (swap indices) in titleForRow. Yours’d work because your array is square, but it’ll go out of bounds otherwise.
Thanks for catching that! Edited!
Hi Chris. I would like to know if there is a way to link the keyboard to the UIPickerview. To search easier through the pickerview, or would it be better to sort the data before it is added to the pickerview? Thank you in advance.
Hey Tienny, you have a typo where the error is.. it’s supposed to be – (void)pickerView:(UIPickerView *)pickerView didSelectRow:(NSInteger)row inComponent:(NSInteger)component
Hi Chris, there is a Parse Issue in ViewController.m. How do I solve this? Thank you