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

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

Đang tải... (xem toàn văn)

Thông tin tài liệu

// 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 . 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. 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*. 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

Ngày đăng: 04/07/2014, 21:20