iOS Training from beginner to advanced
[Updated to Swift 2.0/iOS9.0 9/15/15]
I’ve 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 new post.
UIPickerViews are those spinning wheel slot machine type controls. For a variety of reasons I went into in the earlier post they are not as common as other controls in iOS. The reason I’ll focus on here is the lack of properties for a UIPickerView
. It relies heavily on delegates. In this lesson we’ll look at some of those delegate methods.
I debated just having people go to the UIPickerView lesson, and start with that, but decided it would be simpler to start with a single-component picker view instead of the double component in that lesson. I will go through the setup quickly and if you are not understanding something, I’d suggest clicking here and doing that lesson first.
Make a new project with Command-Shift-N, under iOS make a single view project named SwiftUIPickerFormatted. Drag a label and a picker view onto the view controller on the storyboard.
Open the Assistant editor, and control-drag from the label to make an outlet named myLabel. Control-drag from the picker view and make an outlet named myPicker. Close the assistant editor and go to the ViewController.swift file. Clean up the code, then add the data for the picker view there so it looks like this:
class ViewController: UIViewController { @IBOutlet weak var myPicker: UIPickerView! @IBOutlet weak var myLabel: UILabel! let pickerData = ["Mozzarella","Gorgonzola","Provolone","Brie","Maytag Blue","Sharp Cheddar","Monterrey Jack","Stilton","Gouda","Goat Cheese", "Asiago"] override func viewDidLoad() { super.viewDidLoad() myPicker.dataSource = self myPicker.delegate = self } }
UIPickerView
needs a delegate and a data source. In the viewDidLoad
above we set both the delegate and dataSource to to self
so we can add the required methods here. Change the class declaration to this:
class ViewController: UIViewController,UIPickerViewDataSource,UIPickerViewDelegate {
You will get the classic
Type 'ViewController' does not conform to protocol 'UIPickerViewDataSource'
error. Xcode is whining at you to add some required methods for the protocol. The two methods are in the data source. Add these towards the bottom of your code:
//MARK: - Delegates and data sources //MARK: Data Sources func numberOfComponentsInPickerView(pickerView: UIPickerView) -> Int { return 1 } func pickerView(pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int { return pickerData.count }
We have only one component for the picker view so we return a literal 1. Using .count
we get the number of rows from the data. We have some optional methods to use in UIPickerViewDelegate
. Add these below the Data Source methods:
//MARK: Delegates func pickerView(pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? { return pickerData[row] } func pickerView(pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) { myLabel.text = pickerData[row] }
The first method places the data into the picker and the second selects and displays it in the label. You can now build and run and spin the wheel a bit:
Now that we have set up a picker view, we might want a different font or color. There is a method which uses attributed strings. Add this under the code we already have:
func pickerView(pickerView: UIPickerView, attributedTitleForRow row: Int, forComponent component: Int) -> NSAttributedString? { let titleData = pickerData[row] let myTitle = NSAttributedString(string: titleData, attributes: [NSFontAttributeName:UIFont(name: "Georgia", size: 15.0)!,NSForegroundColorAttributeName:UIColor.blueColor()]) return myTitle }
If you are not familiar with attributed Strings, click here and check out my earlier post on how to use them. This should change the font to blue 15 point Georgia given the attributed string we have. Build and run.
We do get blue, but no font change. You can change many attributes of a string. You cannot change the font in attributed text for picker views.
There is one more very powerful method for displaying something on a UIPickerView
. There is a method in the delegate to display a UIView
. One subclass of UIView
is a UILabel
. So we could make a label and have the delegate method return a formatted UILabel
. Add this example using attributed strings to the code we have so far:
func pickerView(pickerView: UIPickerView, viewForRow row: Int, forComponent component: Int, reusingView view: UIView?) -> UIView { let pickerLabel = UILabel() let titleData = pickerData[row] let myTitle = NSAttributedString(string: titleData, attributes: [NSFontAttributeName:UIFont(name: "Georgia", size: 26.0)!,NSForegroundColorAttributeName:UIColor.blackColor()]) pickerLabel.attributedText = myTitle return pickerLabel }
Build and run.
We get a change of font, but it is left justified. I went back to a black font for our next addition. With a UILabel
in this delegate method, you can use all the properties of a UILabel
instead of attributed text. I could use text
and font
property instead of attributed text. I’ll change textAlignment
to .Center
and I’ll add a colored background to the label by adding this to the delegate method.
//color and center the label's background let hue = CGFloat(row)/CGFloat(pickerData.count) pickerLabel.backgroundColor = UIColor(hue: hue, saturation: 1.0, brightness:1.0, alpha: 1.0) pickerLabel.textAlignment = .Center return pickerLabel
As I discussed in my color post on hues, this is a simple example to do multiple colors. Line two takes the current element and divides it by the number of elements in our array. Since both are type Int
, I cast them to CGFloat
before I do the division. This will be a CGFloat
between 0 and 1, which I use as my hue in assigning a color. Build and run:
You should be a bit more conservative with memory using this. A better way to write this is:
func pickerView(pickerView: UIPickerView, viewForRow row: Int, forComponent component: Int, reusingView view: UIView?) -> UIView { var pickerLabel = view as! UILabel! if view == nil { //if no label there yet pickerLabel = UILabel() //color the label's background let hue = CGFloat(row)/CGFloat(pickerData.count) pickerLabel.backgroundColor = UIColor(hue: hue, saturation: 1.0, brightness: 1.0, alpha: 1.0) } let titleData = pickerData[row] let myTitle = NSAttributedString(string: titleData, attributes: [NSFontAttributeName:UIFont(name: "Georgia", size: 26.0)!,NSForegroundColorAttributeName:UIColor.blackColor()]) pickerLabel!.attributedText = myTitle pickerLabel!.textAlignment = .Center return pickerLabel }
This way we check if the label is already created before creating a new one.
You’ll notice that things seem a little squeezed in using the label. One way of fixing this is two more delegate methods: rowHeightForComponent
and widthForComponent
. Add this to increase the spacing between cells in the picker view:
func pickerView(pickerView: UIPickerView, rowHeightForComponent component: Int) -> CGFloat { return 36.0 }
If you build and run, you get this:
While in a single component, it is not so important, if you have multiple components you can change the width of each spinner as well. Let’s make the component smaller. Add this code:
func pickerView(pickerView: UIPickerView, widthForComponent component: Int) -> CGFloat { return 200 }
We are making all the components the same here, since we only have one. We could use the component
parameter to identify the component and have different lengths for each one. Build and run.
That is a few of the things you could do with a UIPickerView
. There are a lot more, and since it accepts a UIView
anything is possible for a user interface. Apple does recommend in the Human Interface Guide that UITableViews
are a better option if you want to get complex. But the UIPickerView
does have some very good uses, limited by your creativity.
// // ViewController.swift // SwiftUIPickerFormatted // // Created by Steven Lipton on 10/20/14. // Copyright (c) 2014 MakeAppPie.Com. All rights reserved. // Updated to Swift 2.0 9/15/15 SJL import UIKit class ViewController: UIViewController,UIPickerViewDataSource,UIPickerViewDelegate { @IBOutlet weak var myPicker: UIPickerView! @IBOutlet weak var myLabel: UILabel! let pickerData = ["Mozzarella","Gorgonzola","Provolone","Brie","Maytag Blue","Sharp Cheddar","Monterrey Jack","Stilton","Gouda","Goat Cheese", "Asiago"] override func viewDidLoad() { super.viewDidLoad() myPicker.delegate = self myPicker.dataSource = self } //MARK: - Delegates and data sources //MARK: Data Sources func numberOfComponentsInPickerView(pickerView: UIPickerView) -> Int { return 1 } func pickerView(pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int { return pickerData.count } //MARK: Delegates func pickerView(pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? { return pickerData[row] } func pickerView(pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) { myLabel.text = pickerData[row] } func pickerView(pickerView: UIPickerView, attributedTitleForRow row: Int, forComponent component: Int) -> NSAttributedString? { let titleData = pickerData[row] let myTitle = NSAttributedString(string: titleData, attributes: [NSFontAttributeName:UIFont(name: "Georgia", size: 26.0)!,NSForegroundColorAttributeName:UIColor.blueColor()]) return myTitle } /* less conservative memory version func pickerView(pickerView: UIPickerView, viewForRow row: Int, forComponent component: Int, reusingView view: UIView?) -> UIView { let pickerLabel = UILabel() let titleData = pickerData[row] let myTitle = NSAttributedString(string: titleData, attributes: [NSFontAttributeName:UIFont(name: "Georgia", size: 26.0)!,NSForegroundColorAttributeName:UIColor.blackColor()]) pickerLabel.attributedText = myTitle //color and center the label's background let hue = CGFloat(row)/CGFloat(pickerData.count) pickerLabel.backgroundColor = UIColor(hue: hue, saturation: 1.0, brightness:1.0, alpha: 1.0) pickerLabel.textAlignment = .Center return pickerLabel } */ /* better memory management version */ func pickerView(pickerView: UIPickerView, viewForRow row: Int, forComponent component: Int, reusingView view: UIView?) -> UIView { var pickerLabel = view as! UILabel! if view == nil { //if no label there yet pickerLabel = UILabel() //color the label's background let hue = CGFloat(row)/CGFloat(pickerData.count) pickerLabel.backgroundColor = UIColor(hue: hue, saturation: 1.0, brightness: 1.0, alpha: 1.0) } let titleData = pickerData[row] let myTitle = NSAttributedString(string: titleData, attributes: [NSFontAttributeName:UIFont(name: "Georgia", size: 26.0)!,NSForegroundColorAttributeName:UIColor.blackColor()]) pickerLabel!.attributedText = myTitle pickerLabel!.textAlignment = .Center return pickerLabel } func pickerView(pickerView: UIPickerView, rowHeightForComponent component: Int) -> CGFloat { return 36.0 } // for best use with multitasking , dont use a constant here. //this is for demonstration purposes only. func pickerView(pickerView: UIPickerView, widthForComponent component: Int) -> CGFloat { return 200 } }
Fantastic!
Just one question:
Whenever I set the delegates to self in the viewDidLoad method, the program fails. Any suggestions?
Nevermind. I had called a numberOfComponents(1) method on it before, causing the error.
Do you have any suggestions for keeping the UIPickerView very short so as to only show one component until the picker is clicked on? Without compressing the font?
UIPickerViews are really meant to stay pretty static in terms of size (one reason they are not that popular). They really aren’t “clicked on” which is why they have delegates. The Apple Human Interface Guide frowns (uses the term “As much as possible”) on having them conditionally visible, so trying to do what you are doing could get your app rejected.
I used this tutorial to hide the row of a date picker until the above row was selected. http://manenko.com/2014/12/19/put-uidatepicker-inside-static-uitableview/
Hi, I have problem in chapter “Using UIViews to Format the Picker”. When I finish “let myTitle = NSAttributedString…” line, xcode tells me ” Cannot invoke ‘init’ with an argument list of type ‘(string:string, atributes:$T9’ “. I don’t know what I should do. Thanks
There was a change in Swift from the time I wrote this and the current release of XCode. I’m working on a solution now. Apparently there some changes to optionals in attributed text.
ok, thank you. btw nice tutorials.
Thank you for the compliment. And I have a solution.
It turns out that in the latest version of Swift the
UIFont
is now optional. In order to put it in the attributes dictionary correctly you have to unwrap the optional. So the code now reads:Just add an exclamation point after the
UIFont
initializer, and your error should disappear.I updated the tutorial for this change, and I now have to go to the attributed string one and change it there too. Thank you for catching this error.
Steve
thank you so much for the tutorial! I’d been pulling hair for a couple of weeks!
You are welcome. Glad it solved a problem.
This is a quite good blog/tutorial.
I have worked out how to add and format additional components. Seems an un-intuitive approach to have to use if constructs. Is there an alternative/better approach?
I have not been able to make a component loop e.g. the UIDatePicker loops the day, hours and minute components. Can this behaviour be emulated with UIPickerView?
Thanks!
In what circumstances would view == nil ? (line 50)
if view == nil { //if no label there yet
Thanks.
-u
When there is no label in that row yet. The app created a brand new picker view most likely, or cleared the contents of this row. Only when the label is empty does the delegate create a label, otherwise it just reads it. This is more efficient than creating a new label every time you access a row.
Thanks for the answer, but it does not easily make sense to me, you write, “Only when the label is empty does the delegate create a label, otherwise it just reads it.“ If a label is empty, then it (the label) exists, and surely it can only be the text object that does not exist if a label is empty? However, I do not think this is what you intend. I think you are intending the creation of views. So how many views are needed? The code appears to work just as well without if view == nil, and I tried adding a flag or two, to try and see what is happening. There seems to be some correlation between the number of items in the pickerData array and the number of times a view is instantiated up to 10 that is, without making any picker selection(s).
I should have used
view
there instead of label, referring to the parameterview
. The lineis part of a delegate that configures a row for a picker. In this delegate it configures a
UIVIew
for a certain row to display. You are correct you don’t need anif
clause.. As I explained in the text, the five-line version above it:works perfectly fine. But it makes the label every time you display the label. That takes time and memory to do. What I did was make the label once, the first time the row of the pickerView gets displayed. It is a form of lazy instantiation, which is just a good habit to have. It conserves processor power and memory.
All the
if
statement does is create theUILabel
once and make it the view for the row for the entire life of theUIPickerView
.Before the first time theUIPickerview
calls the delegate for a specific row. If in that row there is noUIView
, the value of the parameterview
isnil
. I use that to make sure I only create the label once, no matter how many times this delegate gets called. Technically the only thing that was needed in theif
block was the creation of a label – or I could make everything in the if clause.The number of views come from another delegate, whihc is a required delegate.
So yes there is a very direct correlation between the number of views and the number of elements of
pickerData
That’s where we got it from.The third example you say, “makes a label every time you display the label” (sic), and that example 4 makes the label once, no matter how many times the delegate gets called. Although I get lazy instantiation, I don’t see this. Surely there are at least the same number of label views created, as there are labels visible, and maybe a couple of spares too, unless that is there is some other wizardry at work when building the graphic?
The correlation between elements and calls isn’t what I may have anticipated. The number of calls made ranges between 4 and 10 depending upon the number of elements, where 1 element generates 4 calls, and 2 elements generates 7. 3 elements generates 8 calls and so on to 5 elements, which generates the maximum of 10 calls.
I observe no difference in the delegate call pattern when comparing the two versions.
Then don’t use the if. I got good results in Instruments. I’ll trust my data and practices, you can trust yours. I’ll call this session over.
How do you make the UIPickerView wrap?
Use a UILabel that wraps as your view.
As a follow up to what I said yesterday, Haven’t tried this, but modify the code above like this:
Awesome, thanks. I’ll give this a try as it is something else I wanted to know. What I’m looking for (sorry I wasn’t clear before) is how can you make the UIPickerView spin continuously (wrapping around)? Does that make sense?
Oops. Soory I didnt understand. I really don’t know the answer to that question.
No worries. You answered a question I did want to know, but forgot to ask. Thanks for the help. If I figure it out, I’ll post solution here. Thanks again!
thank you!
Yep I understand now and I really don’t know how you could get the delegate to point back to the beginning of the data when you reach the end.
I’ve done it in Objective-C, but don’t know Objective-C that well {barely} so I don’t know how to convert it to Swift.
I have an idea. Write out the objective-c here . I’ll do a post about converting one to the other. .
Here’s the .h:
//
// ViewController.h
// UIPickerViewWrapShake
//
#import
@interface ViewController : UIViewController
@property (nonatomic, strong) NSArray *produceArray;
@property (nonatomic, strong) IBOutlet UIPickerView *picker;
@property (nonatomic, strong) IBOutlet UILabel *myLabel;
– (IBAction)shakeButton:(UIButton *)sender;
@end
Here’s the .m:
//
// ViewController.m
// UIPickerViewWrapShake
//
#import “ViewController.h”
@interface ViewController ()
@end
@implementation ViewController
@synthesize produceArray;
@synthesize picker;
@synthesize myLabel;
– (IBAction)shakeButton:(UIButton *)sender
{
[picker selectRow:(arc4random() % [self pickerView:picker numberOfRowsInComponent:0]) inComponent:0 animated:YES];
[picker selectRow:(arc4random() % [self pickerView:picker numberOfRowsInComponent:0]) inComponent:1 animated:YES];
[picker selectRow:(arc4random() % [self pickerView:picker numberOfRowsInComponent:0]) inComponent:2 animated:YES];
}
– (void)viewDidLoad
{
[super viewDidLoad];
self.produceArray = [[NSArray alloc] initWithObjects:
@”apple”
,@”watermelonj khguygjgljkhlkh”
,@”banana”
,@”pear”
,@”coconut”
,@”orange”
,@”pineapple”
,@”tomato”
,@”cucumber”
,@”pickle”
,@”pumpkin”
,@”cumquat”
,@”avocado”
,@”lettuce”
,@”Kale”
,@”spinach”
,nil];
self.picker.dataSource = self;
self.picker.delegate = self;
}
– (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
// returns the number of ‘columns’ to display.
– (NSInteger)numberOfComponentsInPickerView:(UIPickerView *)pickerView
{
return 3; //number of columns
}
// returns the # of rows in each component..
– (NSInteger)pickerView:(UIPickerView *)pickerView numberOfRowsInComponent: (NSInteger)component
{
//return [self.produceArray count]; //number of rows
return ([self.produceArray count] * 3); //number of rows * 3 (for wrap)
}
-(NSString *)pickerView:(UIPickerView *)pickerView titleForRow:(NSInteger)row forComponent:(NSInteger)component
{
//return [self.produceArray objectAtIndex:row];
return [self.produceArray objectAtIndex:(row % [self.produceArray count])]; // for wrap using (row % [self.produceArray
}
// enable custom font and wrapping for component object
– (UIView *)pickerView:(UIPickerView *)pickerView
viewForRow:(NSInteger)row
forComponent:(NSInteger)component
reusingView:(UIView *)view
{
UILabel* tView = (UILabel*)view;
if (!tView){
tView = [[UILabel alloc] init];
// Setup label properties – frame, font, colors etc
tView.minimumScaleFactor = .1f;
tView.adjustsFontSizeToFitWidth = YES;
tView.numberOfLines = 1; // 1 = use with adjustFontSizeToFitWidth; 0 = use as many lines as needed, will wrap
} // if (!tView){
// Fill the label text with the array row
//tView.text = [self.produceArray objectAtIndex:row];
tView.text = [self.produceArray objectAtIndex:(row % [self.produceArray count])]; // for wrap using (row % [self.produceArray count])
return tView;
}
– (void)pickerView:(UIPickerView *)pickerView didSelectRow:(NSInteger)row inComponent:(NSInteger)component
{
//we want the selection to always be in the SECOND set (so that it looks like it has stuff before and after)
if (row = (2 * [self.produceArray count]) ) {
row = row % [self.produceArray count];
row += [self.produceArray count];
[pickerView selectRow:row inComponent:component animated:NO];
}
}
@end
Thanks for your help!! (not sure how to post storyboard… ) After posting above, I didn’t think I could have posted a link to create a GitHub repository for it. I’m trying to figure out how to do that.
I have the worst luck with github. no worries. BUt this is what it look like. with a lot less code that what you gave me. What you gave me is more versatile and you could spin infinitely, but if you are not going to spin the dial 1000 times, the code I’m working on works just great.

That’s great! The datasets I have in mind are less than 1000. Does it spin manually, with a button and/or shake? Thanks again for your help! I’m sooooo glad I found your site!
This is a simple manual version. I’ll leave it you to get fancy.
OK, great. Thanks again for your help!
And it is now posted!!! https://makeapppie.com/2015/03/02/converting-objective-c-to-swift-the-circular-picker-view-example/
Thanks so much! I’ll take a look at it. Again, I really appreciate your help!
Pingback: EVERYTHING YOU WANT – Swift resources | swiftioscodetutorial
How to change say “Brie” text to green when selected. All other picker titles remaining black and “Brie” reverting to black when not selected?
Thanks
Great tutorial. Is there a way to make only selected rows a selected color. I have a multi component picker and based on the selection in component 1 I would like to change a row (not necessarily the selected ones) to green in component 2.
Only one I could think of is use another array of UIColor corresponding to your second component to set colors on the second component.
Thanks for the reply. I have tried quite a bit without luck. I was hoping to change let’say the color of the font for just item 2 row 6. I tried attributed text but somehow everything ends up changing all rows.
You can use similar code to the gradient background I demonstrate above the difference either: 1) use an if statement to turn the one you want to the color or as I metioned 2) maintain an array of UIColor. In both cases change the code I have to set to UIColor to the proper color, instead of computing one. LIke tableviews this may mean you’ll have to reload the picker every color change. While I dont know you applcation, I’m not sure putting a color on something you can’t see most of the time is good UI. A label is lot easier, or “freezing” the picker component by making the selected row the only row of the component.
I think you are bringing me on the right track with the if statement. I have to try. My picker is complex in a way. It has 4 items. I have a picker with item 1 that has rows from a to z and item 2 with rows from 1 to 17. I have the same for item 3 and item 4. When I select “a” and “1” in resp. Item 1 and 2 then I want to select “g” and “6” in item 3 and item 4 and give them a selected color. On top of that there will be multiple rows in item 3 and 4 that will get that same color to show possible “matches”. Is there any way to add a screenshot to the post to explain?
i think you got the idea of what you want to and need to do. No need.
Pingback: I built a Tip Calculator App! | Project Innovator
Hate when that happens — and it happens a lot. Nice app.
in func pickerView(pickerView: UIPickerView, attributedTitleForRow row: Int, forComponent component: Int) -> NSAttributedString? {} delegate, I was able to change font color, but the font size and font style of the label in pickerview dont change? any other workarounds? thanks! great blog btw
comment out one of the delegates.
You cannot change the font for attributed text in a UIPickerView. Add a label to the picker view as described in the demo.
Thanks, I used picker view and it works properly but I’m using two components, I was able to return both of my datasource to both components but its throwing “array out of range” when I try to selecting the values back and forth. It would be nice to have a post about pickerview with multiple components. :P
https://makeapppie.com/2014/09/18/swift-swift-implementing-picker-views/
Pingback: How MakeAppPie.com is Updating to Swift 2.0 | Making App Pie
great tutorial! It helped me alot setting up my picker.
Question;
Why u comment @ setting the width of the component:
// for best use with multitasking , dont use a constant here.
//this is for demonstration purposes only.
This is when u have different strings with different lengths or something, right? Picker will not display when the length of the string is longer then the constant u set?
(I hope my question can be clearly understood….)
There is a long answer which will be a post sometime in October. The short answer is you have to fully implement size classes and autolayout in order to use your app in Mutitasking on an iOS9 iPad. You want nothing that’s a constant for a dimention.
Ok, tx for the answer. And I will look for the long answer in Oktober.
Hello to everybody, I’m a new in swift and xcode as well, Im stacked in connection pickerview to attribute in core data (want to show all cells in pickerview) I cant find nothing in net, could somebody give me an example or link how to do that.
Thanks in advance.
if you are new, you shouldn’t really be messing with core data. You are better off with a simple text file or xml for image data anyway.
Great tutorial, Thanks!
Somebody got the “func pickerView(pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String?” to work in Swift 3?
I’ll update this post sometime this week.
great tutorial sir but I need a picker with seven components that rotate horizontally not vertically and my requirement is that the text of pickers should change as in default picker do. I have been trying on it more than 3 days but I did’t get a way and third party as well
You’ll have to write your own scroll views for that.