On my last post, you learned and became familiar with the template codes of Core Data. Now, you will learn how to model your own data using Core Data and you will modify the code to create to-dos instead of timeStamps. To build the to-do application, you will need to modify the data model by deleting the Event entity and adding a new one called ToDo.
The very first thing you need to do is modify the data model by deleting the Event entity, single-click on the Event entity and press delete on your keyboard. Once deleted, click the Add Entity button inside new entity mapping management area, a new entity will appear, name it ToDo. After you created the entity, try running the app and you will get an error that looks something like this:
2011-03-22 13:37:55.848 CoreDataModeling[6642:207] Unresolved error Error Domain=NSCocoaErrorDomain Code=134100 "The operation couldn’t be completed. (Cocoa error 134100.)" UserInfo=0x4d37da0 {metadata=<CFBasicHash 0x4d38330 [0x1009400]>{type = immutable dict, count = 6,
entries =>
0 : <CFString 0x4d38090 [0x1009400]>{contents = "NSStoreModelVersionIdentifiers"} = <CFArray 0x4d38410 [0x1009400]>{type = immutable, count = 0, values = ()}
2 : <CFString 0x4d38380 [0x1009400]>{contents = "NSStoreModelVersionHashesVersion"} = <CFNumber 0x590d530 [0x1009400]>{value = +3, type = kCFNumberSInt32Type}
3 : <CFString 0xe25720 [0x1009400]>{contents = "NSStoreType"} = <CFString 0xe258f0 [0x1009400]>{contents = "SQLite"}
4 : <CFString 0x4d383b0 [0x1009400]>{contents = "NSPersistenceFrameworkVersion"} = <CFNumber 0x4d37f20 [0x1009400]>{value = +320, type = kCFNumberSInt64Type}
5 : <CFString 0x4d383e0 [0x1009400]>{contents = "NSStoreModelVersionHashes"} = <CFBasicHash 0x4d38480 [0x1009400]>{type = immutable dict, count = 1,
entries =>
0 : <CFString 0x4d267e0 [0x1009400]>{contents = "Event"} = <CFData 0x4d38430 [0x1009400]>{length = 32, capacity = 32, bytes = 0x5431c046d30e7f32c2cc809958add1e7 ... 846e97d7af01cc79}
}
6 : <CFString 0xe258b0 [0x1009400]>{contents = "NSStoreUUID"} = <CFString 0x4d381f0 [0x1009400]>{contents = "FB72DCF3-3F9C-47F5-B3E0-5C9A259125B0"}
}
, reason=The model used to open the store is incompatible with the one used to create the store}, {
metadata = {
NSPersistenceFrameworkVersion = 320;
NSStoreModelVersionHashes = {
Event = <5431c046 d30e7f32 c2cc8099 58add1e7 579ad104 a3aa8fc4 846e97d7 af01cc79>;
};
NSStoreModelVersionHashesVersion = 3;
NSStoreModelVersionIdentifiers = (
);
NSStoreType = SQLite;
NSStoreUUID = "FB72DCF3-3F9C-47F5-B3E0-5C9A259125B0";
};
reason = "The model used to open the store is incompatible with the one used to create the store";
}
This error says that “The model used to open the store is incompatible with the one used to create the store”. This actually tells everything, it means that the data storage on the device is not compatible with the revised data storage, so Core Data cannot open it. If you encounter this error, you will need to use Core Data migration to move your data from one data model to another. I will not tackle the Core Data Migration now so just delete the app from the simulator/device and try to run it again. This will force Core Data to build a new data store that is compatible with the revised data model.
Single-click CoreDataModeling.xcdatamodeld and then single-click the ToDo entity. Add a new attribute by click the “plus” icon on the Attributes table and name it text
and set it’s type to String
. You will use this text
attribute to store the text of the to-do task. Add another one and call it date with type Date
.
Of course, you will need a new view controller for entering a new to-do task. You’ll probably want to use Interface Builder to build your controller, however, to keep this tutorial simple, you will just build a very simple interface using a programmatically interfaced view controller.
Let’s Begin
Single-click the CoreDataModeling folder, then, select File > New > New File…, or just simply type in the keys ⌘-N. Click the Cocoa Touch on the left and choose UIViewController subclass
, click Next, Choose UIViewController
on the drop-down menu and uncheck the With XIB for user interface checkbox. Click Next and name the new class AddToDoViewController
.
AddToDoViewController
Open AddToDoViewController
and create an instance for the UITextField
and NSManagedObjectContext
. When the RootViewController
calls up the AddToDoViewController
, it will set the reference to the context.
Make sure to declare the properties of for the UITextField
and NSManagedContext
. Don’t forget to implement the <UITextFieldDelegate>
protocol, our mini app will need to use a UITextFieldDelegate
method to receive messages from the UITextField
.
Your AddToDoViewController.h
should look like this:
#import <UIKit/UIKit.h>
@interface AddToDoViewController : UIViewController
{
UITextField *addToDoTextField;
UILabel *addToDoLabel;
NSManagedObjectContext *managedObjectContext;
}
@property (nonatomic, retain) UITextField *addToDoTextField;
@property (nonatomic, retain) UILabel *addToDoLabel;
@property (nonatomic, retain) NSManagedObjectContext *managedObjectContext;
@end
You will now need to synthesize
the properties you just implemented to your .m file. The code should look like this:
@synthesize addToDoTextField;
@synthesize addToDoLabel;
@synthesize managedObjectContext;
After synthesizing the properties, we will now go thru adding the interface programmatically. Create the UI in the loadView
method:
// Implement loadView to create a view hierarchy programmatically, without using a nib.
- (void)loadView
{
[super loadView];
addToDoTextField = [[UITextField alloc] initWithFrame: CGRectMake(75, 20, 200, 35)];
[addToDoTextField setBorderStyle: UITextBorderStyleRoundedRect];
[addToDoTextField setTextAlignment: UITextAlignmentLeft];
[addToDoTextField setContentVerticalAlignment: UIControlContentVerticalAlignmentCenter];
[addToDoTextField setPlaceholder: @"todo entry"];
[addToDoTextField setDelegate: self];
[[self view] addSubview: addToDoTextField];
addToDoLabel = [[UILabel alloc] initWithFrame: CGRectMake(15, 20, 60, 35)];
[addToDoLabel setText: @"To-do:"];
[[self view] addSubview: addToDoLabel];
}
This code creates a UITextField
object with the specified screen coordinated. CGRectMake
will help us specify it without difficulty. Then, we set the delegate
to self and lastly, the code adds the text field to the view. The same goes for the addToDoLabel
, creates a UILabel
object with the specified coordinates, set the text for the label then add the label to the main view. The interface will look like this:
The next step is to add the textFieldShouldReturn:
method to AddToDoViewController.m
. The delegate will call the method when the key Return is pressed. The code will insert a new to-do task into the storage.
- (BOOL)textFieldShouldReturn:(UITextField *)textField
{
// Create new instance of the entity
NSManagedObjectContext *context = [self managedObjectContext];
NSEntityDescription *entity = [NSEntityDescription entityForName: @"ToDo" inManagedObjectContext: context];
NSManagedObject *managedObject = [NSEntityDescription insertNewObjectForEntityForName: [entity name] inManagedObjectContext: context];
// Configure the new managed object
[managedObject setValue: [NSDate date] forKey: @"date"];
[managedObject setValue: [addToDoTextField text] forKey: @"text"];
// Save the context, return if error
NSError *error = nil;
if (![context save: &error]) {
/*
Replace this implementation with the code to handle the error appropriately.
*/
NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
abort();
}
[self dismissModalViewControllerAnimated: YES];
return YES;
}
The code create a context
pointer, then, creates an entity from the entity named ToDo
from the context
. Next, it inserts a managed object into the context using the object entity name, in this example: ToDo
. Then, we set the value of the date
and text
attribute using the current time by using [NSDate date]
, and the text of addToDoTextField
. It checks if it returned an error, if not, it will close the AddToDoViewController
by calling the dismissModalViewControllerAnimated:
method.
And of course, don’t forget to manage the memory:
- (void)dealloc
{
[addToDoTextField release];
[addToDoLabel release];
[managedObjectContext release];
[super dealloc];
}
- (void)viewDidUnload
{
[self setAddToDoTextField: nil];
[self setAddToDoLabel: nil];
[super viewDidUnload];
// Release any retained subviews of the main view.
// e.g. self.myOutlet = nil;
}
RootViewController
Going back to the RootViewController
, the last thing to do is modify it to use the new AddToDoViewController
and not to insert a new object on the storage. Before you can do that, we need to import the view controller on our RootViewController.m
:
#import "AddToDoViewController.h"
After adding the reference, we will now need to modify the - (NSFetchedResultsController *)fetchedResultsController
method to use the ToDo
entity instead of the Event entity. Search for the lines:
// Change both this line from
NSEntityDescription *entity = [NSEntityDescription entityForName:@"Event" inManagedObjectContext:self.managedObjectContext];
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"timeStamp" ascending:NO];
and change it to match this code:
// Change both this line
NSEntityDescription *entity = [NSEntityDescription entityForName:@"ToDo" inManagedObjectContext:self.managedObjectContext];
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"date" ascending:NO];
We will now need to modify the insertNewObject
method so it will call AddToDoViewController
instead of adding a new date.
- (void)insertNewObject
{
AddToDoViewController *viewController = [[AddToDoViewController alloc] init];
[viewController setManagedObjectContext: [self managedObjectContext]];
[self presentModalViewController: viewController animated: YES];
[viewController release];
}
The code simply creates an AddToDoViewController
, sets the managedObjectContext
, and calls the controller by using the - presentModalViewController:animated:
method.
The final thing to do is modify - configureCell:atIndexPath:
and - tableView:cellForRowAtIndexPath:
method to display our data by using the text
and date
attribute for the main text and detail text of the table’s cells.
configureCell:atIndexPath:
- (void)configureCell:(UITableViewCell *)cell atIndexPath:(NSIndexPath *)indexPath
{
NSManagedObject *managedObject = [self.fetchedResultsController objectAtIndexPath:indexPath];
[[cell textLabel] setText: [[managedObject valueForKey: @"text"] description]];
[[cell detailTextLabel] setText: [[managedObject valueForKey: @"date"] description]];
}
tableView:cellForRowAtIndexPath:
// Customize the appearance of table view cells.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = @"Cell";
// Change UITableViewCellStyleDefault to UITableViewCellStyleSubtitle
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithStyle: UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier] autorelease];
}
// Configure the cell.
[self configureCell:cell atIndexPath:indexPath];
return cell;
}
The code configures the cell by setting it’s textLabel
‘s text and detailTextLabel
‘s text. Next, we dequeue a cell, if it fails, we create a new cell using the UITableViewCellStyleSubtitle
so we can display both the text and the date.
Analyze the code by going to Product > Analyze or by simply typing ⌘-Shift-B. It should not return any error. You can now add a new to-do entry by clicking the “+” button.
The main view with stored data should look like this:
Wrapping Up
Here, you learned the basic of Core Data and it’s architecture and you learned the basic terminology necessary to understand Core Data.
Leave a Reply