Tải bản đầy đủ (.pdf) (10 trang)

Phát triển ứng dụng cho iPhone và iPad - part 24 potx

Bạn đang xem bản rút gọn của tài liệu. Xem và tải ngay bản đầy đủ của tài liệu tại đây (2.77 MB, 10 trang )

// 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.
Building the Editing Controllers

199
CH007.indd 199CH007.indd 199 9/18/10 9:48:52 AM9/18/10 9:48:52 AM
200

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;
CH007.indd 200CH007.indd 200 9/18/10 9:48:53 AM9/18/10 9:48:53 AM
[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]
Building the Editing Controllers

201
CH007.indd 201CH007.indd 201 9/18/10 9:48:53 AM9/18/10 9:48:53 AM
202

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
CH007.indd 202CH007.indd 202 9/18/10 9:48:53 AM9/18/10 9:48:53 AM
creating 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;

Displaying Results in the RootViewController

203

CH007.indd 203CH007.indd 203 9/18/10 9:48:54 AM9/18/10 9:48:54 AM
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];

CH007.indd 204CH007.indd 204 9/18/10 9:48:54 AM9/18/10 9:48:54 AM
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
Generating Grouped Tables Using the NSFetchedResultsController

205
CH007.indd 205CH007.indd 205 9/18/10 9:48:55 AM9/18/10 9:48:55 AM
206

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.
LISTING 7 - 7: LocationTasksViewController.h
#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();
CH007.indd 206CH007.indd 206 9/18/10 9:48:55 AM9/18/10 9:48:55 AM
}

// 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];
Generating Grouped Tables Using the NSFetchedResultsController

207
CH007.indd 207CH007.indd 207 9/18/10 9:48:56 AM9/18/10 9:48:56 AM
208

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.
CH007.indd 208CH007.indd 208 9/18/10 9:48:56 AM9/18/10 9:48:56 AM

×