These features include dynamically calculating property values at runtime, defaulting data at runtime, and single and multiple fi eld validation.. The following is the code for the isOve
Trang 1Here are the implementations for the numberOfSectionsInTableView and tableView:
numberOfRowsInSection: methods:
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { return [[fetchedResultsController sections] count];
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
id < NSFetchedResultsSectionInfo > sectionInfo = [[fetchedResultsController sections] objectAtIndex:section];
return [sectionInfo numberOfObjects];
}
LocationTasksViewController.m
The tableView:cellForRowAtIndexPath: and tableView:didSelectRowAtIndexPath: methods are the same as in the RootViewController You generate the cell text the same way and the behavior when a user selects a row is the same The following is the code for the tableView:
cellForRowAtIndexPath: and tableView:didSelectRowAtIndexPath: methods:
// Customize the appearance of table view cells.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = @”Cell”;
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) { cell = [[[UITableViewCell alloc]
initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
} // Configure the cell.
Task *managedTaskObject = [fetchedResultsController objectAtIndexPath:indexPath];
cell.textLabel.text = managedTaskObject.text;
// Change the text color if the task is overdue
if (managedTaskObject.isOverdue==[NSNumber numberWithBool: YES]) {
cell.textLabel.textColor = [UIColor redColor];
} else { cell.textLabel.textColor = [UIColor blackColor];
} return cell;
}
Trang 2- (void)tableView:(UITableView *)tableView
didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
// Deselect the currently selected row according to the HIG
[tableView deselectRowAtIndexPath:indexPath animated:NO];
// Navigation logic may go here for example, create and push
// another view controller.
Task *managedObject =
[fetchedResultsController objectAtIndexPath:indexPath];
ViewTaskController* taskController =
[[ViewTaskController alloc]
initWithStyle:UITableViewStyleGrouped];
taskController.managedTaskObject=managedObject;
taskController.managedObjectContext = self.managedObjectContext;
[self.navigationController pushViewController:taskController animated:YES];
[taskController release];
}
LocationTasksViewController.m
There is one additional method that you need to implement tableView:
titleForHeaderInSection: This method generates the titles for the sections of the table Again,
you will use an NSFetchedResultsController to get these titles The fetched results controller
maintains a sections array that contains the list of sections in the result set You simply need to
get the item out of the array that corresponds to the section that the TableView is asking for
Here is the code:
- (NSString *)tableView:(UITableView *)tableView
titleForHeaderInSection:(NSInteger)section
{
id < NSFetchedResultsSectionInfo > sectionInfo =
[[fetchedResultsController sections] objectAtIndex:section];
return [sectionInfo name];
}
LocationTasksViewController.m
Finally, modify the dealloc method to release your instance variables:
- (void)dealloc {
[fetchedResultsController release];
[managedObjectContext release];
[super dealloc];
}
LocationTasksViewController.m
Trang 3Now that you have built the LocationTasksViewController , you need to modify the RootViewController to call your new location controller when the user taps the Location button
In the RootViewController.m implementation fi le, add a #import directive to import the new controller:
#import “LocationTasksViewController.h”
Modify the code in the locationButtonPressed method to create an instance of your new controller and push it on to the navigation stack:
-(IBAction)locationButtonPressed:(id)sender {
NSLog(@”locationButtonPressed”);
LocationTasksViewController* ltvc = [[LocationTasksViewController alloc]
initWithStyle:UITableViewStylePlain];
ltvc.managedObjectContext = self.managedObjectContext;
[self.navigationController pushViewController:ltvc animated:YES];
[ltvc release];
}
RootViewController.m
You are now ready to build and run the application You should be able to use all of the buttons
at the bottom of the RootViewController to fi lter the data, sort the data, and bring up the tasks grouped by location screen
IMPLEMENTING CUSTOM MANAGED OBJECTS
Up until this point, you haven ’ t used any of the features of an NSManagedObject subclass You could have written all of the Core Data code that you have seen so far in this chapter using key value coding and generic NSManagedObject s
In this section, you learn about the additional features that you can implement by using custom subclasses of NSManagedObject These features include dynamically calculating property values at runtime, defaulting data at runtime, and single and multiple fi eld validation
Coding a Dynamic Property
You can use dynamic properties to implement properties that you need to calculate at runtime In the data modeler, you should mark dynamic properties as Transient because you will compute their value at runtime and will not save this value in the data store
In this example, you will code an isOverdue property for the Task object This property should return YES if a task is overdue and NO if it is not You may notice that when you declare a Boolean property in the data modeler, Xcode translates the property into an NSNumber in the generated code This is not a problem because there is a class method in the NSNumber class that returns an NSNumber representation of a BOOL
Trang 4The following is the code for the isOverdue accessor method:
- (NSNumber*) isOverdue
{
BOOL isTaskOverdue = NO;
NSDate* today = [NSDate date];
if (self.dueDate != nil) {
if ([self.dueDate compare:today] == NSOrderedAscending)
isTaskOverdue=YES;
}
return [NSNumber numberWithBool:isTaskOverdue];
}
Task.m
This code simply compares the dueDate in the current Task object against the current system date
If the dueDate is earlier than today, the code sets the isTaskOverdue variable to YES The code then
uses the numberWithBool method to return an NSNumber that corresponds to the Boolean result
If you run the application now, any tasks that occurred in the past should turn red in the
RootViewController
Defaulting Data at Runtime
Just as you can dynamically generate property values at runtime, you can also generate default
values at runtime There is a method called awakeFromInsert that the Core Data framework calls
the fi rst time that you insert an object into the managed object context
Subclasses of NSManagedObject can implement awakeFromInsert to set default values for
properties It is critical that you call the superclass implementation of awakeFromInsert before
doing any of your own implementation
Another consideration is that you should always use primitive accessor methods to modify property
values while you are in the awakeFromInsert method Primitive accessor methods are methods
of the Managed Objects that are used to set property values but that do not notify other classes
observing your class using key - value observing This is important because if you modify a property
and your class sends out a notifi cation that results in another class modifying the same property,
your code could get into in an endless loop
You can set primitive values in your code using key value coding and the setPrimitiveValue:
forKey: method A better way is to defi ne custom primitive accessors for any properties that
you need to access in this way In this case, you will be modifying the dueDate property in
awakeFromInsert so you need to defi ne a primitive accessor for the dueDate Fortunately, Core
Data defi nes these properties for you automatically at runtime All that you need to do is declare the
property in the Task header:
@property (nonatomic, retain) NSDate * primitiveDueDate;
Trang 5Then, add an @dynamic directive in the implementation fi le to indicate that Core Data will dynamically generate this property at runtime:
@dynamic primitiveDueDate;
Now, you can set the dueDate property in the awakeFromInsert method without fear of any side effects that may occur because of the possibility that other classes are observing the Task for changes
Implement the awakeFromInsert method to create an NSDate object three days from the current date and use the primitive property to set the default date The following is the code for the awakeFromInsert method:
- (void)awakeFromInsert {
// Core Data calls this function the first time the receiver // is inserted into a context.
[super awakeFromInsert];
// Set the due date to 3 days from now (in seconds) NSDate* defualtDate = [[NSDate alloc]
initWithTimeIntervalSinceNow:60*60*24*3];
// Use custom primitive accessor to set dueDate field self.primitiveDueDate = defualtDate ;
[defualtDate release];
}
Task.m
Before you build and run the application, delete the old app from the simulator fi rst because you may have tasks that do not have due dates Now you can run the application New tasks that you create will now have a default due date set three days into the future You should be able to use the “ Tasks due sooner than this one ” button on the task detail screen now because all tasks will have defaulted due dates
Validating a Single Field
Single - fi eld validation in a custom class is straightforward Core Data will automatically call a method called validate Xxxx if you have implemented the method The Xxxx is the name of your property with the fi rst letter capitalized The method signature for the validate method for the dueDate fi eld looks like this:
-(BOOL)validateDueDate:(id *)ioValue error:(NSError **)outError{
A single fi eld validation method should return YES if the validation is successful and NO if it failed
The method accepts an id* , which is the value that you are testing for validity and an NSError**
that you should use to return an NSError object if the validation fails
Trang 6Because this method receives an id* , you could modify the object that is passed into the validation
function, but you should never do this Users of a class would not expect validation to modify the
object that was submitted for validation Modifying the object that was passed in would create an
unexpected side effect Creating side effects is usually a poor design choice which you should avoid
You should treat the object passed in for validation as read only
In the validation of the dueDate of the Task object, you are going to enforce a rule that assigning
due dates that have occurred in the past is invalid Here is the implementation of the dueDate
validation function:
-(BOOL)validateDueDate:(id *)ioValue error:(NSError **)outError{
// Due dates in the past are not valid.
// enforce that a due date has to be > = today’s date
if ([*ioValue compare:[NSDate date]] == NSOrderedAscending) {
if (outError != NULL) {
NSString *errorStr = [[[NSString alloc] initWithString:
@”Due date must be today or later”]
autorelease];
NSDictionary *userInfoDictionary =
[NSDictionary dictionaryWithObject:errorStr
forKey:@”ErrorString”];
NSError *error =
[[[NSError alloc] initWithDomain:TASKS_ERROR_DOMAIN
code:DUEDATE_VALIDATION_ERROR_CODE
userInfo:userInfoDictionary] autorelease];
*outError = error;
}
return NO;
}
else {
return YES;
}
}
Task.m
The fi rst thing that you do is check the date that you receive as an input parameter and compare it
to the current system date If the comparison fails, you create an error string that you will return to
the caller in the NSError object Next, you add the error string to an NSDictionary object that you
pass back to the class user as the userInfo in the NSError object Then, you allocate and initialize
an NSError object with an error domain and error code The domain and code are custom values
used to identify your error and can be any values that you like For this sample, I have defi ned them
in the Task.h header fi le like this:
#define TASKS_ERROR_DOMAIN @”com.Wrox.Tasks”
#define DUEDATE_VALIDATION_ERROR_CODE 1001
Task.h
Trang 7You pass the userInfo dictionary that you created to hold the error string to the initializer of the NSError object Users of your Task class can interrogate the userInfo dictionary that they receive
in the NSError to get details about the problem and act accordingly Finally, you return NO to indicate that validation has failed
If the validation succeeds, the code simply returns YES Run the application now and try to set the due date for a task to a date in the past You should get
an error indicating that this is invalid
Multi - Field Validation
Multi - fi eld validation is slightly more complicated than single - fi eld validation Core Data will call two methods automatically if they exist: validateForInsert and validateForUpdate Core Data calls validateForInsert when you insert an object into the context for the fi rst time When you update an existing object, Core Data calls validateForUpdate
If you want to implement a validation rule that runs both when an object is inserted or updated,
I recommend writing a new validation function and then calling that new function from both the validateForInsert and validateForUpdate methods The example follows this approach
In this sample, you will be enforcing a multi - fi eld validation rule that says that high - priority tasks must have a due date within the next three days Any due date farther in the future should cause an error You cannot accomplish this within a single fi eld validation rule because you need to validate both that the task is high priority and that the due date is within a certain range In the Task.h header fi le, add a new method declaration for the function that you will call to validate the data:
- (BOOL)validateAllData:(NSError **)error;
Here is the function that enforces the rule:
- (BOOL)validateAllData:(NSError **)outError {
NSDate* compareDate = [[[NSDate alloc] initWithTimeIntervalSinceNow:60*60*24*3] autorelease];
// Due dates for hi-pri tasks must be today, tomorrow, or the next day.
if ([self.dueDate compare:compareDate] == NSOrderedDescending & &
[self.priority intValue]==3) {
if (outError != NULL) { NSString *errorStr = [[[NSString alloc] initWithString:
@”Hi-pri tasks must have a due date within two days of today”]
autorelease];
NSDictionary *userInfoDictionary = [NSDictionary dictionaryWithObject:errorStr forKey:@”ErrorString”];
NSError *error = [[[NSError alloc] initWithDomain:TASKS_ERROR_DOMAIN code:PRIORITY_DUEDATE_VALIDATION_ERROR_CODE userInfo:userInfoDictionary] autorelease];
*outError = error;
}
Trang 8return NO;
}
else {
return YES;
}
}
Task.m
The code fi rst generates a date to which to compare the selected dueDate Then, the code checks to
see if the dueDate chosen is greater than this compare date and that the priority of the task is high
If the data meets both of these criteria, it is invalid and you generate an NSError object just like in
the last section A new error code is used and should be added to the Task.h header:
#define PRIORITY_DUEDATE_VALIDATION_ERROR_CODE 1002
The code then returns NO to indicate that the validation has failed If the validation is successful, the
method returns YES
Now, you have to add the two validation methods that Core Data calls and code them to call your
validateAllData method:
- (BOOL)validateForInsert:(NSError **)outError
{
// Call the superclass validateForInsert first
if ([super validateForInsert:outError]==NO)
{
return NO;
}
// Call out validation function
if ([self validateAllData:outError] == NO)
{
return NO;
}
else {
return YES;
}
}
- (BOOL)validateForUpdate:(NSError **)outError
{
// Call the superclass validateForUpdate first
if ([super validateForUpdate:outError]==NO)
{
return NO;
}
// Call out validation function
if ([self validateAllData:outError] == NO)
{
return NO;
Trang 9} else { return YES;
} }
Task.m
First, both of these methods call their superclass counterpart method You have to call the superclass method because that method handles validation rules implemented in the model and calls the single - fi eld validation methods If the superclass validation routine is successful, the methods go
on to call your validateAllData method
Build and run the application If you try to set the due date for a high - priority task to more than two days in the future, or if you try to set the priority of a task to high that has a due date more than two days in the future, you will get an error
MOVING FORWARD
This chapter covered a lot of material You learned how to implement the Core Data concepts that you learned about in the last chapter
Now you have a fully functioning Core Data – based application that demonstrates many of the features of Core Data You can use this application like a sandbox to play with these features or implement new functionality in the Tasks application on your own
Now that you have built an entire application, I hope that you have confi dence in your ability to implement an application using the Core Data framework You should now be able to use Core Data effectively in your own applications
In the next chapter, you will learn more about some features of Cocoa Touch that you often use with Core Data You will explore key value coding and key value observing You will also learn more about NSPredicates and how to implement sort descriptors using your own custom classes
Finally, you will learn how to migrate your existing data from one Core Data model version to another