這章的標題很有趣,當按下 Hero cell 想要編輯時,就是 detail view 登場的時候,剛開始我還搞不懂 detail view 的意思。 一開始先討論 detail view 設計的方法, [Table-Based vs. Nib-Based],Human Interface Guidelines 並沒有對此做任何假設,這裡會使用 Table-Based 的方式。
而 Detail View 設計的困難在於其對應關係,例如我按下 Name 這個 cell 、或是按下 Birthdate cell,會有不同的 Editing SubController 產生,要如何設計其對應關係, that's a challenge。
下面的 code 是一般的做法,這種 nested switch 的方式造成 maintain 上的困難,本章是結合 paired array 和 nested array 的方式來處理對應關係,有彈性的多。
enum HeroEditControllerSections {
HeroEditControllerSectionName = 0,
HeroEditControllerSectionGeneral,
HeroEditControllerSectionCount
};
enum HeroEditControllerNameSection {
HeroEditControllerNameRow = 0,
HeroEditControllerNameSectionCount
};
enum HeroEditControllerGeneralSection {
HeroEditControllerGeneralSectionSecretIdentityRow,
HeroEditControllerGeneralSectionBirthdateRow,
HeroEditControllerGeneralSectionSexRow,
HeroEditControllerGeneralSectionCount
};
//Then, in every method where you are provided with an index path, you can take the
//appropriate action based on the row and section represented by the index path, using
//switch statements, like this:
- (void)tableView:(UITableView *)tableView
didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
NSUInteger section = [indexPath section];
NSUInteger row = [indexPath row];
switch (section) {
case HeroEditControllerSectionName:
switch (row)
{
case HeroEditControllerNameRow :
// Create a controller to edit name
// and push it on the stack
...
break;
default:
break;
}
break;
case HeroEditControllerSectionGeneral:
switch (row) {
case HeroEditControllerGeneralSectionSecretIdentityRow:
// Create a controller to edit secret identity
// and push it on the stack
...
CHAPTER 4: The Devil in the Detail View 87
break;
case HeroEditControllerGeneralSectionBirthdateRow:
// Create a controller to edit birthdate and
// push it on the stack
...
break;
case HeroEditControllerGeneralSectionSexRow:
// Create a controller to edit sex and push it
// on the stack
...
break;
default:
break;
}
break;
default:
break;
}
}
// combine nested and paired array
- (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];
rowControllers = [[NSArray alloc] initWithObjects:
// Section 1
[NSArray arrayWithObject:@"ManagedObjectStringEditor"],
// Section 2
[NSArray arrayWithObjects:@"ManagedObjectStringEditor",
@"ManagedObjectDateEditor",
@"ManagedObjectSingleSelectionListEditor", nil],
// Sentinel
nil];
rowArguments = [[NSArray alloc] initWithObjects:
// Section 1
[NSArray arrayWithObject:[NSNull null]],
// Section 2,
[NSArray arrayWithObjects:[NSNull null],
[NSNull null],
[NSDictionary dictionaryWithObject:[NSArray
arrayWithObjects:@"Male", @"Female", nil]
forKey:@"list"],
nil],
// Sentinel
nil];
[super viewDidLoad];
}
這裡有用到
[NSNull null]] 到的技巧,第一次看不是很明白,主要是用於 unname section 的情況??
接下來是設計真正的 editing subcontroller 的部份,下面兩張圖分別是 Birthdate 和 Name 欄位的editing subcontroller,這裡用了相同的父類別 (終於用到繼承了 XD) 來提取相同的部份。
對於 attribute 的字串處理 ,用到了 category 的技巧,如下 code
@protocol HeroValueDisplay
- (NSString *)heroValueDisplay;
@end
@interface NSString (HeroValueDisplay)
- (NSString *)heroValueDisplay;
@end
@interface NSDate (HeroValueDisplay)
- (NSString *)heroValueDisplay;
@end
@interface NSNumber (HeroValueDisplay)
- (NSString *)heroValueDisplay;
@end
@interface NSDecimalNumber (HeroValueDisplay)
- (NSString *)heroValueDisplay;
@end
其他要注意的是
- loadview 的使用時機 (在 datapicker 中有用到)
- UIButton 和 Bar Button 之間的不同
- NSClassFromString 的使用技巧 (like Java's Introspection)
這章很多東西 = =
hi