Xem mẫu

98 CHAPTER 4: The Devil in the Detail View - (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 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 100 CHAPTER 4: The Devil in the Detail View 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 102 CHAPTER 4: The Devil in the Detail View 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]; ... - tailieumienphi.vn
nguon tai.lieu . vn