1. Trang chủ
  2. » Công Nghệ Thông Tin

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

10 259 0

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

THÔNG TIN TÀI LIỆU

Here 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; } Generating Grouped Tables Using the NSFetchedResultsController ❘ 209 CH007.indd 209CH007.indd 209 9/18/10 9:48:57 AM9/18/10 9:48:57 AM 210 ❘ CHAPTER 7 BUILDING A CORE DATA APPLICATION - (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 CH007.indd 210CH007.indd 210 9/18/10 9:48:57 AM9/18/10 9:48:57 AM Implementing Custom Managed Objects ❘ 211 Now 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 . CH007.indd 211CH007.indd 211 9/18/10 9:48:58 AM9/18/10 9:48:58 AM 212 ❘ CHAPTER 7 BUILDING A CORE DATA APPLICATION The 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; CH007.indd 212CH007.indd 212 9/18/10 9:48:58 AM9/18/10 9:48:58 AM Implementing Custom Managed Objects ❘ 213 Then, 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. CH007.indd 213CH007.indd 213 9/18/10 9:48:59 AM9/18/10 9:48:59 AM 214 ❘ CHAPTER 7 BUILDING A CORE DATA APPLICATION Because 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 CH007.indd 214CH007.indd 214 9/18/10 9:48:59 AM9/18/10 9:48:59 AM Implementing Custom Managed Objects ❘ 215 You 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; } CH007.indd 215CH007.indd 215 9/18/10 9:49:00 AM9/18/10 9:49:00 AM 216 ❘ CHAPTER 7 BUILDING A CORE DATA APPLICATION return 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; CH007.indd 216CH007.indd 216 9/18/10 9:49:00 AM9/18/10 9:49:00 AM } 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. Moving Forward ❘ 217 CH007.indd 217CH007.indd 217 9/18/10 9:49:01 AM9/18/10 9:49:01 AM CH007.indd 218CH007.indd 218 9/18/10 9:49:01 AM9/18/10 9:49:01 AM . 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. tableView: numberOfRowsInSection: methods: - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { return [[fetchedResultsController sections] count]; } - (NSInteger)tableView:(UITableView. 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

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

Xem thêm: Phát triển ứng dụng cho iPhone và iPad - part 25 docx

TỪ KHÓA LIÊN QUAN