[self.navigationController pushViewController:elc animated:YES]; [elc release]; break; } case 4: { UIAlertView* alert = [[[UIAlertView alloc] initWithTitle:@”Hi-Pri Tasks” message:ni
Trang 1// Create a date formatter to format the date from the picker NSDateFormatter* df = [[NSDateFormatter alloc] init];
[df setDateStyle:NSDateFormatterLongStyle];
cell.textLabel.text = [df stringFromDate:datePicker.date ];
[df release];
} return cell;
}
EditDateController.m
You can see that you are once again using an NSDateFormatter to convert the NSDate object into a string for display in the TableViewCell
In the dealloc method, release the member variables that correspond to the class properties:
- (void)dealloc { [managedTaskObject release];
[managedObjectContext release];
[datePicker release];
[tv release];
[super dealloc];
}
EditDateController.m
Finishing Up the Editing Controllers
You have now fi nished implementing all of the edit controllers The last thing that you need to do before you are ready to run the program is go back and add code to the ViewTaskController.m to use the new subcontrollers to edit the task data
In ViewTaskController.m , add an import statement for each subcontroller:
#import “EditTextController.h”
#import “EditPriorityController.h”
#import “EditDateController.h”
#import “EditLocationController.h”
ViewTaskController.m
You will also need to add an import for the App delegate because you need to get a reference to the managedObjectModel in order to use your stored fetch request:
#import “TasksAppDelegate.h”
You can now implement the didSelectRowAtIndexPath method that you left out when you were implementing the ViewTaskController earlier in the chapter This method runs when a user selects
a row in the table The method should display the correct edit View Controller based on which row the user selects
Trang 2200 ❘ CHAPTER 7 BUILDING A CORE DATA APPLICATION
The last two buttons in the table do not use edit View Controllers The Hi - Pri Tasks button
demonstrates how to use a fetched property to get a list of high - priority tasks The “ Tasks due
sooner ” button shows you how to use a stored fetch request
The following is the code for didSelectRowAtIndexPath :
- (void)tableView:(UITableView *)tableView
didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
// Deselect the currently selected row according to the HIG
[tableView deselectRowAtIndexPath:indexPath animated:NO];
// Based on the selected row, choose which controlelr to push
switch (indexPath.row) {
case 0:
{
EditTextController* etc = [[EditTextController alloc]
initWithStyle:UITableViewStyleGrouped];
etc.managedObject = self.managedTaskObject;
etc.keyString=@”text”;
etc.managedObjectContext = self.managedObjectContext;
[self.navigationController pushViewController:etc animated:YES];
[etc release];
break;
}
case 1:
{
EditPriorityController* epc =
[[EditPriorityController alloc]
initWithStyle:UITableViewStyleGrouped];
epc.managedTaskObject = self.managedTaskObject;
epc.managedObjectContext = self.managedObjectContext;
[self.navigationController pushViewController:epc animated:YES];
[epc release];
break;
}
case 2:
{
EditDateController* edc = [[EditDateController alloc] init];
edc.managedTaskObject = self.managedTaskObject;
edc.managedObjectContext = self.managedObjectContext;
[self.navigationController pushViewController:edc animated:YES];
[edc release];
break;
}
case 3:
{
EditLocationController* elc = [[EditLocationController alloc] init];
elc.managedObjectContext = self.managedObjectContext;
elc.managedTaskObject = self.managedTaskObject;
Trang 3[self.navigationController pushViewController:elc animated:YES];
[elc release];
break;
} case 4:
{ UIAlertView* alert = [[[UIAlertView alloc] initWithTitle:@”Hi-Pri Tasks”
message:nil delegate:self cancelButtonTitle:@”OK”
otherButtonTitles:nil ] autorelease];
// Use Fetched property to get a list of high-pri tasks NSArray* highPriTasks = managedTaskObject.highPriTasks;
NSMutableString* alertMessage = [[[NSMutableString alloc] init] autorelease];
// Loop through each hi-pri task to create the string for // the message
for (Task * theTask in highPriTasks) {
[alertMessage appendString:theTask.text];
[alertMessage appendString:@”\n”];
} alert.message = alertMessage;
[alert show];
break;
} case 5:
{ UIAlertView* alert = [[[UIAlertView alloc] initWithTitle:@”Tasks due sooner”
message:nil delegate:self cancelButtonTitle:@”OK”
otherButtonTitles:nil ] autorelease];
NSMutableString* alertMessage = [[[NSMutableString alloc] init] autorelease];
// need to get a handle to the managedObjectModel to use the stored // fetch request
TasksAppDelegate* appDelegate = [UIApplication sharedApplication].delegate;
NSManagedObjectModel* model = appDelegate.managedObjectModel;
// Get the stored fetch request NSDictionary* dict =
[[NSDictionary alloc]
Trang 4202 ❘ CHAPTER 7 BUILDING A CORE DATA APPLICATION
initWithObjectsAndKeys:managedTaskObject.dueDate,
@”DUE_DATE”,nil];
NSFetchRequest* request =
[model fetchRequestFromTemplateWithName:@”tasksDueSooner”
substitutionVariables:dict];
[dict release];
NSError* error;
NSArray* results =
[managedObjectContext executeFetchRequest:request error: & error];
// Loop through eachtask to create the string for the message
for (Task * theTask in results)
{
[alertMessage appendString:theTask.text];
[alertMessage appendString:@”\n”];
}
alert.message = alertMessage;
[alert show];
break;
}
default:
break;
}
}
ViewTaskController.m
In the code for this method, cases 0 through 3 are very similar Each creates an instance of the
appropriate edit controller, populates the necessary properties, and then pushes the controller onto
the navigation stack
Case 4 implements the Hi - Pri Tasks feature, which uses the highPriTasks fetched property of the
Task object that you defi ned in the previous chapter If you don ’ t remember, this property simply
returns a list of all tasks that are marked as High Priority
The interesting thing to notice about using the fetched property is that it returns an array of objects
instead of a set You can also see that using a fetched property is as simple as using a regular
property The code loops through each Task object returned from the fetched property and appends
the Task name to a string The code then displays the string using a UIAlertView
Case 5 uses a stored fetch request to get a list of tasks that are due sooner than the current task
There are a couple of points of interest when using a stored fetch request First, you need a reference
to the managed object model because stored fetch requests reside in the model and not in your
managed object class
Next, if you specifi ed substitution variables in the fetch request, you need to provide them to the
fetch request in an NSDictionary that contains the objects and the keys You can see that you are
Trang 5creating an NSDictionary using the dueDate property of the current Task object and the key text DUE_DATE The key text is the same as the variable name that you specifi ed in the previous chapter when defi ning the stored fetch request
The code then creates an NSFetchRequest It uses the fetchRequestFromTemplateWithName:
method by supplying the name of the stored fetch request tasksDueSooner and the NSDictionary containing your substitution variables and keys
The code then executes the fetch request against the context Finally, the code iterates over the results, creating a string with the text from each returned Task object, and displays the string using
a UIAlertView You are now ready to build and run the application You should get a clean build with no errors
or warnings You should be able to add new tasks and edit all of the attributes of your tasks You should also be able to create new locations and delete existing locations Clicking the “ hi - pri tasks ” button in the task viewer will use the fetched property to display all of your high - priority tasks The “ Tasks due sooner ” feature won ’ t quite work yet because you have to implement date defaulting in the Task object If you try to select “ Tasks due sooner ” and all of your tasks do not have due dates, you will get an error
DISPLAYING RESULTS IN THE ROOTVIEWCONTROLLER
In this section, you are going to implement the fi ltering and sorting buttons on the RootViewController The easiest way to implement this functionality is to modify the sort descriptor or predicate of the fetched results controller, and then execute the fetch
Sorting Results with NSSortDescriptor
When the user taps the Asc or Dsc buttons, the toolbarSortOrderChanged method will run
In this method, you get a reference to the fetch request used by the fetched results controller and change the sort descriptor to match the sort order that the user selected Then, you need to tell the fetchedResultsController to perform the fetch with the revised sort descriptor Finally, you tell the TableView to reload its data The following is the code for the toolbarSortOrderChanged method:
-(IBAction)toolbarSortOrderChanged:(id)sender;
{ NSLog(@”toolbarSortOrderChanged”);
// Get the fetch request from the controller and change the sort descriptor NSFetchRequest* fetchRequest = self.fetchedResultsController.fetchRequest;
// Edit the sort key based on which button was pressed BOOL ascendingOrder = NO;
UIBarButtonItem* button = (UIBarButtonItem*) sender;
if ([button.title compare:@”Asc”]== NSOrderedSame) ascendingOrder=YES;
else ascendingOrder=NO;
Trang 6
204 ❘ CHAPTER 7 BUILDING A CORE DATA APPLICATION
NSSortDescriptor *sortDescriptor =
[[NSSortDescriptor alloc] initWithKey:@”text” ascending:ascendingOrder];
NSArray *sortDescriptors =
[[NSArray alloc] initWithObjects:sortDescriptor, nil];
[sortDescriptor release];
[fetchRequest setSortDescriptors:sortDescriptors];
[sortDescriptors release];
NSError *error = nil;
if (![[self fetchedResultsController] performFetch: & error]) {
NSLog(@”Unresolved error %@, %@”, error, [error userInfo]);
abort();
}
[taskTableView reloadData];
}
RootViewController.m
You use the same method regardless of which sort button the user tapped At runtime, the code
checks the title of the button to determine if the user wants the data sorted in ascending or
descending order Then, you use the compare method of the NSString class to perform the sort
You can also add additional sort descriptors to sort your data using multiple fi elds Core Data
applies the sort descriptors to the results in the order that you specify them in the array that
you pass into the setSortDescriptors function You will learn more about implementing sort
descriptors in the next chapter
Filtering Results with NSPredicate
When the user taps the Hi Pri button, your code should fi lter the list of tasks to show only high
priority tasks Conversely, when the user selects the All button, you need to clear the fi lter to show
all of the tasks again You can build these features as you did with the sorting functionality in the
last section However, instead of modifying the sort descriptor, you will be modifying the fetch
request ’ s predicate You can use predicates to fi lter data in all sorts of data structures Their use is
not limited to Core Data You learn more about predicates in the next chapter
The toolbarFilterHiPri method needs to set the predicate used by the fetch request to return
tasks with a priority of 3 Then, the method has to tell the TableView to reload its data The
following is the code for the toolbarFilterHiPri method:
-(IBAction)toolbarFilterHiPri:(id)sender{
NSLog(@”toolbarFilterHiPri”);
// Change the fetch request to display only high pri tasks
// Get the fetch request from the controller and change the predicate
NSFetchRequest* fetchRequest = self.fetchedResultsController.fetchRequest;
NSPredicate *predicate = [NSPredicate predicateWithFormat:@”priority == 3”];
[fetchRequest setPredicate:predicate];
Trang 7
NSError *error = nil;
if (![[self fetchedResultsController] performFetch: & error]) { NSLog(@”Unresolved error %@, %@”, error, [error userInfo]);
abort();
} [taskTableView reloadData];
}
RootViewController.m
The code gets a reference to the predicate used by the fetchedResultsController Then, you create a new predicate with the criteria of priority == 3 Next, you set the predicate of the fetch request to your new predicate, perform the fetch, and tell the table to reload its data
The toolbarFilterAll method simply removes the predicate from the fetch request You do that
by setting the predicate to nil , as follows:
-(IBAction)toolbarFilterAll:(id)sender {
NSLog(@”toolbarFilterAll”);
// Change the fetch request to display all tasks // Get the fetch request from the controller and change the predicate NSFetchRequest* fetchRequest = self.fetchedResultsController.fetchRequest;
// nil out the predicate to clear it and show all objects again [fetchRequest setPredicate:nil];
NSError *error = nil;
if (![[self fetchedResultsController] performFetch: & error]) { NSLog(@”Unresolved error %@, %@”, error, [error userInfo]);
abort();
} [taskTableView reloadData];
}
RootViewController.m
This looks very similar to the toolbarFilterHiPri method except that here, you set the predicate
to nil instead of creating a new predicate to apply to the fetchRequest Removing the predicate effectively un - fi lters the data
GENERATING GROUPED TABLES USING
THE NSFETCHEDRESULTSCONTROLLER
In Chapter 3, you learned how to use the UILocalizedIndexedCollation class to create a TableView that organized data within sections When you work with Core Data, you can use the NSFetchedResultsController to achieve the same results In this section, you will build the
Trang 8206 ❘ CHAPTER 7 BUILDING A CORE DATA APPLICATION
LocationTasksViewController The application displays the LocationTasksViewController
when the user selects the Location button on the RootViewController The
LocationTasksViewController displays all of the tasks grouped by location
The fi rst step is to create a new UITableviewController without XIB called
LocationTasksViewController Modify the header fi le by adding instance variables and
properties for the context and a fetched results controller Also, mark your class as implementing
the NSFetchedResultsControllerDelegate protocol The header should look like Listing 7 - 7
#import < UIKit/UIKit.h >
@interface LocationTasksViewController :
UITableViewController < NSFetchedResultsControllerDelegate >
NSManagedObjectContext *managedObjectContext;
NSFetchedResultsController *fetchedResultsController;
}
@property (nonatomic, retain) NSManagedObjectContext *managedObjectContext;
@property (nonatomic, retain)
NSFetchedResultsController *fetchedResultsController;
@end
Moving into the implementation fi le, add #import directives for the Location.h , Task.h , and
ViewTaskController.h headers:
#import “Location.h”
#import “Task.h”
#import “ViewTaskController.h”
LocationTasksViewController.m
Next, synthesize the properties that you declared in the header:
@synthesize managedObjectContext,fetchedResultsController;
Now, implement viewDidLoad to perform the fetch on the fetched results controller and set the title
of the screen in the nav bar:
- (void)viewDidLoad {
[super viewDidLoad];
NSError* error;
if (![[self fetchedResultsController] performFetch: & error]) {
NSLog(@”Unresolved error %@, %@”, error, [error userInfo]);
abort();
Trang 9} // set the title to display in the nav bar self.title = @”Tasks by Location”;
}
LocationTasksViewController.m
In viewDidUnload , set the properties that you declared in the header to nil :
- (void)viewDidUnload { self.managedObjectContext=nil;
self.fetchedResultsController = nil;
[super viewDidUnload];
}
LocationTasksViewController.m
Next, you will write the fetchedResultsController accessor method You have implemented this method several times before The difference in this case is that you will need to specify a sectionNameKeyPath when you initialize the NSFetchedResultsController
The sectionNameKeyPath parameter allows you to specify a key path that the fetched results controller will use to generate the sections for your table The fetched results controller will contain the entire set of Task objects You want the tasks grouped by location Remember that the Task object has a location property that refers to a related Location object The Location object has
a name property that contains the name of the Location You really want the tasks grouped by the name property of the contents of the location property Because you are holding a reference to a Task object, the key path to the Location ’ s name property is location.name The following is the code for the fetchedResultsController accessor:
- (NSFetchedResultsController *)fetchedResultsController {
if (fetchedResultsController != nil) { return fetchedResultsController;
} /*
Set up the fetched results controller
*/
// Create the fetch request for the entity
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
// Edit the entity name as appropriate
NSEntityDescription *entity = [NSEntityDescription entityForName:@”Task”
inManagedObjectContext:managedObjectContext];
[fetchRequest setEntity:entity];
// Edit the sort key as appropriate
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@”location.name”
ascending:YES];
Trang 10208 ❘ CHAPTER 7 BUILDING A CORE DATA APPLICATION
NSArray *sortDescriptors =
[[NSArray alloc] initWithObjects:sortDescriptor, nil];
[fetchRequest setSortDescriptors:sortDescriptors];
// Edit the section name key path and cache name if appropriate
// nil for section name key path means “no sections”
NSFetchedResultsController *aFetchedResultsController =
[[NSFetchedResultsController alloc]
initWithFetchRequest:fetchRequest
managedObjectContext:managedObjectContext
sectionNameKeyPath:@”location.name” cacheName:@”Task”];
aFetchedResultsController.delegate = self;
self.fetchedResultsController = aFetchedResultsController;
[aFetchedResultsController release];
[fetchRequest release];
[sortDescriptor release];
[sortDescriptors release];
return fetchedResultsController;
}
LocationTasksViewController.m
If you look at the defi nition of the sort descriptor, you can see that you are also using the key path to
the Location object ’ s name property In order for the fetched results controller to create the sections
properly, you need to sort the result set using the same key that you use to generate the sections As
mentioned previously, the section key is set when initializing the NSFetchedResultsController in
the sectionNameKeyPath parameter
Next, you need to code the controllerDidChangeContent method to reload the data in the
TableView when the contents of the fetched results controller changes:
- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller {
// In the simplest, most efficient, case, reload the table view
[self.tableView reloadData];
}
LocationTasksViewController.m
The next step is to implement the TableView methods The numberOfSectionsInTableView
method will look just like the corresponding method in the RootViewController However,
because you are using sections on this screen, the fetched results controller will not just return 1 for
the number of sections Instead, the fetched results controller will calculate the number of sections
based on the number of different values in the location.name property
The tableView:numberOfRowsInSection: method also uses the fetch results controller to
populate the TableView In this case, you get an NSFetchedResultsSectionInfo object from
the fetched results controller that corresponds to the current section The section info object has a
numberOfObjects property that returns the number of objects in the section This value is returned
as the number of rows in the section