Adding Events
The goal of this chapter is to create the application logic to allow the user to create new event objects and display them in the user interface.
Implementing the addEvent Method
You create new Event objects in the addEvent method. Recall that it’s invoked when the user taps the Add button (see “Implementing viewDidLoad”). There are several parts to the method. It has to:
Get the current location
Create an
Eventobject and configure it using the current location informationSave the
EventobjectUpdate the events array and the user interface
First, though, declare the addEvent method.
>> Add a declaration of the addEvent method to the RootViewController header file:
- (void)addEvent; |
Get the Current Location
When you create a new Event object, you need to set its location. You get the location from the location manager. If it’s not able to provide a location, then don’t continue.
>> Add the following to RootViewController implementation file:
- (void)addEvent { |
CLLocation *location = [locationManager location]; |
if (!location) { |
return; |
} |
} |
Create and Configure the Event object
You typically create a managed object using a convenience method—insertNewObjectForEntityForName:inManagedObjectContext:—of NSEntityDescription, which returns a properly initialized instance of the correct class for the entity you specify, inserted into the managed object context. (For more about the initialization process, see “Managed Objects” in Core Data Programming Guide). After you’ve created the object, you can set its property values.
You get the latitude and longitude from the location as scalar values, so you need to convert these to NSNumber objects for the Event object. You could get the time stamp from the location as well, but this is a constant value in Simulator. Instead, here you can use date method of NSDate to get a date object representing the current date and time.
>> Add the following code at the end of the current implementation of addEvent:
// Create and configure a new instance of the Event entity. |
Event *event = (Event *)[NSEntityDescription insertNewObjectForEntityForName:@"Event" inManagedObjectContext:managedObjectContext]; |
CLLocationCoordinate2D coordinate = [location coordinate]; |
[event setLatitude:[NSNumber numberWithDouble:coordinate.latitude]]; |
[event setLongitude:[NSNumber numberWithDouble:coordinate.longitude]]; |
[event setCreationDate:[NSDate date]]; |
This illustrates the general technique you use to update a managed object, whether it’s a new managed object you created, or one you fetch from the store. You simply use accessor methods, just as you would any other Cocoa object. Importantly, though, changes are not pushed to the persistent store until you explicitly save the context.
Save the New Event
Remember that the managed object context acts like a scratch pad (see “Managed Objects and the Managed Object Context”). Whatever changes you make—whether editing property values or adding or deleting whole objects—aren’t actually committed to the persistent store (file) until you save the context. Typically, in an iOS application, you save changes as soon as the user has made them.
>> Add the following code at the end of the current implementation of addEvent:
NSError *error = nil; |
if (![managedObjectContext save:&error]) { |
// Handle the error. |
} |
In common with several Core Data methods, the NSManagedObjectContext save: method takes an error parameter and returns a Boolean value to indicate success or failure. The situation is really no different from that in any other application; it’s just that the return value from the save: method and the error parameter tend to bring into sharper focus the possibility of a problem occurring.
Handling Errors
It’s up to you to decide how you handle a Core Data error.
In a scenario as simple as that described in “Save the New Event”—where the only change you expect is the addition of a single object—if the data can’t be saved it’s likely to be indicative of some sort of catastrophic failure from which recovery might be difficult or impossible. In this situation you might just present an alert sheet telling the user to restart the application.
In a more complex scenario, the user might have changed property values, or added or deleted managed objects in such a way that either an individual object is in an inconsistent state (validation fails) or the object graph as a whole is inconsistent. If you have more than one managed object context, it’s also possible that the persistent store was updated when changes made in a different context were committed and so the objects in the current context are inconsistent with the corresponding records in the store.
In general, you can interrogate the error object to find out what went wrong.
You should think carefully about what the user experience should be in the event of an error occurring. What information should you present to the user? What options might you give them for recovering from the problem? These are not questions that Core Data is able to answer.
Update the Events Array and the Table View
Finally, you need to add the new Event object to the events array, then update the table view. Since this is a new Event, and Events are displayed with most recent events at the top of the list, add the new object to the beginning of the events array, add a corresponding row to the top of the table view, then scroll the table view to show the new row.
>> Add the following code at the end of the current implementation of addEvent:
[eventsArray insertObject:event atIndex:0]; |
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:0 inSection:0]; |
[self.tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] |
withRowAnimation:UITableViewRowAnimationFade]; |
[self.tableView scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:0] atScrollPosition:UITableViewScrollPositionTop animated:YES]; |
The next task is to complete the implementation of the table view data-source methods to display the events.
Displaying Events in the Table View
You need to update two table view data-source methods to display the events.
First simply tell the table view how many events to display.
>> Update the implementation of tableView:numberOfRowsInSection: to return the number of objects in the events array (there’s only one section, so you don’t need to test the section number):
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { |
return [eventsArray count]; |
} |
Next, you need to configure the table view cells to display information about each event. You’ll see there is a nontrivial amount of code, but most of it is related to user interface and display rather than data management.
>> Replace the implementation of tableView:(UITableView *)tableView cellForRowAtIndexPath: with the following:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { |
// A date formatter for the time stamp. |
static NSDateFormatter *dateFormatter = nil; |
if (dateFormatter == nil) { |
dateFormatter = [[NSDateFormatter alloc] init]; |
[dateFormatter setTimeStyle:NSDateFormatterMediumStyle]; |
[dateFormatter setDateStyle:NSDateFormatterMediumStyle]; |
} |
// A number formatter for the latitude and longitude. |
static NSNumberFormatter *numberFormatter = nil; |
if (numberFormatter == nil) { |
numberFormatter = [[NSNumberFormatter alloc] init]; |
[numberFormatter setNumberStyle:NSNumberFormatterDecimalStyle]; |
[numberFormatter setMaximumFractionDigits:3]; |
} |
static NSString *CellIdentifier = @"Cell"; |
// Dequeue or create a new cell. |
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier]; |
if (cell == nil) { |
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier] autorelease]; |
} |
Event *event = (Event *)[eventsArray objectAtIndex:indexPath.row]; |
cell.textLabel.text = [dateFormatter stringFromDate:[event creationDate]]; |
NSString *string = [NSString stringWithFormat:@"%@, %@", |
[numberFormatter stringFromNumber:[event latitude]], |
[numberFormatter stringFromNumber:[event longitude]]]; |
cell.detailTextLabel.text = string; |
return cell; |
} |
Build and Test
If you build the project, it should compile without errors. The application should also launch and run correctly, until you tap the Add button—at which point it will crash. This is because the events array hasn’t been created yet.
>> Solely for testing purposes, add the following line to the end of the RootViewController object’s implementation of viewDidLoad:
eventsArray = [[NSMutableArray alloc] init]; |
If you build and run now, you should find that if you tap the Add button new events are displayed in the table view. If you quit and relaunch the application, though, you won’t see the list of Events when it starts up. To remedy this, you need to populate the events array on launch with the existing Event objects. This is your task in the next chapter. Before doing that, restore the project to its pre-testing state.
>> Delete the line you added for testing.
Core Data Recap
There was a lot of code in this chapter, and not much related directly to Core Data. The important points are that:
You typically create a new managed object using the convenience method
insertNewObjectForEntityForName:inManagedObjectContext:ofNSEntityDescription.This method ensures that you get a properly initialized instance of the class that represents the entity you specify.
To commit changes to the persistent store, you need to save the managed object context.
The context acts as a scratch pad; if you add or modify objects, the changes are held in memory until you invoke
save:. It’s up to you to decide how to deal with any error that might occur during a save operation.You get and set a managed object’s property values using accessor methods, just as you would any other object.
You can also use key-value coding, just as you would any other object, but using accessor methods is much more efficient (see “Using Managed Objects” in Core Data Programming Guide).
© 2012 Apple Inc. All Rights Reserved. (Last updated: 2012-12-13)