Introduction
In previous tutorials I’ve shown how to use a UITableView to select from existing values stored in Core Data. The example project showed how to select what Role a Person is in. This tutorial will cover how to use a UIPickerView for the same purpose. I think the Picker looks cooler and is just as practical:
As usual I’m writing this tutorial without a clue in the world of how to do what I’m teaching. Luckily for me there are other tutorial writers out there! After a lot of research I found this great tutorial then expanded on it to use Core Data. I hope the results are useful to you.
Prerequisites
We follow on from Part 5, so download the project from the end of Part 5 then extract and open it with Xcode.
CoreDataTableViewCell Class
Typically you would display values in a TableViewCell found within a TableView. To change a value you would tap the TableViewCell then expect something to let you change that cell value. In order to make TableViewCell show a Core Data Picker I’ve created a base subclass called CoreDataTableViewCell. To use CoreDataTableViewCell we need to subclass it again in order to customise what it fetches from Core Data.
Start off by downloading, extracting and dragging the two files from CoreDataTableViewCell.zip into your project. Next create a new class by clicking File > New > New File…
Call the class RolePickerTVCell and make it a subclass of UITableViewCell:
Replace all the code in RolePickerTVCell.h with the code below. Note that RolePickerTVCell inherits from CoreDataTableViewCell:
#import <UIKit/UIKit.h> #import "CoreDataTableViewCell.h" #import "Role.h" @class RolePickerTVCell; @protocol RolePickerTVCellDelegate - (void)roleWasSelectedOnPicker:(Role*)role; @end @interface RolePickerTVCell : CoreDataTableViewCell @property (nonatomic, weak) id <RolePickerTVCellDelegate> delegate; @property (strong, nonatomic) NSManagedObjectContext *managedObjectContext; @property (nonatomic, strong) Role *selectedRole; @end |
Replace all the code in RolePickerTVCell.m with the following:
#import "RolePickerTVCell.h" #import "AppDelegate.h" @implementation RolePickerTVCell @synthesize managedObjectContext = __managedObjectContext; @synthesize selectedRole = _selectedRole; @synthesize delegate; - (void)setupFetchedResultsController { // 0 - Ensure you have a MOC if (!self.managedObjectContext) { NSLog(@"RolePickerTVCell wasn't given a Managed Object Context ... so it's going to go get one itself!"); AppDelegate *ad = [[UIApplication sharedApplication] delegate]; self.managedObjectContext = ad.managedObjectContext; } // 1 - Decide what Entity you want NSString *entityName = @"Role"; // Put your entity name here NSLog(@"RolePickerTVCell is Setting up a Fetched Results Controller for the Entity named %@", entityName); // 2 - Request that Entity NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:entityName]; // 3 - Filter it if you want //request.predicate = [NSPredicate predicateWithFormat:@"Person.name = Blah"]; // 4 - Sort it if you want request.sortDescriptors = [NSArray arrayWithObject:[NSSortDescriptor sortDescriptorWithKey:@"name" ascending:YES selector:@selector(localizedCaseInsensitiveCompare:)]]; // 5 - Fetch it self.fetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:request managedObjectContext:self.managedObjectContext sectionNameKeyPath:nil cacheName:nil]; [self.fetchedResultsController performFetch:nil]; NSLog(@"The following roles were fetched for the Picker by RolePickerTVCell:"); for (Role *fetchedRole in [self.fetchedResultsController fetchedObjects]) { NSLog(@"Role: %@", fetchedRole.name); } } - (void)initalizeInputView { // Prepare the Data for the Picker [self setupFetchedResultsController]; self.picker = [[UIPickerView alloc] initWithFrame:CGRectZero]; self.picker.showsSelectionIndicator = YES; self.picker.autoresizingMask = UIViewAutoresizingFlexibleHeight; if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) { UIViewController *popoverContent = [[UIViewController alloc] init]; popoverContent.view = self.picker; popoverController = [[UIPopoverController alloc] initWithContentViewController:popoverContent]; popoverController.delegate = self; } } - (id)initWithCoder:(NSCoder *)aDecoder { self = [super initWithCoder:aDecoder]; if (self) { [self initalizeInputView]; self.picker.delegate = self; self.picker.dataSource = self; } return self; } - (void)done:(id)sender { NSLog(@"Passing back the selected '%@' Role to the delegate", self.selectedRole.name); [self.delegate roleWasSelectedOnPicker:self.selectedRole]; [self resignFirstResponder]; } #pragma mark - #pragma mark UIPickerViewDataSource - (NSInteger)numberOfComponentsInPickerView:(UIPickerView *)pickerView { return 1; } - (NSInteger)pickerView:(UIPickerView *)pickerView numberOfRowsInComponent:(NSInteger)component { return [[self.fetchedResultsController fetchedObjects] count]; } #pragma mark - #pragma mark UIPickerViewDelegate - (NSString *)pickerView:(UIPickerView *)pickerView titleForRow:(NSInteger)row forComponent:(NSInteger)component { // Display the Roles we've fetched on the picker Role *role = [[self.fetchedResultsController fetchedObjects] objectAtIndex:row]; return role.name; } - (CGFloat)pickerView:(UIPickerView *)pickerView rowHeightForComponent:(NSInteger)component { // Configure the row height return 44.0f; } - (CGFloat)pickerView:(UIPickerView *)pickerView widthForComponent:(NSInteger)component { // Configure the width of the picker wheel thing return 300.0f; } - (void)pickerView:(UIPickerView *)pickerView didSelectRow:(NSInteger)row inComponent:(NSInteger)component { Role *role = [[self.fetchedResultsController fetchedObjects] objectAtIndex:row]; self.selectedRole = role; NSLog(@"The '%@' Role was selected using the picker", self.selectedRole.name); } @end |
RolePickerTVCell exists to help you select a role so a selectedRole property also exists for you to store a pointer to a role. The rest of the class methods have the following purposes:
- setupFetchedResultsController is where you customise what you want to bring back to the Picker.
- initalizeInputView calls setupFetchedResultsController then sets up some device specific settings for the picker.
- initWithCoder is the first method called. It simply calls initalizeInputView.
- done is called when the done button on the picker is pressed. This method passes the selectedRole to the delegate.
- The remaining methods are there because RolePickerTVCell implements the UIPickerViewDataSource and UIPickerViewDelegate protocols.
Currently the Staff Manager app only lets you assign a role to a person via the Person Detail Table View. For our example we will configure the Add Person Table View to use the picker for role selection. Click MainStoryboard.storyboard then select the Add Person Table View. Edit the Add Person Table View as shown below, note that Table View Section – Role does not need a Text Field:
Select the Role Table View Cell on the Add Person Table View then set its Custom Class to RolePickerTVCell:
Run the app now and add a person with the ‘+’ button. When you tap the Role cell the Core Data Role Picker should appear:
That’s almost great. If you tap done the role isn’t passed back to the Add Person Table View yet because AddPersonTVC isn’t a delegate of RolePickerTVCell. You should be getting used to configuring delegates by now although they are a difficult concept to grasp sometimes. Before we configure the delegate Control-drag a line from the Role Table View Cell to AddPersonTVC.h to create a new property called personRoleTVCell:
Now let’s set up the delegate. Open up AddPersonTVC.h and add/edit the following lines of code:
#import "RolePickerTVCell.h" @interface AddPersonTVC : UITableViewController <RolePickerTVCellDelegate> @property (nonatomic, strong) Role *selectedRole; |
Add the following to AddPersonTVC.m:
@synthesize selectedRole; - (void)viewWillAppear:(BOOL)animated { personRoleTVCell.textLabel.text = @""; personRoleTVCell.delegate = self; personRoleTVCell.managedObjectContext = self.managedObjectContext; } - (void)roleWasSelectedOnPicker:(Role *)role { self.selectedRole = role; personRoleTVCell.textLabel.text = self.selectedRole.name; NSLog(@"AddPersonTVC has set '%@' as the Selected Role", self.selectedRole.name); } |
Finally, add the following to the existing save method in AddPersonTVC.m just after person.surname = … :
person.inRole = selectedRole; |
One thing I noticed is that the Person Detail Table View isn’t currently loading in the role associated with a person so edit the existing viewDidLoad method of the PersonDetailTVC to include these lines:
self.personRoleTableViewCell.textLabel.text = self.person.inRole.name; self.selectedRole = self.person.inRole; // ensure null role doesn't get saved. |
That’s it! You should now be able to select a role for a new person using a picker!
Here’s the complete source code so far
If you liked this tutorial or found something wrong with it please let me know!
If you want to support my work and have an iPad please consider purchasing iSoccer *wink*
-Tim
Go to Part 7 or the Tutorials Index













adrian phillips
February 27, 2012 at 2:27 am
the picker looks cool man. thanks for the tutorial. great job.
adrian
Paul
April 7, 2012 at 4:56 am
Your tutorials are great. I’ve been struggling with core data and these have helped a lot.
I am having a problem with this part of the tutorial though:
I keep getting a warning for the line in initWithCoder:
self.picker.delegate = self;
Passing ‘RolePickerTVCell *__strong’ to parameter of incompatible type ‘id’
and another error for this line right below it:
self.picker.dataSource = self;
Passing ‘RolePickerTVCell *__strong’ to parameter of incompatible type ‘id’
Ugh, just can’t figure it out. Many thanks if you have an idea as to why I’m getting the warnings.
And since people are making suggestions I may as well add my two cents: It’d be great to see a tutorial where the Roles are grouped with the Name of the person associated with each. Just a suggestion. I’m going to forge ahead with the rest of your tutorial. Thanks again for doing these tutorials!
Tim Roadley
April 8, 2012 at 8:05 am
Hi Paul,
Do you get this error with the source code provided at the end of the tutorial or in a adaptation of the code in a different project?
Cheers
Paul G
April 9, 2012 at 11:59 pm
No, it was strictly within your tutorial. Not sure why. I took it to part 6(?) before adding the picker and search bar, implemented part 8 and it worked fine for me. :)
My issue now is in trying to adapt it to my own project: In what would be the Roles View, I would like to select the row (with a check) to be used elsewhere in the app. But also I’d like to add a new Role or Edit the selected role. I see no other way to do that except by adding a toolbar at the bottom that has two buttons: add and edit which would segue accordingly over to the RoleDetail View. Trouble is I can’t add a toolbar in a table view controller, so I put a toolbar and a table view into a View Controller. I realize I can’t use CoreDataTableView Controller and I think I can get around that. But I’m still coming up with errors every single time I run it. Have you ever done anything similar?
Ben
May 3, 2012 at 11:41 pm
Hey Tim,
Your tutorials are very instructive and easy to read. Thanks a lot.
I think the cause of those 2 warnings is that the RolePickerTVCell.h needs to declare the UIPIckerViewDelegate and UIPickerViewDataSource.
Ben
Jay
September 28, 2012 at 2:46 am
Awesome Tutorial.
I also encountered the same error and in doing a code comparison between the downloaded project and what’s above there are a couple of differences.
First Thing:
In RolePickerTVCell.h it should read
@protocol RolePickerTVCellDelegate
and
@interface RolePickerTVCell : CoreDataTableViewCell
This is done to have this class conform to the DataSource and Delegate structures.
Second thing:
Click on the CoreDataTableViewCell.m file, and open the Utilities panel. Click on the File Inspector Icon and in the Target Membership section, Check the Checkbox beside the name of your project (probably Staff Manager).
Tim Roadley
April 10, 2012 at 6:50 am
Paul,
Instead of showing ticks for selection try just highlighting the cell to show selection. You need the disclosure indicator space for the edit segue:
You can segue from a disclosure indication with this code:
You will need to manually set the identifier of the roleDetailTVC in the storyboard:
Cheers
Tim.
Paul G
April 10, 2012 at 1:11 pm
I will try your suggestion Tim! You have been a bigger help to me than you realize, especially with me being a self-taught newbie to all this! THANKS!
Paul G
April 11, 2012 at 12:15 am
In my Roles view, I set my prototype cell’s Accessory and Editing Acc. to ‘Detail Disclosure’. But when I run the app and tap anywhere on the cell, it still segues to the RoleDetailTVC. I was thinking it would only segue when I tapped on the little blue detail disclosure indicator on the right of the cell, and I could just highlight the cell and not segue if I tapped anywhere else on the cell. Is my segue set up incorrectly? I don’t think it is. I’ve got my View Controller Identifier set up OK to RoleDetailTVC, but how does the it know to use the accessoryButtonTapped method you showed above? I can’t set the cell to use it, or does the compiler “just know” to use it?
MIchael
April 18, 2012 at 12:27 pm
Could you do a tutorial on how to create a sectioned TableView with Core Data? You could maybe build off of this and divide up the TableView into sections based on the selected role, and the subtitle wouldn’t be necessary any more.
BSGraphic
April 22, 2012 at 7:32 am
Yes, TableView with Core Data (Xcode 4.3) tutorial that will be awesome!
Tim Roadley
April 25, 2012 at 8:18 am
At some point I will. I’ve only just learned how to do this. The technique is to populate separate arrays (per section) from a predicate filtered subset of the data that comes from the fetchedResultsController. Once you have those arrays you should use a switch statement to vary what goes into each section in the cellForRowAtIndexPath method. You also need that switch statement in the numberOfRowsInSection method. The numberOfSectionInTableView should return the number of arrays have.
Kyle M
May 9, 2012 at 1:12 pm
Hi Tim,
Great tutorial! Very detailed and is a huge help in learning :)
One thing I noticed was that on the Role Picker, if you select the first role (with out scrolling), it wouldn’t take the role, just come back as null. To fix this I added the fetching to the initalizeInputView at the end but with an objectAtIndex:0 so it would pull the first item in the array:
Role *role = [[self.fetchedResultsController fetchedObjects] objectAtIndex:0];
self.selectedRole = role;
Might want to add it in!
Thanks again for sharing all your hard work!
David
June 19, 2012 at 1:15 am
Hi,
first of all congratulations for your tutorials.
I made this tutorial for IPad but I have a problem. I can not see the Done button. I see PickerView with the data but not the Done button.
Do you have any idea?
Thank’s a lot.
David.
Margreet
June 30, 2012 at 2:01 am
Hello, your tutorial is a great help for me, being completely ignorant on objective C. Thanks!!!!
Now my problem.
I’m struggling for a week now with the following:
I want to make a list of all people with a specific role.
I thought I could write something like this:
//
// PersonsTVC.m
// Staff Manager
//
ALL YOUR CODE UNCHANGED AND THAN THIS:
// 3 – Filter it if you want
//request.predicate = [NSPredicate predicateWithFormat:@"Person.inRole" == Blah"];
NO WAY, what is the correct way?
And what if I want the role to be an input field like:
//request.predicate = [NSPredicate predicateWithFormat:@"Person.inRole %@" == selectedinputfield"];
Your help is really needed, I’m really stuck for a week.
Thanks, Margreet
Margreet
July 2, 2012 at 6:15 pm
ok, I found the right statement:
request.predicate = [NSPredicate predicateWithFormat:@"ANY inRole.name == 'Java Developer'"];
Margreet
July 7, 2012 at 4:39 am
Now for something completely different: I have an entity: MOVE, it has 2 attributes: FROM and TO. I want to make a list of all moves until one is back to the starting point (endstation == starting point (New York)) OR if no new move is found ( Paris London, if they hadn’t moved to New York after that)
Like this:
From To
New York Washington
Washington Paris
Paris London
London New York
.
They adviced me to use recursion. Since I’m new in all this, could somebody indicate how to write the code?
Thanks!!!!!!!!
James
July 11, 2012 at 3:47 am
Have you tried this on a device? The docs for UIPopoverController state “Popover controllers are for use exclusively on iPad devices. Attempting to create one on other devices results in an exception.”
- James
James
July 11, 2012 at 4:01 am
Ah nvm. I see its only being used for iPad
Ken Davis
August 24, 2012 at 7:33 am
I really appreciate this tutorial, but when I use this code example in a project it runs quite well except the addition of this object instead of a regular tableviewcell causes the keyboard to cover up any cells below the UIPicker section.
Any ideas as to a code block to correct the scroll problem?
Ken Davis
August 25, 2012 at 2:51 am
Let me explain this better:
Using your code from this part6:
1. open storyboard for AddPersonTVC
2. click drag the Role section to be at the top
3. Run app in simulator
4. picker works fine
5. click on Surname and keyboard covers the textfield
any Ideas how to make the textfield scroll up to be above the keyboard?
Tim Roadley
August 26, 2012 at 7:36 am
Hi Ken,
I think there is an example on http://www.stackoverflow.com
http://stackoverflow.com/questions/1126726/how-to-make-a-uitextfield-move-up-when-keyboard-is-present
I hope this helps
Ken Davis
August 26, 2012 at 2:06 pm
Tim:
Thank you for sending me to this page. It was full of different ideas about recalculating the view or the frame usually in the context of a ‘view’ not a ‘tableview’.
I spent the better part of the day testing all of the various solutions presented to find them all overly complex or non-functional.
However, the solution was near the bottom in a very brief comment.
You can use your PickerView or any other custom object that will work in a table cell and put it in a section anywhere in a TableView. However, because of it’s presence, after the view is loaded and some object becomes the first responder, as your code was written, caused an issue with fields being covered by the keyboard. That normally should not happen as the UITableViewController should bring the textfield into the view just above the keyboard without any coding.
The solution was simple. Because the picker code used the ‘viewWillAppear’ code block to set and return delegate matters, all that was needed was to add two lines of code to the ‘viewWillAppear’
//add the following line to fix keyboard issue
[super viewWillAppear:animated];
[self.tableView reloadData];
Problem solved: Now the UITableViewController method code works as expected.
Again, thank you for your tutorial and the rapid response to my comments.
Autumn
September 30, 2012 at 1:40 am
Thank you so much for this (and all of your) tutorials!!
I was wondering if you can use code similar to this to get the UIDatePicker to appear. So that the user can select a date and have it appear in the cell.
Jay
October 2, 2012 at 5:56 am
There is one issue with the Role Picker: UIPickerView has a ‘feature’ whereas it will not select (pick) something unless you scroll the picker first. In other words, if you tap row 0 (the default row in this case) and click Save, nil is returned. If the picker is scrolled and then moved back to row 0 and tapped, it returns the proper information.
Jay
October 2, 2012 at 6:59 am
Here’s one solution..
In RolePickerTVCell.m code the done: method to look like this
//clicked the done button
- (void)done:(id)sender {
if ( self.selectedRole == nil ) { //if self.selectedRole is nil then it means nothing was tapped, i.e. row 0
Role *role = [[self.fetchedResultsController fetchedObjects] objectAtIndex:0];
self.selectedRole = role;
}
[self.delegate roleWasSelectedOnPicker:self.selectedRole];
[self resignFirstResponder];
NSLog(@” done: Passing back the selected ‘%@’ Role to the delegate”, self.selectedRole.name);
}
However a better solution would be to have a default row 0 of ‘None’ which will then allow the user to scroll to select the role, or optionally select None.
carrie
October 18, 2012 at 11:42 pm
This is a great example, but I’d like to have a form which has this picker only when in edit mode. I know the method to call for editing, but how can I change the enabled/disabled state of this picker, i.e. turn off the click event when not in editing mode?
Thanks in advance
Franz
October 26, 2012 at 4:50 am
Hi,
the tutorial is really great. It helps to get things done although I don’t really understand what I am doing.
I have setup my app so that the picker is shown on the PersonDetail view. It works well, but when activating the picker it always starts at row 0.
Ideally, for an existing person with a selected role, the picker should directly jump to the corresponding row and show it.
I was able to add some code to determine which row it should display. But I am stuck at how to actually tell the picker to jump to the desired row. In the developer’s doc, there is a method: selectRow:inComponent:animated: but I am not able to use it. Does not seem to work for CoredataTableViewCell. Who can help?
thanks a lot
Franz