This is the third and final part of a series to help get you up to speed with the basics of Core Data quickly.
In the first part of the series, we created a visual data model for our objects, ran a quick and dirty test to make sure it works, and hooked it up to a table view so we could see a list of our objects.
In the second part of the series, we discussed how to import or preload existing data into Core Data so that we have some good default data when our app starts up.
In this part of the series, we’re going to discuss how we can optimize our app by using NSFetchedResultsController, to reduce memory overhead and improve response time.
Why Use NSFetchedResultsController?
So far, we’re at exactly the same point we were using the SQLite3 method. However, we didn’t have to write nearly as much code (notice the absence of a FailedBankDatabase class constructing raw SQL statements), and adding other functionality such as insert/delete operations would be much simpler.
However, there’s one notable thing that we could add pretty easily with Core Data that could give us huge benefits to performance: use NSFetchedResultsController.
Right now we’re loading all of the FailedBankInfo objects from the database into memory at once. That might be fine for this app, but the more data we have the slower this will be, and could have a detrimental impact to the user.
Ideally we’d like to load only a subset of the rows, based on what the user is currently looking at in the table view. Luckily, Apple has made this easy for us by providing a great utility class called NSFetchedResultsController.
So, start by opening up FailedBanksListViewController.h, removing out our old NSArray of failedBankInfos, and adding a new NSFetchedResultsController instead:
@interface FailedBanksListViewController : UITableViewController <NSFetchedResultsControllerDelegate> { NSFetchedResultsController *_fetchedResultsController; NSManagedObjectContext *_context; } @property (nonatomic, retain) NSFetchedResultsController *fetchedResultsController; @property (nonatomic, retain) NSManagedObjectContext *context; @end |
In the synthesize section, remove old failedBankInfos synthesize statement and add:
@synthesize fetchedResultsController = _fetchedResultsController; |
Then before we forget, set fetchedResultsController to nil inside the dealloc method:
self.fetchedResultsController.delegate = nil; self.fetchedResultsController = nil; |
Another awesome thing about NSFetchedResultsController is you an set it to nil upon viewDidUnload, which means that all of the data that is in memory can be freed up in low memory conditions (and the view is offscreen). All you have to do is set it to null in viewDidUnload (and make sure it’s re-initialized in viewDidLaod):
- (void)viewDidUnload { self.fetchedResultsController = nil; } |
Ok, now onto the fun part – creating our fetched results controller! We are going to override the get method for our property so that it checks to see if the fetched results controller exists first. If it does exist, it will return it, otherwise it will create it.
Add the following function toward the top of the file:
- (NSFetchedResultsController *)fetchedResultsController { if (_fetchedResultsController != nil) { return _fetchedResultsController; } NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init]; NSEntityDescription *entity = [NSEntityDescription entityForName:@"FailedBankInfo" inManagedObjectContext:_context]; [fetchRequest setEntity:entity]; NSSortDescriptor *sort = [[NSSortDescriptor alloc] initWithKey:@"details.closeDate" ascending:NO]; [fetchRequest setSortDescriptors:[NSArray arrayWithObject:sort]]; [fetchRequest setFetchBatchSize:20]; NSFetchedResultsController *theFetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:_context sectionNameKeyPath:nil cacheName:@"Root"]; self.fetchedResultsController = theFetchedResultsController; _fetchedResultsController.delegate = self; [sort release]; [fetchRequest release]; [theFetchedResultsController release]; return _fetchedResultsController; } |
This should look pretty familiar to the code that we used to have in viewDidLoad, to create a fetch request to pull out the FailedBankInfo objects. However, there’s a lot of new stuff here so let’s discuss…
First, any time we use an NSFetchedResultsController, we need to set a sort descriptor on the fetch request. A sort descriptor is just a fancy term for an object we set up to tell Core Data how we want our results sorted.
The cool thing about sort descriptors is they are very powerful. Not only can you sort on any property of the object you are returning, but you can sort on properties of related objects – just like we see here! We want to sort the objects based on the close date in the FailedBankDetails, but still only receive the data in FailedBankInfo – and Core Data can do this!
A very important part is the next statement – to set the batch size on the fetch request to some small size. In fact, this is the very reason we want to use the fetched results controller in this case. This way, the fetched results controller will only retrieve a subset of objects at a time from the underlying database, and automatically fetch mroe as we scroll.
So once we finish tweaking the fetch request with the sort descriptor and batch size, we just create a NSFetchedRequestController and pass in the fetch request. Note it takes a few other parameters too:
- For the managed object context, we just pass in our context.
- The section name key path lets us sort the data into sections in our table view. We could sort the banks by State if we wanted to, for example, here.
- The cacheName the name of the file the fetched results controller should use to cache any repeat work such as setting up sections and ordering contents.
So now that we have a method to return a fetched results controller, let’s modify our class to use it rather than our old array method. First, update viewDidLoad as follows:
- (void)viewDidLoad { [super viewDidLoad]; NSError *error; if (![[self fetchedResultsController] performFetch:&error]) { // Update to handle the error appropriately. NSLog(@"Unresolved error %@, %@", error, [error userInfo]); exit(-1); // Fail } self.title = @"Failed Banks"; } |
All we do here is get a handle to our fetchedResultsController (which implicitly creates it as well) and call performFetch to retrieve the first batch of data.
Then, update numberOfRowsInSection:
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { id <NSFetchedResultsSectionInfo> sectionInfo = [[_fetchedResultsController sections] objectAtIndex:section]; return [sectionInfo numberOfObjects]; } |
And update cellForRowAtIndexPath like the following:
- (void)configureCell:(UITableViewCell *)cell atIndexPath:(NSIndexPath *)indexPath { FailedBankInfo *info = [_fetchedResultsController objectAtIndexPath:indexPath]; cell.textLabel.text = info.name; cell.detailTextLabel.text = [NSString stringWithFormat:@"%@, %@", info.city, info.state]; } - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { static NSString *CellIdentifier = @"Cell"; UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier]; if (cell == nil) { cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier] autorelease]; } // Set up the cell... [self configureCell:cell atIndexPath:indexPath]; return cell; } |
Note we split out part of the logic into a separate configureCell method – this is because we’ll need it later.
Ok one more thing – we need to implement the delegate methods for the NSFetchedResultsController. The good news is these are mostly boilerplate – I literally copied and pasted these from an Apple sample. So just add these methods to the bottom of your file:
- (void)controllerWillChangeContent:(NSFetchedResultsController *)controller { // The fetch controller is about to start sending change notifications, so prepare the table view for updates. [self.tableView beginUpdates]; } - (void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject atIndexPath:(NSIndexPath *)indexPath forChangeType:(NSFetchedResultsChangeType)type newIndexPath:(NSIndexPath *)newIndexPath { UITableView *tableView = self.tableView; switch(type) { case NSFetchedResultsChangeInsert: [tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath] withRowAnimation:UITableViewRowAnimationFade]; break; case NSFetchedResultsChangeDelete: [tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade]; break; case NSFetchedResultsChangeUpdate: [self configureCell:[tableView cellForRowAtIndexPath:indexPath] atIndexPath:indexPath]; break; case NSFetchedResultsChangeMove: [tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade]; [tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath] withRowAnimation:UITableViewRowAnimationFade]; break; } } - (void)controller:(NSFetchedResultsController *)controller didChangeSection:(id <NSFetchedResultsSectionInfo>)sectionInfo atIndex:(NSUInteger)sectionIndex forChangeType:(NSFetchedResultsChangeType)type { switch(type) { case NSFetchedResultsChangeInsert: [self.tableView insertSections:[NSIndexSet indexSetWithIndex:sectionIndex] withRowAnimation:UITableViewRowAnimationFade]; break; case NSFetchedResultsChangeDelete: [self.tableView deleteSections:[NSIndexSet indexSetWithIndex:sectionIndex] withRowAnimation:UITableViewRowAnimationFade]; break; } } - (void)controllerDidChangeContent:(NSFetchedResultsController *)controller { // The fetch controller has sent all current change notifications, so tell the table view to process all updates. [self.tableView endUpdates]; } |
Compile and run your project, and it should look the same. However, if you examine the debug output you will see something very amazing…
SELECT 0, t0.Z_PK FROM ZFAILEDBANKINFO t0 LEFT OUTER JOIN
ZFAILEDBANKDETAILS t1 ON t0.ZDETAILS = t1.Z_PK
ORDER BY t1.ZCLOSEDATE DESC
total fetch execution time: 0.0033s for 234 rows.
SELECT 0, t0.Z_PK, t0.Z_OPT, t0.ZNAME, t0.ZSTATE, t0.ZCITY,
t0.ZDETAILS FROM ZFAILEDBANKINFO t0 LEFT OUTER JOIN
ZFAILEDBANKDETAILS t1 ON t0.ZDETAILS = t1.Z_PK WHERE
t0.Z_PK IN (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)
ORDER BY t1.ZCLOSEDATE DESC LIMIT 20
total fetch execution time: 0.0022s for 20 rows.
SELECT 0, t0.Z_PK, t0.Z_OPT, t0.ZNAME, t0.ZSTATE, t0.ZCITY,
t0.ZDETAILS FROM ZFAILEDBANKINFO t0 LEFT OUTER JOIN
ZFAILEDBANKDETAILS t1 ON t0.ZDETAILS = t1.Z_PK WHERE
t0.Z_PK IN (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)
ORDER BY t1.ZCLOSEDATE DESC LIMIT 20
total fetch execution time: 0.0017s for 20 rows.
You can see here that behind the scenes, the NSFetchedResultsController is getting a list of IDs from FailedBankInfo, in the proper sort order. Then, as the user pages through the table, it loads one batch at a time – rather than loading all of the objects into memory at once!
This would have been a lot more code to do with raw SQLite – and is just one of the many reasons why using Core Data can save time and increase performance.
Show Me The Code!
Here is a sample project with all of the code we have developed in the above tutorial.
Where to Go From Here?
You should have a good understanding of the basics of Core Data at this point. A good exercise would be to continue this example to add in the detail view that we created in the SQLite tutorial, or to add in support for adding/editing/deleting items.
I’d recommend also taking a look at some of the Apple samples out there – they have 5 different Core Data examples currently available and they all show different and interesting aspects of things you can do.
Also, if you have any advice about Core Data or gotchas that you’ve encountered with Core Data in your projects, please share!
Category: iPhone







Hi Ray!
Thanks for the tutorial! It’s really helpful.
While working through it I noticed a small typo in this snippet:
- (NSInteger)tableView:(UITableView *)tableView
numberOfRowsInSection:(NSInteger)section {
id sectionInfo =
[[fetchedResultsController sections] objectAtIndex:section];
return [sectionInfo numberOfObjects];
}
I had to change the line
[[fetchedResultsController sections] objectAtIndex:section];
to:
[[_fetchedResultsController sections] objectAtIndex:section];
And also earlier in the text you said:
In the synthesize section, remove old failedBankInfos synthesize statement and add:
@synthesize context = _context;
whereas you probably meant to say:
@synthesize fetchedResultsController = _fetchedResultsController;
And also in this method:
- (void)configureCell:(UITableViewCell *)cell atIndexPath:(NSIndexPath *)indexPath {
FailedBankInfo *info = [fetchedResultsController objectAtIndexPath:indexPath];
cell.textLabel.text = info.name;
cell.detailTextLabel.text = [NSString stringWithFormat:@"%@, %@",
info.city, info.state];
}
I had to change the line
FailedBankInfo *info = [fetchedResultsController objectAtIndexPath:indexPath];
to either:
FailedBankInfo *info = [_fetchedResultsController objectAtIndexPath:indexPath];
or:
FailedBankInfo *info = [self.fetchedResultsController objectAtIndexPath:indexPath];
@Alexei: D’oh! Thanks for pointing those out those typos, I’ve fixed the blog post above!
Hi, Ray,
I’m entirely new to app dev for the iPad/iPhone and find your tutorials most helpful in learning the process. I learn best by following the designs of others and then adapting them to my ideas.
In particular, the app I have in mind is very ambitious and must involve core data and must involve split views on the iPad for effective navigation and UI.
Your tutorials start out with split view on the iPad and later move to considerations of core data, so this looks to be exactly what I need to get started. However, the core data tutorial (no. 3) is an iPhone app rather than an extension of core data in the split view iPad environment.
My other model for learning is Apple’s CoreDataBooks sample code, which if recast into the iPad split view design, would be even more helpful in that it uses the grouped list and navigation bar buttons for hierarchy and editing.
I wonder if your tutorials could move to this level of design including headers and footers extracted from the core data to show a more structured view of the data for navigation. For example, grouping the banks by state and then building in the edit and add buttons in the navigation bar.
Once again, thanks for your willingness to share.
@Mike: Sounds like a good idea! I wrote the Core Data tutorials first and then the iPad tutorials after, which is why the Core Data tutorials are for the iPhone.
I actually considered making the iPad examples involve Core Data, but decided against it to help keep things simple. My current hope is that if people go through the Core Data tutorials and iPad tutorials separately, they should have enough background knowledge in order to combine the two in one of their own apps.
However Core Data is a deep subject, so perhaps I will revisit it some day – and if so your post is a good idea for how to do so! :]
Thanks, Ray,
As you know, the iPad gives us so much more real estate with which to play that enterprise apps that require data entry as well as retrieval and display of complex data are vastly more likely on this platform.
My app is or I hope will be a port of a very extensive real estate app I wrote back in the late ’70s on a Wang mini. Now, with the iPad and its ease of use and the possibility of adding graphs and maps and photos, the time is ripe to revive my enterprise app with the latest technology. I hope to pitch it toward students and professionals alike built on the same database structure.
I’m having a hard time getting the basic split view, editing, adding, grouping UI, and core data hooks to work properly (or at all in some cases), but hope that once I can crack the code, the real app development can grow rapidly. I’ll keep pounding away.
I’ve looked at all the books I can find and at all the resources on the web, but none are directly on point, which surprises me a bit because the structure that I have outlined seems so obvious for building many, many enterprise apps.
Thanks, again for your effort.
Hi, Ray.
Thanks for the tutorials!
I’m hoping to apply what I’ve learned to a situation where I’m using multiple database tables and I wonder if you have any thoughts as to how to go about adapting the FetchedResultsController to handle that.
For example, say I have a database table for employees and another one for workgroups. It’s a many-to-many relationship and I have viewTables for both with buttons that allow me to switch between them.
I’ve populated the database and I can see one of the tables upon startup with the proper data – they both work ok on startup. But when I press the button to switch views, I can’t seem to get fetchedResultsController to fetch the new data.
I’ve tried setting fetchedResultsController to nil to force a re-define, calling reloadData on the viewTable, setting the cache argument to nil, and fiddling with controllerWillChangeContent but to no avail.
I wonder if you have any advice as to how to best proceed?
Thanks,
James
@James: I haven’t played with that situation myself, but on first thought, why not just a) have two different NSFetchedResultControllers, b) have a state variable which one you want to use and have the table view use the appropriate one based on the variable, c) switch the state variable and reload the table view upon a switch?
Hi, Ray.
Thanks – your first thought worked out!
It wasn’t obvious to me that I couldn’t just re-use the NSFetchedResultsController object over two different data sets. I thought if I’d just reset the entity, set the cacheName to nil, then I’d be able to have it re-fetch things…maybe holdover thinking from SQL stuff.
Thanks again!
James
@James: Awesome, glad it worked! :]
Hi Ray, great job on your tutorials, very will written and detailed. Tutorials like that take a lot of time, and we all appreciate you taking that time to help us.
I think a previous comment is similar to what I wanted to ask.
I have a self referential table. a Folder can have a Collection of subfolders, etc. So if a user selects a folder in the tableView it needs to then go deeper and display its subfolders.
Can this be done with a single FetchResultsController? If so, how? I was thinking the FetchResultsController could have a property to hold the parent Folders id, nil if it is the top parent, then include it in the predicate if it is not nil. Hmm, but then how to remove … Wait, in my didSelect… method I create a new instance of my TableViewController and push it onto the navigation controller stack. So I guess I just have a instance of FetchResultsController for each TableViewController and set the folder id in it like I stated above.
Yes, I think I found my solution while typing this, but I am still going to post it, just in case someone else out there has the same question.
@Mark: Thanks! Yeah these tutorials take a crazy amount of work, so I’m glad they’ve come in handy to some people :]
The solution you mentioned sounds like it should work – thanks for sharing for others!
hi ray ,
thanks for your all part of tutorial on core data thats solve all of my problem in it and i learn to work with this completely,you are a great developer .
Does setting the NSFetchedResultsController to nil in viewDidUnload create a memory leak? If not then why not? You retained it when you assigned it to self.fetchedResultsController so setting it to nil without releasing it should create leak. Sorry if this is a bit basic, but I am still learning.
@Ali: Thanks so much for the kind words!
@Marc: When you call self.fetchedResultsController = nil, it is actually the same as calling [self setFetchedResultsController:nil].
This method setFetchedResultsController is automatically generated by the compiler when you call this method:
@synthesize fetchedResultsController = _fetchedResultsController.
Because the property was declared as retain, the generated property method is smart enough to release the old object before setting it to a new value. So when you call self.fetchedResultsController = nil, it’s actually releasing any old value, then setting the instance variable to nil!
thanks for the great tutorial. if you wanted to split the results into different sections how would you do that?
@glj: For details on how to do that, I recommend Chapter 3 of More iPhone 3 Development by Dave Mark and Jeff LaMarche.
Hi Ray.
i want to create an application which have a root tableview that shows list of country and when i tap on each one go to another table view and show some city from that country ,i don’t know how to save the city in core data.
Hi Ray,
can u tell me how to store the information like customers into our own tables without modyfying address book.
Is NSFetchedResultsController the best way to “lazy load” sqlite databases, it sounds fantastic that it loads only what’s in the viewport. Have you tested in on bigger scale databases (say a table with 1,000 entries or so)? Thanks for your help.
@ali, shri: Sorry these questions are far too vague for me to be able to help out.
@Amarjit: I haven’t tested it with 1,000 entry DBs but it should work just fine, loading the data nicely into batches. It is a pretty cool and useful class!
Ray, please extend this tutorial on how to do add, edits, deletes and searches! Also, how to do categories (“section headers”) and how to implement moving and reflect these changes to the database.
Also, how to do INNER JOINS, or custom SQL commands via Core Data?
Thanks
@AD: Thanks for the ideas, added to the idea list for potential future posts!
Ray, you say that you can use OFFSET/LIMIT as a way to batch results from a SQLite database but I cannot find any examples of how to do this. Can you point me in the right direction?
Would doing this therefore mean any relationships would have to be hardcoded too?
My main problem is I want to build a data entry editor in Django and then import this database (pre-filled) into the iphone, but Core Data makes this hard for me to do without using CSV files or whatever.
@AD: When you add LIMIT to the end of a query, such as “LIMIT 100″, it will only return the first 100 records.
When you add OFFSET to the end of a query, such as OFFSET 100, it will skip the first 100 records that would have been returned and continue from there.
So, you can combine the two to batch results – use OFFSET 0, LIMIT 100 the first time to get the first 100, OFFSET 100, LIMIT 100 the next time to get the next 100, and so on. Of course, for this to be of use you need to first run a query to figure out the total number of rows available for your query.
Regarding importing data, not sure if you’ve read it already but I discuss methods of doing that in this post:
http://www.raywenderlich.com/980/core-data-tutorial-how-to-preloadimport-existing-data#comments
@Ray – I’m aware of the syntax of how to do an OFFSET/LIMIT; this is not what I am asking.
I am asking – where do I put the offset/limit query in the .m file; is it in the cellAtRowForIndexPath, or is it somewhere else; how does Objective C keep a record of what the current offset is?
In addition would you need to keep a cache of all offset/limits already captured to aid smoother scrolling?
In other words I was asking if Core Data uses NSFetchController to batch results, how would you do it WITHOUT Core Data.
I am not looking for explanations, I am looking for examples.
Thank you.
Ray,
I am very new to CoreData (and still fairly new to Objective C), so please forgive me if this is a newbie question.
One thing really confuses me here. How the did you assign _context? I know you did a synthesize context = _context; But now where in the code did you get a context from anywhere. It just seems to be magically initialized as soon as the code enters viewDidLoad. Of course, when I copied and past your code into a project I’m working on, it doesn’t do that. It crashes on
NSEntityDescription *entity = [NSEntityDescription entityForName:@"FailedBankInfo" inManagedObjectContext:_context];
in (NSFetchedResultsController *)fetchedResultsController {
because _context is still nil. Of course, it was b/c I don’t see it initialized anywhere. Would really appreciate if you could explain, because I understand the rest of the code, but not this part.
Thanks!
@AD: If you’re looking for examples of how to do that, you’re looking in the wrong place – I do not have an example of doing that on this site yet. An explanation of how to do it I can provide, but sounds like you’re not interested :]
@Leon: Ah, in the first part of the tutorial, we had the FailedBanksCDAppDelegate.m set the context on the FailedBanksListViewController in the applicationDidFinishLaunching method.
I’d be happy for sites that you have bookmarked or an explanation that have a reference to examples of how to use Offset/limit in tableView and/or cellForIndexPath.
With this, I can go off to find the relevant example, and if necessary post a link here or elsewhere on the subject.
Thanks for your help
@AD: Unfortunately, I don’t know of any sites with examples of this off the top of my head.
Hello Ray,
I understand. I just wanted to see if I could get away from not using Core Data and use SQLite as I would do with a website application. Anyway, it doesn’t matter; I’ve decided to keep playing with Core Data, try parent-child relationships and learn as much as I can.
Thanks Ray for your help and patience.
Ray,
Pardon my ignorance. I’m a self taught programmer and have a few holes in my understanding.
I have noticed on several of your tutorials (thanks for all BTW) you use an underscore to name a variable in the @interface section and then without the underscore in the @property section.
Also you use it like this: @synthesize variable =_variable.
I haven’t seen that in any books.
Does that have special meaning?
Love all your tutorials. Thanks.
Hi Ray,
I came across this tutorial while trying to find answer to the problem I am facing. Since, you are offering help to others, I thought I shall try asking you. I am trying to build a table view for displaying selectable options in each cell using core data along with NSFetchedResultsController. The cells are grouped by sections. I need to display the sections in a custom order and the cells within the sections also in a custom order. If I use sort descriptor on a particular field in the table which has the order in which the cells should be displayed, the cells are properly ordered but does not show under the section to which they should belong. If I use the sort descriptor on the section name field itself then the cells are displayed under proper sections but the sections are displayed in ascending order whereas I would like the sections to be displayed in a custom order. An example of how I would like the table to be displayed is shown below:
BSectionName
CellB2
CellB1
ASectionName
CellA1
CellA2
CSectionName
CellC1
CellC2
Hope to get some guidance from you. Thanks.
@Kurt T – The _ (underscore) to name a variable in the @interface section and then without the underscore in the @property section. IE: “@synthesize variable =_variable.” is a private class instance variable (I think I got that wording right). It is mapping the private class variable to one you can use in your .m file; you don’t specifically need to do this as you can just reference variable without resorting to using underscores. Think of it as best practise.
@AD: Thanks for helping out @Kurt! :]
@VC: When you set up a NSFetchedResultsController, one of the parameters is sectionNameKeyPath. What you set here will group the entities you request by that field name so they can be split into sections. Are you using that?
@Ray,
Thanks for looking into the issue. Yes, I am using the sectionNameKeyPath parameter which divides the cells into groups/sections. I am basically building a home page for my app using tableview (as a menu of selectable options) which is made up of sections and their respective items.
As noted earlier, I am using core data to build the view. The table which holds each cell’s data within a section has a field named groupTitle which is basically the section name and I am using sectionNameKeyPath on that field. I am able to get the display in the form of sections and cells but it seems that because I have used sectionNameKeyPath on groupTitle, the output is in sorted order i.e. ASectionName is displayed first and then BSectionName is displayed whereas I would like to have BSectionName displayed first and then ASectionName. This is issue #1.
Now, I also want the cells containing data within each section to be ordered in a specific way too. This is issue#2.
Currently, I am able to build the home menu options properly by using a small workaround i.e. by prefixing number in the groupTitle value i.e. BSectionName is now called 1.BSectionName and ASectionName is now called 2.ASectionName. so that sections are ordered by the initial number in their value.
This is the first time I have reached out to the web world for getting help and was quite excited to get a reply from you. Thanks again for your time.
@VC: I haven’t tried this myself yet, but have you tried using NSSortDescriptors on the fetch request to try to order the data? You can specify a custom sort order too with custom comparisons. Here’s some information in Apple’s docs:
http://developer.apple.com/library/ios/#documentation/Cocoa/Conceptual/SortDescriptors/Concepts/Creating.html
@Ray,
Yeah, I am using NSSortDescriptors on the on the fetch. The strange thing I noticed was that using sectionNameKeyPath on a field groups and sorts the sections and if additionally if I used NSSortDescriptor on another field (ascending:YES) in the fetchRequest, it resulted in the cells appearing in the wrong sections. Consider table with following data:
RecordId Field1 Field2
1 SectionA Cell2
2 SectionB Cell1
If for a fetch I used sectionNameKeyPath on Field1 and NSSortDescriptor on Field2, I get the following in UI:
SectionA
Cell1
SectionB
Cell2
I was excepting Cell2 to appear under SectionA and Cell1 to appear under SectionB. I guess it loses track of which cell belong to which section if I set NSSortDescriptor on another column.
I am also new to iOS programming and would eventually understand what’s going on and maybe it is not supposed to be work as I am expecting.
I shall take a look at the link given by you. Thanks again for your time. I started to follow you on twitter, the first time I landed on this page. You looked like a friendly person going by your picture and your other responses above. Thanks again,VC.
@VC: Cool, well if you figure it out post the solution here if you can, so anyone else running into the same problem knows what to do!
Thanks for the kind words, and it’s great to meet you! Let’s stay in touch :]
Hi Ray, just reading trough your Core data stuff, excellent as always. What pops immediately in my mind while reading is that I am going to want to have at least 2 different data models in my app. I know the pain of bundling a database along a distributable app, so what pops in my mind is that I’ll have set data which I need as database (say country names, locations, etc.) and I’ll have another set of data which will be what the user creates – his favorite locations for example. Now … when an update of app comes I’d want to update the first set and keep the second untouched (so the user won’t loose what he’s been doing up to the app update).
So it looks I’ll have to have 2 persistentStoreCoordinators and managedObjectModels to handle this right? But then how do I make joins and relations between them?
What do you think of such setup?
@Marin Todorov — Do you mean having 1 database with all your prefilled data, whilst the other is what the user created data?
I’m very interested in this answer too. Bundling 2 db with the same relationships seems a bit silly. I’m not sure if versioning is the answer.
Hi Ad – yes kind of have 2 databases (data models) one for the data that will be updated/overwritten with a coming update of the app and one which the user creates. Say you have list of bars, the list of the bars comes with the app and changes with every app update (adding more bars to the list), but the user has the ability to mark his favorites so he can see them in a separate favorites list. The favorites list would be the 2nd database (user created) so when the app would show the favorites table view it will have to relate the comments and info from the favorites list (user created) with the bar info from the app bundled data model… (just an example)
@Marin/@AD: Keep in mind that I haven’t done this myself so take it with a grain of salt… but based on what I understand and have read, the common solution isn’t so much to partition your data into the non-user-modifiable parts and the user-modifiable parts (since that leads to issues with joins like you mentioned, or just you changing your mind later about what’s modifyable and what isn’t).
Instead, the idea is you ship with some preset data, and have some code to update the existing database with the preset data on an update, according to an app-specific algorithm. So you’d loop through the objects, reading from the preset DB/context, and inserting/update into the user’s existing DB context appropriately.
Thanks for this wonderful tutorial. It really help and very well documented.
This is the best tutorial so far ive encountered.
Thanks man.
@ZaldzBugs: Thanks so much, I really appreciate the kind words :]
Hi Ray! thanks for the great job!
I need just one little advice…
I’m trying to create an app that is basically a database, users are supposed to create events wich contain different kind of information (a picture, GPS coordinates, some notes, and so on) for each entry!
do you think Core Data is the right technology to manage all this? or should I go deeper in other directions?
thank you for the advice!
bye from Italy!
Yes, Core Data seems fine for that kind of thing, download the sample core data (books) from developer.apple.com for more sample projects.
fantastic !!
Hi Ray,
I am struggling since weeks with an issue related to fetchedResultsController. Reading this thread, looks like you may be able to help me.
Below a “summary”, simplified, version of my code.
I manage in CoreData a simple hierarchy.”CardSet” is the master table “Card” is the child.
This code works find for the first selection (whichever “cardSet” is selected, the right “childs” cards are displayed).
But for any further selection, whichever “cardSet” is selected, the same “childs” are always displayed.
Looks like, in the fetchedResultsController method, aside from the 1st time, the only code executed is: “if (_fetchedResultsController != nil) return _fetchedResultsController;”, hence giving always the same result. I think I have an issue with “resetting” the fetchedResultsController.
Thank you very much for your time.
———————————————————
In each controller, I have
- (void)viewDidUnload {
self.fetchedResultsController=nil; }
- (void)dealloc {
[_fetchedResultsController release];}
————————————————————————————————————————————
// CardSetTableController.m (is a UITableViewController)
- (void)viewDidLoad {
entityForName=@”CardSet”;
sortKey=@”label”;
[super viewDidLoad];
}
- (void)viewWillAppear:(BOOL)animated {
NSError *error;
[self fetchedResultsController] performFetch:&error];
[self.tableView reloadData];
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
if (downCardController == nil) {
downCardController=[[CardTableController alloc]
initWithStyle:UITableViewStylePlain];
}
CardSet *oneCardSet=[self.fetchedResultsController objectAtIndexPath:indexPath];
downCardController.cardSet=oneCardSet;
[self.navigationController pushViewController:downCardController animated:YES];
}
-(NSFetchedResultsController *) fetchedResultsController {
// Is the same as the code below in CardTableController.m, except for the
// predicate indicated between //============
}
——————————————————————————————————————————————————————————
// CardTableController.m (is a UITableViewController)
- (void)viewDidLoad {
entityForName=@”Card”;
sortKey=@”label”;
[super viewDidLoad];
}
- (void)viewWillAppear:(BOOL)animated {
NSError *error;
[self fetchedResultsController] performFetch:&error];
[self.tableView reloadData];
}
-(NSFetchedResultsController *) fetchedResultsController {
if (_fetchedResultsController != nil) return _fetchedResultsController;
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
AppDelegate *appDelegate = [[UIApplication sharedApplication] delegate];
NSManagedObjectContext *context=[appDelegate managedObjectContext];
NSEntityDescription *entity=[NSEntityDescription entityForName:entityForName
inManagedObjectContext:context];
[fetchRequest setEntity:entity];
//====================================================================
//Predicate for selection of Cards “of” the selected CardSet
NSPredicate *pred= [[NSPredicate alloc]init];
pred=[NSPredicate predicateWithFormat:@"(cardSet.label=%@)",cardSet.label];
[fetchRequest setPredicate:pred];
//====================================================================
NSSortDescriptor *sortDescriptor=[[NSSortDescriptor alloc] initWithKey:sortKey
ascending:YES];
NSArray *sortDescriptors=[[NSArray alloc] initWithObjects:sortDescriptor, nil];
[fetchRequest setSortDescriptors:sortDescriptors];
NSFetchedResultsController *frc= [[NSFetchedResultsController alloc]
initWithFetchRequest:fetchRequest
managedObjectContext:context
sectionNameKeyPath:nil
cacheName:@”cacheCard”];
frc.delegate=self;
_fetchedResultsController=frc;
[fetchRequest release];
[sortDescriptor release];
[sortDescriptors release];
[pred release];
return _fetchedResultsController;
}
—————————————————————————————————————————————————–
@Luca: Yes, Core Data is usually best for this kind of thing, it just requires a bit of learning curve at start.
@AD: Thanks for helping out Luca! :]
@Jaho: Thanks! :]
@Jean-Marc: In your viewWillAppear, update the fetchRequest’s predicate, then call performFetch to re-fetch what you want to see.
Ray,
I didn’t do exactly what you proposed (the predicate is defined in fetchedResultsController, not in viewWillAppear), but your answer led me to the solution:” _fetchedResultsController=nil;” before re-fetching (in viewWillAppear).
Thank you very much for your help.
hi Ray, I’ve been working with CoreData lately and i found that when i set numberOfSectionsInTableView to return 2 for example. The app will crash with the error ->>>>> [NSMutableArray objectAtIndex:]: index 1 beyond bounds [0 .. 0]‘ <<<<—–
Can you teach me how to work with number of sections in CoreData.
Was planning to work with coredata together with indexes for searching.
Thanks for your help Ray.
Hi Ray
thanks so much for your tutorials bro (very enlightening!)
One question, request plz
Im porting the same example to a ipad app, but with a UIViewController instead of a UITableViewController,
to have a little table not for the whole screen
but I get this error>
“Request for member ‘tableView’ in something not a structure or union
here is my project
http://hyperprogram.com/banksCD.zip
is the same I think for the one you show us how to make but with the ipad in mind, I have searched a lot in google how to fix it to no avail
plz help man,
also check the project and I’ll leave it for other people if you want,
thank you so much!