Create a Core Data Model with Xcode 4.2
- Create a new Core Data Model
- Select File -> New -> New file ...
- Select iOS -> Core Data (On the left panel)
- Select Data Model (On the right panel)
- Select Model.xcdatamodeld
- Create Entity
- Select Add Entity
- Enter the Entity Name
- Select Add Attribute and enter attribute name and type
- In the Utility Panel, select Data Model Inspector
- Change the attribute's settings if needed
- Create model relationship
- Add + sign under relationship in the editor window
- Enter the destination
- Specific whether it is to-many relationship in the Model Inspector
Create Object Class for the Created Data Entities
- Select Model.xcdatamodeld
- In the editor, select the Entities of interest
- Select File -> New -> New File ...
- Select NSManagedObject subclass
Generate Code
#import <Foundation/Foundation.h>
#import <CoreData/CoreData.h>
@class Phone;
@interface User : NSManagedObject
@property (nonatomic, retain) NSString *name;
@property (nonatomic, retain) NSSet *phones;
@end
@interface User (CoreDataGeneratedAccessors)
- (void)addPhoneObject:(Phone *)value;
- (void)removePhoneObject:(Phone *)value;
- (void)addPhone:(NSSet *)values;
- (void)removePhone:(NSSet *)values;
@end
#import "User.h"
#import "Phone.h"
@implementation User
@dynamic name;
@dynamic phone;
@end
- Objective C runtime allows application to interview when received an un-defined message call
- @dynamic allows the compiler to suppress compiler error when an object send an un-defined message in the code
- NSManagedObject will intervine and serve the un-defined message
- The following will not create a compilation error even the implementation class does not implement the method
Fetch & Create Data
+ (phone *)findOrCreatePhone:(NSDictionary *)data inManagedObjectContext:(NSManagedObjectContext *)context
{
Phone *phone = nil;
NSFetchRequest *request = [[NSFetchRequest alloc] init];
request.entity = [NSEntityDescription entityForName:@"Phone" inManagedObjectContext:context];
request.predicate = [NSPredicate predicateWithFormat:@"number = %@", [data objectForKey:@"number"]];
NSError *error = nil;
phone = [[context executeFetchRequest:request error:&error] lastObject];
if (!error && !phone) {
phone = [NSEntityDescription insertNewObjectForEntityForName:@"Phone" inManagedObjectContext:context];
phone.number = [data objectForKey:@"number"];
phone.user = [User findOrCreateUser:data inManagedObjectContext:context];
}
return phone;
}
Select the name of the entity
request.entity = [NSEntityDescription entityForName:@"Phone" inManagedObjectContext:context];
Define the condition(s)
request.predicate = [NSPredicate predicateWithFormat:@"number = %@", [data objectForKey:@"number"]];
Execute the query
phone = [[context executeFetchRequest:request error:&error] lastObject];
Create a new row.
- insertNewObjectForEntityForName: only creates a new object
phone = [NSEntityDescription insertNewObjectForEntityForName:@"Phone" inManagedObjectContext:context];
- A separate API save persist the data to the DB
Another example of finding or creating a object
+ (User *)findOrCreateUser:(NSDictionary *)data inManagedObjectContext:(NSManagedObjectContext *)context
{
User *user = nil;
NSFetchRequest *request = [[NSFetchRequest alloc] init];
request.entity = [NSEntityDescription entityForName:@"User" inManagedObjectContext:context];
request.predicate = [NSPredicate predicateWithFormat:@"name = %@", [data objectForKey:@"name"]];
NSError *error = nil;
user = [[context executeFetchRequest:request error:&error] lastObject];
if (!error && !user) {
user = [NSEntityDescription insertNewObjectForEntityForName:@"User" inManagedObjectContext:context];
user.name = [data objectForKey:@"name"];
}
return user;
}
Application Delegate
Application Delegate header file
#import <UIKit/UIKit.h>
#import <CoreData/CoreData.h>
@interface MyAppDelegate : NSObject <UIApplicationDelegate> {
UIWindow *window;
@private
NSManagedObjectContext *_managedObjectContext;
NSPersistentStoreCoordinator *_persistentStoreCoordinator;
NSManagedObjectModel *_managedObjectModel_;
}
@property (nonatomic, strong) IBOutlet UIWindow *window;
@property (nonatomic, strong, readonly) NSManagedObjectContext *managedObjectContext;
@property (nonatomic, strong, readonly) NSManagedObjectModel *managedObjectModel;
@property (nonatomic, strong, readonly) NSPersistentStoreCoordinator *persistentStoreCoordinator;
- (NSString *)applicationDocumentsDirectory;
- (void)saveContext;
@end
Maintain pointers to the Managed Objects needed in other APIs
@property (nonatomic, strong, readonly) NSManagedObjectContext *managedObjectContext;
@property (nonatomic, strong, readonly) NSManagedObjectModel *managedObjectModel;
@property (nonatomic, strong, readonly) NSPersistentStoreCoordinator *persistentStoreCoordinator;
Retrieve Data in the Application Delegate
- Retrieve a list of phone information when the application start
- Create the phone/user object if it is not in the DB
- Save it to the DB
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
NSArray *phones = [...]; Retrieve phones/users information
for (NSDictionary *phone in phones) {
[Phone findOrCreatePhone::phone inManagedObjectContext:self.managedObjectContext];
}
[self saveContext];
...
}
- Persist all changes in the Managed Object Context to the DB
- (void)saveContext {
NSError *error = nil;
if (_managedObjectContext != nil) {
if ([_managedObjectContext hasChanges] && ![_managedObjectContext save:&error]) {
...
NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
}
}
}
Application Delegate System Logic
Maintain the Managed Objects in the application delegate that needed in other Core Data APIs
Returns the managed object context
- (NSManagedObjectContext *)managedObjectContext {
if (managedObjectContext_ != nil) {
return _managedObjectContext;
}
NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];
if (coordinator != nil) {
_managedObjectContext = [[NSManagedObjectContext alloc] init];
[_managedObjectContext setPersistentStoreCoordinator:coordinator];
}
return _managedObjectContext;
}
Return the managed object model
- NSManagedObjectModel describes a DB schema
- Create the model if not created
- (NSManagedObjectModel *)managedObjectModel {
if (_managedObjectModel != nil) {
return _managedObjectModel;
}
NSString *modelPath = [[NSBundle mainBundle] pathForResource:@"Model" ofType:@"momd"];
NSURL *modelURL = [NSURL fileURLWithPath:modelPath];
_managedObjectModel = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL];
_return managedObjectModel;
}
NSPersistentStoreCoordinator associate persistent stores with a model
- (NSPersistentStoreCoordinator *)persistentStoreCoordinator {
if (_persistentStoreCoordinator != nil) {
return _persistentStoreCoordinator;
}
NSURL *storeURL = [NSURL fileURLWithPath: [[self applicationDocumentsDirectory]
stringByAppendingPathComponent: @"Model.sqlite"]];
NSError *error = nil;
_persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc]
initWithManagedObjectModel:[self managedObjectModel]];
if (![_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType
configuration:nil URL:storeURL options:nil error:&error]) {
...
NSLog(@"Error %@, %@", error, [error userInfo]);
}
return _persistentStoreCoordinator;
}
Return the document root directory of the application
- (NSString *)applicationDocumentsDirectory {
return [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];
}
Save data when terminate or move to the background
- (void)applicationWillTerminate:(UIApplication *)application {
[self saveContext];
}
- (void)applicationDidEnterBackground:(UIApplication *)application {
[self saveContext];
}
Implement View Controllers to Display Core Data
We are going to implement 2 Table View Controllers
- When the application starts, it lists all the users with the UserTableViewController
- When a specific user is selected, it navigates to the phone list for that user
Implement a User Table View Controller to display all users
#import <Foundation/Foundation.h>
#import "BasicTableViewController.h"
@interface UserTableViewController : BasicTableViewController
- initWithMOC:(NSManagedObjectContext *)context;
@end
- initWithMOC allows the application delegate to pass in a Managed Object Context
#import "UserTableViewController.h"
#import "PhoneTableViewController.h"
@implementation UserTableViewController
- initWithMOC:(NSManagedObjectContext *)context
{
if (self = [super initWithStyle:UITableViewStylePlain])
{
NSFetchRequest *request = [[NSFetchRequest alloc] init];
request.entity = [NSEntityDescription entityForName:@"User" inManagedObjectContext:context];
request.sortDescriptors = [NSArray arrayWithObject:[NSSortDescriptor sortDescriptorWithKey:@"name" ascending:YES
selector:@selector(caseInsensitiveCompare:)]];
request.predicate = nil;
request.fetchBatchSize = 10;
NSFetchedResultsController *rc = [[NSFetchedResultsController alloc]
initWithFetchRequest:request
managedObjectContext:context
sectionNameKeyPath:@"firstChar"
cacheName:@"MyCache"];
self.fetchedResultsController = rc;
self.titleKey = @"name";
self.searchKey = @"name";
}
return self;
}
* Create and configure the NSFetchRequest to fetch all user
* Create the NSFetchedResultsController and set it to self.fetchedResultsController
* Create section when display the name by the first character
{code:title=User.m}
- (NSString *)firstChar
{
return [[self.name substringToIndex:1] capitalizedString];
}
To set the User Table View Controller as the first screen of the application
- Inside application delegate's didFinishLaunchingWithOptions:
- Initialize the UserTableViewController with the Managed Object Context
- Initialize a navigation controller
- Push the UserTableViewController to the navigation controller
- Display the navigation controller
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
...
UserTableViewController *vc = [[UserTableViewController alloc] initWithMOC:self.managedObjectContext];
UINavigationController *nav = [[UINavigationController alloc] init];
[nav pushViewController:vc animated:NO];
[window addSubview:nav.view];
[window makeKeyAndVisible];
return YES;
}
When a user is selected, push the phone table view onto the navigation controller
- Initialized the phone table view with the user object
- Push the phone table view onto the navigation control
- (void)managedObjectSelected:(NSManagedObject *)managedObject
{
User *user = (User *)managedObject;
PhoneTableViewController *vc = [[PhoneTableViewController alloc] initWithUser:user];
[self.navigationController pushViewController:vc animated:YES];
}
@end
The second Table View Controller
#import <Foundation/Foundation.h>
#import "BasicTableViewController.h"
#import "User.h"
@interface PhoneTableViewController : BasicTableViewController
- initWithUser:(User *)user;
@end
Phone Table View Controller implementation
#import "PhoneTableViewController.h"
#import "Phone.h"
@implementation PhoneTableViewController
- initWithUser:(User *)user;
{
if (self = [super initWithStyle:UITableViewStylePlain])
{
NSManagedObjectContext *context = user.managedObjectContext;
NSFetchRequest *request = [[NSFetchRequest alloc] init];
request.entity = [NSEntityDescription entityForName:@"Phone" inManagedObjectContext:context];
request.sortDescriptors = [NSArray arrayWithObject:[NSSortDescriptor sortDescriptorWithKey:@"number" ascending:YES]];
request.predicate = [NSPredicate predicateWithFormat:@"user = %@", user];
request.fetchBatchSize = 10;
NSFetchedResultsController *rc = [[NSFetchedResultsController alloc]
initWithFetchRequest:request
managedObjectContext:context
sectionNameKeyPath:nil
cacheName:nil];
self.fetchedResultsController = rc;
self.titleKey = @"number";
}
return self;
}
- (void)managedObjectSelected:(NSManagedObject *)managedObject
{
....
}
@end
- Set cache to nil because request.predicate change what is requested and therefore the result is different everytime and cannot be cached
BasicTableViewController
#import "BasicTableViewController.h"
@implementation BasicTableViewController
@synthesize fetchedResultsController;
@synthesize titleKey, subtitleKey, searchKey;
- (void)createSearchBar
{
if (self.searchKey.length) {
if (self.tableView && !self.tableView.tableHeaderView) {
UISearchBar *searchBar = [[[UISearchBar alloc] init] autorelease];
[[UISearchDisplayController alloc] initWithSearchBar:searchBar contentsController:self];
self.searchDisplayController.searchResultsDelegate = self;
self.searchDisplayController.searchResultsDataSource = self;
self.searchDisplayController.delegate = self;
searchBar.frame = CGRectMake(0, 0, 0, 38);
self.tableView.tableHeaderView = searchBar;
}
} else {
self.tableView.tableHeaderView = nil;
}
}
- (void)setSearchKey:(NSString *)aKey
{
[searchKey release];
searchKey = [aKey copy];
[self createSearchBar];
}
- (NSString *)titleKey
{
if (!titleKey) {
NSArray *sortDescriptors = [self.fetchedResultsController.fetchRequest sortDescriptors];
if (sortDescriptors.count) {
return [[sortDescriptors objectAtIndex:0] key];
} else {
return nil;
}
} else {
return titleKey;
}
}
- (void)performFetchForTableView:(UITableView *)tableView
{
NSError *error = nil;
[self.fetchedResultsController performFetch:&error];
if (error) {
NSLog(@"[BasicTableViewController performFetchForTableView:] %@ (%@)", [error localizedDescription], [error localizedFailureReason]);
}
[tableView reloadData];
}
- (NSFetchedResultsController *)fetchedResultsControllerForTableView:(UITableView *)tableView
{
if (tableView == self.tableView) {
if (self.fetchedResultsController.fetchRequest.predicate != normalPredicate) {
[NSFetchedResultsController deleteCacheWithName:self.fetchedResultsController.cacheName];
self.fetchedResultsController.fetchRequest.predicate = normalPredicate;
[self performFetchForTableView:tableView];
}
[currentSearchText release];
currentSearchText = nil;
} else if ((tableView == self.searchDisplayController.searchResultsTableView) && self.searchKey && ![currentSearchText isEqual:self.searchDisplayController.searchBar.text]) {
[currentSearchText release];
currentSearchText = [self.searchDisplayController.searchBar.text copy];
NSString *searchPredicateFormat = [NSString stringWithFormat:@"%@ contains[c] %@", self.searchKey, @"%@"];
NSPredicate *searchPredicate = [NSPredicate predicateWithFormat:searchPredicateFormat, self.searchDisplayController.searchBar.text];
[NSFetchedResultsController deleteCacheWithName:self.fetchedResultsController.cacheName];
self.fetchedResultsController.fetchRequest.predicate = [NSCompoundPredicate andPredicateWithSubpredicates:[NSArray arrayWithObjects:searchPredicate, normalPredicate , nil]];
[self performFetchForTableView:tableView];
}
return self.fetchedResultsController;
}
- (void)searchDisplayControllerWillEndSearch:(UISearchDisplayController *)controller
{
[self fetchedResultsControllerForTableView:self.tableView];
}
- (void)setFetchedResultsController:(NSFetchedResultsController *)controller
{
fetchedResultsController.delegate = nil;
[fetchedResultsController release];
fetchedResultsController = [controller retain];
controller.delegate = self;
normalPredicate = [self.fetchedResultsController.fetchRequest.predicate retain];
if (!self.title) self.title = controller.fetchRequest.entity.name;
if (self.view.window) [self performFetchForTableView:self.tableView];
}
- (UITableViewCellAccessoryType)accessoryTypeForManagedObject:(NSManagedObject *)managedObject
{
return UITableViewCellAccessoryDisclosureIndicator;
}
- (UIImage *)thumbnailImageForManagedObject:(NSManagedObject *)managedObject
{
return nil;
}
- (void)configureCell:(UITableViewCell *)cell forManagedObject:(NSManagedObject *)managedObject
{
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForManagedObject:(NSManagedObject *)managedObject
{
static NSString *ReuseIdentifier = @"BasicTableViewCell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:ReuseIdentifier];
if (cell == nil) {
UITableViewCellStyle cellStyle = self.subtitleKey ? UITableViewCellStyleSubtitle : UITableViewCellStyleDefault;
cell = [[[UITableViewCell alloc] initWithStyle:cellStyle reuseIdentifier:ReuseIdentifier] autorelease];
}
if (self.titleKey) cell.textLabel.text = [managedObject valueForKey:self.titleKey];
if (self.subtitleKey) cell.detailTextLabel.text = [managedObject valueForKey:self.subtitleKey];
cell.accessoryType = [self accessoryTypeForManagedObject:managedObject];
UIImage *thumbnail = [self thumbnailImageForManagedObject:managedObject];
if (thumbnail) cell.imageView.image = thumbnail;
return cell;
}
- (void)managedObjectSelected:(NSManagedObject *)managedObject
{
}
- (void)deleteManagedObject:(NSManagedObject *)managedObject
{
}
- (BOOL)canDeleteManagedObject:(NSManagedObject *)managedObject
{
return NO;
}
- (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath
{
NSManagedObject *managedObject = [[self fetchedResultsControllerForTableView:tableView] objectAtIndexPath:indexPath];
return [self canDeleteManagedObject:managedObject];
}
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath
{
NSManagedObject *managedObject = [[self fetchedResultsControllerForTableView:tableView] objectAtIndexPath:indexPath];
[self deleteManagedObject:managedObject];
}
#pragma mark UIViewController methods
- (void)viewDidLoad
{
[self createSearchBar];
}
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
[self performFetchForTableView:self.tableView];
}
#pragma mark UITableViewDataSource methods
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return [[[self fetchedResultsControllerForTableView:tableView] sections] count];
}
- (NSArray *)sectionIndexTitlesForTableView:(UITableView *)tableView
{
return [[self fetchedResultsControllerForTableView:tableView] sectionIndexTitles];
}
#pragma mark UITableViewDelegate methods
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return [[[[self fetchedResultsControllerForTableView:tableView] sections] objectAtIndex:section] numberOfObjects];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
return [self tableView:tableView cellForManagedObject:[[self fetchedResultsControllerForTableView:tableView] objectAtIndexPath:indexPath]];
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
[self managedObjectSelected:[[self fetchedResultsControllerForTableView:tableView] objectAtIndexPath:indexPath]];
}
- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section
{
return [[[[self fetchedResultsControllerForTableView:tableView] sections] objectAtIndex:section] name];
}
- (NSInteger)tableView:(UITableView *)tableView sectionForSectionIndexTitle:(NSString *)title atIndex:(NSInteger)index
{
return [[self fetchedResultsControllerForTableView:tableView] sectionForSectionIndexTitle:title atIndex:index];
}
#pragma mark NSFetchedResultsControllerDelegate methods
- (void)controllerWillChangeContent:(NSFetchedResultsController *)controller
{
[self.tableView beginUpdates];
}
- (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)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:
[tableView reloadRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
break;
case NSFetchedResultsChangeMove:
[tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
[tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath] withRowAnimation:UITableViewRowAnimationFade];
break;
}
}
- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller
{
[self.tableView endUpdates];
}
#pragma mark dealloc
- (void)dealloc
{
fetchedResultsController.delegate = nil;
[fetchedResultsController release];
[searchKey release];
[titleKey release];
[currentSearchText release];
[normalPredicate release];
[super dealloc];
}
@end
iOS Core Data Programming
To insert a new row
NSManagedObject *user =
[NSEntityDescription insertNewObjectForEntityForName:@"User"
inManagedObjectContext:(NSManagedObjectContext *)ctxt];
To access a column with the MSManagedObject
- (id)valueForKey:(NSString *)key;
- (void)setValue:(id)value forKey:(NSString *)key;
|