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

more iphone 3 development phần 3 docx

57 260 0

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

Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống

THÔNG TIN TÀI LIỆU

Thông tin cơ bản

Định dạng
Số trang 57
Dung lượng 0,96 MB

Nội dung

CHAPTER 4: The Devil in the Detail View 98 - (void)viewDidLoad { sectionNames = [[NSArray alloc] initWithObjects: [NSNull null], NSLocalizedString(@"General", @"General"), nil]; rowLabels = [[NSArray alloc] initWithObjects: // Section 1 [NSArray arrayWithObjects:NSLocalizedString(@"Name", @"Name"), nil], // Section 2 [NSArray arrayWithObjects:NSLocalizedString(@"Identity", @"Identity"), NSLocalizedString(@"Birthdate", @"Birthdate"), NSLocalizedString(@"Sex", @"Sex"), nil], // Sentinel nil]; rowKeys = [[NSArray alloc] initWithObjects: // Section 1 [NSArray arrayWithObjects:@"name", nil], // Section 2 [NSArray arrayWithObjects:@"secretIdentity", @"birthdate", @"sex", nil], // Sentinel nil]; // TODO: Populate the rowControllers array [super viewDidLoad]; } - (void)dealloc { [hero release]; [sectionNames release]; [rowLabels release]; [rowKeys release]; [rowControllers release]; [super dealloc]; } #pragma mark - #pragma mark Table View Methods - (NSInteger)numberOfSectionsInTableView:(UITableView *)theTableView { return [sectionNames count]; } - (NSString *)tableView:(UITableView *)theTableView titleForHeaderInSection:(NSInteger)section { id theTitle = [sectionNames objectAtIndex:section]; if ([theTitle isKindOfClass:[NSNull class]]) return nil; return theTitle; CHAPTER 4: The Devil in the Detail View 99 } - (NSInteger)tableView:(UITableView *)theTableView numberOfRowsInSection:(NSInteger)section { return [rowLabels countOfNestedArray:section]; } - (UITableViewCell *)tableView:(UITableView *)theTableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { static NSString *CellIdentifier = @"Hero Edit Cell Identifier"; UITableViewCell *cell = [theTableView dequeueReusableCellWithIdentifier:CellIdentifier]; if (cell == nil) { cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue2 reuseIdentifier:CellIdentifier] autorelease]; } NSString *rowKey = [rowKeys nestedObjectAtIndexPath:indexPath]; NSString *rowLabel = [rowLabels nestedObjectAtIndexPath:indexPath]; id <HeroValueDisplay, NSObject> rowValue = [hero valueForKey:rowKey]; cell.detailTextLabel.text = [rowValue heroValueDisplay]; cell.textLabel.text = rowLabel; cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator; return cell; } - (void)tableView:(UITableView *)theTableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { // TODO: Push editing controller onto the stack. } @end Let’s take a look at the code we just wrote. Notice first that we import both of the categories we created earlier. If we don’t import the category headers, the compiler doesn’t know that those methods exist and will give us compile warnings. We also synthesize our only property: #import "HeroEditController.h" #import "NSArray-NestedArrays.h" #import "HeroValueDisplay.h" @implementation HeroEditController @synthesize hero; Next comes viewDidLoad. In this method, we create and populate those various arrays we discussed earlier that will define the structure of our tables. For now, we’re just going to create the arrays here in code. If our table gets more complex, we might want to consider putting the contents of the arrays into property lists or text files and creating the arrays from those files rather than hardcoding them as we’ve done here. That would reduce the size and complexity of our controller class. At this point, there doesn’t seem to be much benefit to doing that. One of the nice things about this approach is that since the arrays’ contents drive the table structure and the rest of the code in this CHAPTER 4: The Devil in the Detail View 100 controller class is relatively generic, we can change how we create our arrays without impacting the functionality of the rest of the code in this controller. The first array we populate is the sectionNames array. Notice that because we are not using a property, we don’t have an accessor. Since we’re not using an accessor that will retain the instance for us, we don’t release it. After this line of code, sectionNames has a retain count of 1, which is exactly what it would be if we assigned it to a property specified with the retain keyword, and then released it after making the assignment. - (void)viewDidLoad { sectionNames = [[NSArray alloc] initWithObjects: [NSNull null], NSLocalizedString(@"General", @"General"), nil]; TIP: Notice that we pass a nil as the last parameter to initWithObjects:. This is important. initWithObjects: is a variadic method, which is just a fancy way of saying it takes a variable number of arguments. We can pass in any number of objects to this method, and they will all get added to this array. The terminating nil is how we tell the initWithObjects: method that we’ve got not more objects for it. This terminating nil is called a sentinel. Starting with Snow Leopard, Xcode will warn you if you forget the sentinel, but on Leopard, a missing sentinel can be a very hard-to-debug problem. After this line of code fires, sectionNames has two elements. The first one is that special placeholder, NSNull, we talked about. If you look at Figure 4–2, you can see that the first section has no header. This is how we’re going to indicate that there’s a section, but that it doesn’t have a header. The second object in the array is a localized string that contains the word “General.” By creating a localized string, we have the ability to translate this header into whatever languages we wish. If you need a refresher on localizing your apps, the topic is covered in Chapter 17 of Beginning iPhone 3 Development. Next, we populate the rowLabels array. This is the array that defines the blue labels displayed on each row that you can see in Figure 4–2. Notice again, that we’ve used localized strings so that if we want to later translate our labels into other languages, we have the ability to do so without having to change our code. Because we’ve got nested object creation here, we’ve added comments so that when we revisit this somewhat complex code, we’ll remember what each bit of code is used for. rowLabels = [[NSArray alloc] initWithObjects: // Section 1 [NSArray arrayWithObjects:NSLocalizedString(@"Name", @"Name"), nil], // Section 2 [NSArray arrayWithObjects:NSLocalizedString(@"Identity", @"Identity"), NSLocalizedString(@"Birthdate", @"Birthdate"), NSLocalizedString(@"Sex", @"Sex"), nil], CHAPTER 4: The Devil in the Detail View 101 // Sentinel nil]; The code that populates the rowKeys array is very similar, except we don’t localize the strings. These are key values that are used to indicate which attribute gets shown in which row, and localizing them would break the functionality. The key is the same regardless of the language our user understands. rowKeys = [[NSArray alloc] initWithObjects: // Section 1 [NSArray arrayWithObjects:@"name", nil], // Section 2 [NSArray arrayWithObjects:@"secretIdentity", @"birthdate", @"sex", nil], // Sentinel nil]; We have one more array, but we’re not populating it yet. The last array defines which controller classes are used to edit which rows. We haven’t written any such controller classes yet, so we’ve got nothing to put in that array. We’re also not yet accessing this array anywhere, so it’s okay to just put in a reminder to do it later. As you’ve already seen, when developing more complex applications, you will often have to implement some functionality in an incomplete manner and then come back later to finish it. // TODO: Populate the rowControllers array [super viewDidLoad]; } The next method we implemented was dealloc, and there shouldn’t be anything too surprising here. We release all of the objects that we’ve retained, both those that are associated with properties, and those that aren’t. Remember, in viewDidLoad, we left our various structure arrays at a retain count of 1, so we have to release them here to avoid leaking memory. - (void)dealloc { [hero release]; [sectionNames release]; [rowLabels release]; [rowKeys release]; [rowControllers release]; [super dealloc]; } Even though we haven’t yet created or populated rowControllers, it’s perfectly okay to release it here. Sending a release message to nil is just fine and dandy in Objective-C. Next up are the table view datasource methods. The first one we implement tells our table view how many sections we have. We return the count from sectionNames here. By doing that, if we change the number of objects in the sectionNames array, we CHAPTER 4: The Devil in the Detail View 102 automatically change the number of sections in the table and don’t have to touch this method. #pragma mark - #pragma mark Table View Methods - (NSInteger)numberOfSectionsInTableView:(UITableView *)theTableView { return [sectionNames count]; } Since sections have an optional header displayed, we also implement tableView:titleForHeaderInSection:. For this, we just need to return the value from sectionNames. If the value NSNull is stored as a section name, we need to convert it to nil, since that’s what UITableView expects for a section with no header. - (NSString *)tableView:(UITableView *)theTableView titleForHeaderInSection:(NSInteger)section { id theTitle = [sectionNames objectAtIndex:section]; if ([theTitle isKindOfClass:[NSNull class]]) return nil; return theTitle; } In addition to telling our table view the number of sections, we need to tell it the number of rows in each section. Thanks to that category on NSArray we wrote earlier, this can be handled with one line of code. It doesn’t matter which of the paired arrays we use, since they should all have the same number of rows in every subarray. We obviously can’t use rowControllers, since we haven’t populated it yet. We chose rowLabels, but rowKeys would have worked exactly the same. - (NSInteger)tableView:(UITableView *)theTableView numberOfRowsInSection:(NSInteger)section { return [rowLabels countOfNestedArray:section]; } The tableView:cellForRowAtIndexPath: method is where we actually create the cell to be displayed. We start out almost exactly in the same way as every other table view controller, by looking for a dequeued cell and using it, or creating a new cell if there aren’t any dequeued cells. - (UITableViewCell *)tableView:(UITableView *)theTableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { static NSString *CellIdentifier = @"Hero Edit Cell Identifier"; UITableViewCell *cell = [theTableView dequeueReusableCellWithIdentifier:CellIdentifier]; if (cell == nil) { cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue2 reuseIdentifier:CellIdentifier] autorelease]; } Next, we retrieve the attribute name and the label for this row, again using that category method we added to NSArray to retrieve the correct object based on index path. NSString *rowKey = [rowKeys nestedObjectAtIndexPath:indexPath]; CHAPTER 4: The Devil in the Detail View 103 NSString *rowLabel = [rowLabels nestedObjectAtIndexPath:indexPath]; Once we know the attribute name, we can retrieve the object that’s used to represent this attribute using valueForKey:. Notice that we declare our rowValue object as id. We do this because the returned object could be instances of any number of different classes. We put HeroValueDisplay between angle brackets to indicate that we know the returned object will be an object that conforms to that HeroValueDisplay protocol we created earlier. This gives us the ability to call the heroValueDisplay method on whatever was returned without having to figure out what type of object it was. id <HeroValueDisplay, NSObject> rowValue = [hero valueForKey:rowKey]; Finally, we assign the label and value to the cell’s labels, and then return the cell. cell.detailTextLabel.text = [rowValue heroValueDisplay]; cell.textLabel.text = rowLabel; cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator; return cell; } The final method in our controller class is just a stub with a reminder to add this functionality later. - (void)tableView:(UITableView *)theTableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { // TODO: Push editing controller onto the stack. } @end Using the New Controller Now that we have our new controller class, we have to create instances of it somewhere and push those onto the stack. To do that, we have to revisit HeroListViewController. We could create a new instance of HeroEditController every time a row is tapped. Only one copy of HeroEditController will ever need to be on the navigation stack at a time. As a result, we can reuse a single instance over and over. We can also save ourselves several lines of code by adding an instance of HeroEditController to MainWindow.xib and adding an outlet to that instance to HeroListViewController. Remember, when you add an icon to a nib, an instance of that object gets created when the nib loads. Declaring the Outlet Single-click HeroListViewController.h, and add the following code to add an outlet for the instance of HeroEditController we’re going to add to MainWindow.xib: #import <UIKit/UIKit.h> #define kSelectedTabDefaultsKey @"Selected Tab" enum { kByName = 0, CHAPTER 4: The Devil in the Detail View 104 kBySecretIdentity, }; @class HeroEditController; @interface HeroListViewController : UIViewController <UITableViewDelegate, UITableViewDataSource, UITabBarDelegate, UIAlertViewDelegate, NSFetchedResultsControllerDelegate>{ UITableView *tableView; UITabBar *tabBar; HeroEditController *detailController; @private NSFetchedResultsController *_fetchedResultsController; } @property (nonatomic, retain) IBOutlet UITableView *tableView; @property (nonatomic, retain) IBOutlet UITabBar *tabBar; @property (nonatomic, retain) IBOutlet HeroEditController *detailController; @property (nonatomic, readonly) NSFetchedResultsController *fetchedResultsController; - (void)addHero; - (IBAction)toggleEdit; @end Now that we’ve got it declared, save HeroListViewController.h, and we’ll go add the instance to MainWindow.xib. Adding the Instance to MainWindow.xib Double-click on MainWindow.xib to open the nib file in Interface Builder. Look in the library for a Table View Controller, and drag one of those over to the nib’s main window. The newly added controller should be selected, so press 4 to bring up the identity inspector and change the underlying class from UITableViewController to HeroEditController. Next, in the main nib window, click on the Hero Edit Controller disclosure triangle and double-click on the Table View that appears. Alternatively, you can just click in the Hero Edit Controller window so the Table View shown in that window is selected. Now, press 1 to bring up the attribute inspector. You’ll know you’ve got the right item selected when the inspector window’s title changes from Hero Edit Controller Attributes to Table View Attributes. Change the table’s Style from Plain to Grouped. Back in the main nib window, open the disclosure triangle to the left of Navigation Controller to reveal an item named Hero List View Controller (Root View Controller). Control-drag from that item to the Hero Edit Controller icon and select the detailController outlet. CHAPTER 4: The Devil in the Detail View 105 NOTE: Note that your Hero List View Controller (Root View Controller) might instead have the name Hero List View Controller (SuperDB). No worries, it should work just fine. Save and close this nib and go back to Xcode. Pushing the New Instance onto the Stack Single-click HeroListViewController.m. There are two methods that we need to implement. When a user taps a row, we want to use the detail controller to show them information about the hero on which they tapped. When they add a new hero, we also want to take them down to the newly added hero so they can edit it. We haven’t implemented the editing functionality yet, but we can still configure and push detailController onto the stack now, so let’s do that. First, we need to import HeroEditController.h and synthesize the detailController outlet: #import "HeroListViewController.h" #import "SuperDBAppDelegate.h" #import "HeroEditController.h" @implementation HeroListViewController @synthesize tableView; @synthesize tabBar; @synthesize detailController; @synthesize fetchedResultsController = _fetchedResultsController; Now, find the addHero method, and add the following new code to it. You can also delete the old TODO comment. - (void)addHero { NSManagedObjectContext *context = [self.fetchedResultsController managedObjectContext]; NSEntityDescription *entity = [[self.fetchedResultsController fetchRequest] entity]; NSManagedObject *newManagedObject = [NSEntityDescription insertNewObjectForEntityForName:[entity name] inManagedObjectContext:context]; NSError *error; if (![context save:&error]) NSLog(@"Error saving entity: %@", [error localizedDescription]); // TODO: Instantiat e detail editing controller and push onto stack detailController.hero = newManagedObject; [self.navigationController pushViewController:detailController animated:YES]; } CHAPTER 4: The Devil in the Detail View 106 We assign the new managed object to detailController’s hero property, which is how we tell that controller that this is the hero to be viewed and/or edited. Then, we push it onto the stack. Easy enough? Now, find tableView:didSelectRowAtIndexPath:. It should just be a stub with a TODO comment. Replace it with this new version: - (void)tableView:(UITableView *)theTableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { detailController.hero = [self.fetchedResultsController objectAtIndexPath:indexPath]; [self.navigationController pushViewController:detailController animated:YES]; [theTableView deselectRowAtIndexPath:indexPath animated:YES]; } That should look pretty familiar. We’re doing almost the same thing, except instead of pushing a new managed object onto the stack, we’re retrieving the object that corresponds to the row on which the user tapped. Trying Out the View Functionality Save HeroListViewController.m and then build and run your application. Try adding new rows, or tapping on an existing row. You still don’t have the ability to edit them, but when you add a new row, you should get a new screen of data that looks like Figure 4–7. Figure 4–7. Adding a new hero now takes you to the new controller class CHAPTER 4: The Devil in the Detail View 107 All that’s missing is the ability to edit the individual fields, so let’s add that now. Adding Editing Subcontrollers Our next step is to create a series of new controllers, each of which can be used to edit an individual value on a hero. For now, we need one that can edit string attributes (Figure 4–8) and one that can edit date attributes (Figure 4–9). We’ll be adding other controllers later. All of these controllers have common functionality. They’ll all take a managed object and the name of the attribute on that managed object to be edited. They’ll all need a Save button and a Cancel button. Figure 4–8. The subcontroller that will allow the user to edit string attributes. Here, it’s being used to edit the name attribute. [...]... store on their iPhone will be unusable in the new version of your application Your application will crash on launch If you launch the new version from Xcode, you will see a big, scary error message like the following: 2009-09-08 14 :37 :26 .39 2 SuperDB[4 138 :207] Unresolved error Error Domain=NSCocoaErrorDomain Code= 134 100 UserInfo=0x14049d0 "Operation could not be completed (Cocoa error 134 100.)", { metadata... e0d620b1 f592e6b0 66ea66 63 ef6bc252>; }; NSStoreModelVersionHashesVersion = 3; NSStoreModelVersionIdentifiers = ( ); NSStoreType = SQLite; NSStoreUUID = "663F93E0-BD32-4F80-87F2-D72011101610"; }; reason = "The model used to open the store is incompatible with the one used to create the store"; } 127 128 CHAPTER 5: Preparing for Change: Migrations and Versioning If this happens in development, it’s not... save:&error]) NSLog(@"Error saving: %@", [error localizedDescription]); [self.navigationController popViewControllerAnimated:YES]; } @end 1 13 114 CHAPTER 4: The Devil in the Detail View Almost everything we do in this class is covered in Chapters 8 and 9 of Beginning iPhone 3 Development, but there’s some code in tableView:cellForRowAtIndexPath: that is worth taking a look at We’ve set default values for two... @property (nonatomic, retain) NSArray *list; @end The structure here might seem somewhat familiar It’s almost identical to one of the controllers from the Nav application in Chapter 9 of Beginning iPhone 3 Development The list property will contain the array of values from which the user can select, and lastIndexPath will be used to keep track of the selection Save ManagedObjectSingleSelectionListEditor.h... really nothing new here The logic we’re using is exactly the same that we used in the Nav application If you aren’t sure what’s going on here, go back and take a look through Chapter 9 of Beginning iPhone 3 Development The only difference here is that we’re using the keypath and managedObject to determine the initial selection and then pushing the final selection back into managedObject when the user... one in our superclass, which throws an exception Looking at that save method, you might also be CHAPTER 4: The Devil in the Detail View wondering if we made a mistake in this controller In Beginning iPhone 3 Development, we warned against relying on controls on table view cells to maintain state for you, since cells can get dequeued and reused to represent a different row Yet we are doing just that here... initWithStyle:UITableViewCellStyleDefault reuseIdentifier:GenericManagedObjectDateEditorCell] autorelease]; cell.textLabel.font = [UIFont systemFontOfSize:17.0]; cell.textLabel.textColor = [UIColor colorWithRed:0.2 43 green:0 .30 6 blue:0. 435 alpha:1.0]; } NSDateFormatter *formatter = [[NSDateFormatter alloc] init]; [formatter setDateStyle:NSDateFormatterMediumStyle]; cell.textLabel.text = [formatter stringFromDate:[self.datePicker... and Settings… from the iPhone Simulator menu in the simulator or uninstall the application from your iPhone using Xcode’s Organizer window, and Core Data will create a new persistent store based on the revised data model next time you install and run your application If, however, you have given the application to others, they will be stuck with an unusable application on their iPhone unless they uninstall... be left untouched The fact that the higher number is the older file might seem a little weird but, as more versions accumulate, the numbering will make more sense The next time we create a CHAPTER 5: Preparing for Change: Migrations and Versioning new version, the old version will be named SuperDB 3. xcdatamodel, and so on The numbering makes sense for all the non-current versions, since each version... [[UITableView alloc] initWithFrame: CGRectMake(0.0, 67.0, 32 0.0, 480.0) style:UITableViewStyleGrouped]; theTableView.delegate = self; theTableView.dataSource = self; [self.view addSubview:theTableView]; self.dateTableView = theTableView; [theTableView release]; UIDatePicker *theDatePicker = [[UIDatePicker alloc] initWithFrame:CGRectMake(0.0, 200.0, 32 0.0, 216.0)]; theDatePicker.datePickerMode = UIDatePickerModeDate; . need a refresher on localizing your apps, the topic is covered in Chapter 17 of Beginning iPhone 3 Development. Next, we populate the rowLabels array. This is the array that defines the blue. View 114 Almost everything we do in this class is covered in Chapters 8 and 9 of Beginning iPhone 3 Development, but there’s some code in tableView:cellForRowAtIndexPath: that is worth taking. Devil in the Detail View 115 wondering if we made a mistake in this controller. In Beginning iPhone 3 Development, we warned against relying on controls on table view cells to maintain state for

Ngày đăng: 12/08/2014, 21:20

TỪ KHÓA LIÊN QUAN