三十而立,從零開始學ios開發(十六):Navigation Controllers and Table Views(下)


終於進行到下了,這是關於Navigation Controllers和Table Views的最后一個例子,稍微復雜了一點,但也僅僅是復雜而已,難度不大,我們開始吧。

如果沒有上一篇的代碼,可以從這里下載Nav_2

1)第六個subtableview:An Editable Detail Pane
打開你iphone上的通訊錄,首先看見的是你通訊錄中所有的聯系人列表,點選一個聯系人,就會切換到聯系人的詳細頁面,再點擊右上角的編輯按鈕,就可以對聯系人的內容進行編輯。我們的這個例子與之有點相似之處,首先也是一個列表,這個列表中列舉了歷任的美國總統(他們的名字和任期),然后點擊其中的一個總統名字,view切換到該總統的詳細頁面,我們可以對該總統的信息進行編輯保存。

好了,開始我們的例子,與以往不同的是,這次我們需要先創建一個類,叫做BIDPresident,這個類記錄了每位美國總統的信息,名字啊,任職起止年,來自哪個政黨等信息。選中Project navigator中的Nav文件夾,單擊鼠標右鍵,選擇“New File...”,在彈出的窗口中,左邊選擇Cocoa Touch,右邊選擇Objective-C class,點擊Next按鈕,在下一個窗口中將class命名為BIDPresident,Subclass of命名為NSObject,點擊Next按鈕,完成創建。


打開BIDPresident.h,添加如下代碼

#import <Foundation/Foundation.h>

#define kPresidentNumberKey @"President"
#define kPresidentNameKey   @"Name"
#define kPreisdentFromKey   @"FromYear"
#define kPresidentToKey     @"ToYear"
#define kPresidentPartyKey  @"Party"

@interface BIDPresident : NSObject <NSCoding> @property int number; @property (nonatomic, copy) NSString *name; @property (nonatomic, copy) NSString *fromYear; @property (nonatomic, copy) NSString *toYear; @property (nonatomic, copy) NSString *party; @end

我們首先定義了5個常量,這5個常量會在標識總統信息的時候用到,看后面的代碼就明白了,我們一般在編程的時候都會用到常量,這里沒什么區別。接着我們用到了一個新的家伙<NSCoding>協議,這個東西的作用是將一個對象寫入文件或者從文件中創建一個對象(the NSCoding protocol is what allows this object to be written to and created from files.)在這里你僅僅需要知道這些就可以了,在以后的章節中,會對NSCoding進行詳細的了解。

好了,我們接着打開BIDPresident.m,添加如下代碼

#import "BIDPresident.h"

@implementation BIDPresident

@synthesize number; @synthesize name; @synthesize fromYear; @synthesize toYear; @synthesize party; #pragma mark -
#pragma mark NSCoding
- (void)encodeWithCoder:(NSCoder *)coder { [coder encodeInt:self.number forKey:kPresidentNumberKey]; [coder encodeObject:self.name forKey:kPresidentNameKey]; [coder encodeObject:self.fromYear forKey:kPreisdentFromKey]; [coder encodeObject:self.toYear forKey:kPresidentToKey]; [coder encodeObject:self.party forKey:kPresidentPartyKey]; } - (id)initWithCoder:(NSCoder *)coder { if(self = [super init]) { number = [coder decodeIntForKey:kPresidentNumberKey]; name = [coder decodeObjectForKey:kPresidentNameKey]; fromYear = [coder decodeObjectForKey:kPreisdentFromKey]; toYear = [coder decodeObjectForKey:kPresidentToKey]; party = [coder decodeObjectForKey:kPresidentPartyKey]; } return self; }

encodeWithCoder和initWithCoder是2個NSCoding的協議方法,encodeWithCoder用於將我們的對象進行編碼,initWithCoder用於將我們的對象進行解碼。這2個方法的作用是從plist中讀取數據,並轉換成BIDPresident的對象。

下載President.plist.zip,解壓后,將President.plist拖入到項目中

下面開始創建controller,我們需要創建2個controller,一個用於展示presidents列表,另一個是展示總統信息的詳細頁面。選中Project navigator中的Nav文件夾,單擊鼠標右鍵,選擇“New File...”,在彈出的窗口中,左邊選擇Cocoa Touch,右邊選擇Objective-C class,點擊Next按鈕,在下一個窗口中將class命名為BIDPresidentsViewController,Subclass of命名為BIDSecondLevelViewController,點擊Next按鈕,完成創建。使用同樣的方法再創建一個,class命名為BIDPresidentDetailController,Subclass of命名為UITableViewController

打開BIDPresidentsViewController.h,添加如下代碼

#import "BIDSecondLevelViewController.h"

@interface BIDPresidentsViewController : BIDSecondLevelViewController

@property (strong, nonatomic) NSMutableArray *list; @end

這個list用於保存Presidents的列表

打開BIDPresidentsViewController.m,添加如下代碼

#import "BIDPresidentsViewController.h"
#import "BIDPresidentDetailController.h"
#import "BIDPresident.h"

@implementation BIDPresidentsViewController

@synthesize list; - (void)viewDidLoad { [super viewDidLoad]; NSString *path = [[NSBundle mainBundle] pathForResource:@"Presidents" ofType:@"plist"]; NSData *data; NSKeyedUnarchiver *unarchiver; data = [[NSData alloc] initWithContentsOfFile:path]; unarchiver = [[NSKeyedUnarchiver alloc] initForReadingWithData:data]; NSMutableArray *array = [unarchiver decodeObjectForKey:@"Presidents"]; self.list = array; [unarchiver finishDecoding]; } - (void)viewWillAppear:(BOOL)animated { [super viewWillAppear:animated]; [self.tableView reloadData]; }

上面這段代碼中,要對viewDidLoad稍微解釋一下:
首先,通過下面的代碼找到Presidents.plist:
NSString *path = [[NSBundlemainBundle] pathForResource:@"Presidents"ofType:@"plist"];

接着,我們聲明了一個NSData對象,一個NSKeyedUnarchiver對象,NSData是一個保存數據的類,其保存數據的格式是二進制的(應該是二進制的吧,byte類型),然后NSData可以將其內容轉換為其他的類型,例如NSString。NSKeyedUnarchiver類從字節流中讀取數據(與NSKeyedUnarchiver類對應的有一個NSKeyedArchiver類,它的作用是將對象寫入字節流)。
NSData *data;
NSKeyedUnarchiver *unarchiver;

接着初始化data中的數據,Presidents.plist中的數據,然后將data中的數據賦給unarchiver
data = [[NSDataalloc] initWithContentsOfFile:path];
unarchiver = [[NSKeyedUnarchiver alloc] initForReadingWithData:data];

接着對unarchiver中的內容進行解碼,並賦給新創建的NSMutableArray
NSMutableArray *array = [unarchiver decodeObjectForKey:@"Presidents"];

最后將array賦值給list,並終止解碼過程
self.list = array;
[unarchiver finishDecoding];

這段viewDidLoad代碼如果是第一次接觸的話,確實有點搞腦子,幾個對象是第一次遇到,如果實在看不懂也不要太在意,只要知道這個過程是干嘛的,只要知道我們從plist中讀取了Presidents的對象,然后把它賦值給了list就可以了。

viewWillAppear貌似也是我們第一次使用到吧,這個方法是在每次view即將出現之前被調用到的(在viewDidLoad之后被調用),當我們修改了某一個president的信息后,那么這個president在列表中的顯示也將發生變化,我們所采用的方法不是去判斷這個president出現在列表的哪個位置,而是不管三七二十一,整個的重新載入列表,這是一個比較偷懶且簡單易實現的方法。

繼續添加代碼

#pragma mark -
#pragma mark Table Data Source Methods
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { return [list count]; } - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { static NSString *PresidentListCellIdentifier = @"PresidentListCellIdentifier"; UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:PresidentListCellIdentifier]; if(cell == nil) { cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:PresidentListCellIdentifier]; } NSUInteger row = [indexPath row]; BIDPresident *thePres = [self.list objectAtIndex:row]; cell.textLabel.text = thePres.name; cell.detailTextLabel.text = [NSString stringWithFormat:@"%@ - %@", thePres.fromYear, thePres.toYear]; return cell; }

在tableView:cellForRowAtIndexPath方法中,首先要注意的是,cell的style是UITableViewCellStyleSubtitle。其次我們每次都對cell的textLabel和detailTextLabel重新賦值,這個是因為我們可能會對某些president的信息進行修改,這樣重新取回的cell中保存的還是舊數據,所以要更新一下。

繼續添加代碼

#pragma mark -
#pragma mark Table Delegate Methods
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { NSUInteger row = [indexPath row]; BIDPresident *prez = [self.list objectAtIndex:row]; BIDPresidentDetailController *childController = [[BIDPresidentDetailController alloc] initWithStyle:UITableViewStyleGrouped]; childController.title = prez.name; childController.president = prez; [self.navigationController pushViewController:childController animated:YES]; }

當我們點選一個cell后,將創建一個BIDPresident的對象,里面包含了選中的president的詳細信息,然后創建一個BIDPresidentDetailController的對象childController,並設置其title屬性(可以在navigation bar上面顯示)和其需要展示的president,BIDPresidentDetailController對象的style為UITableViewStyleGrouped。navigationController的pushViewController方法之前也用到過,將新的childController push進棧並顯示childController。

另外,這里應該會有一個錯誤:childController.president = prez,因為到目前位置BIDPresidentDetailController中什么都沒有,更別說president,不過沒關系,先留在這里,我們馬上就要去實現BIDPresidentDetailController了。

保存好文件后,我們開始編輯BIDPresidentDetailController,打開BIDPresidentDetailController.h,添加如下代碼

#import <UIKit/UIKit.h>

@class BIDPresident; #define kNumberOfEditableRows   4
#define kNameRowIndex           0
#define kFromYearRowIndex       1
#define kToYearRowIndex         2
#define kPartyIndex             3

#define kLabelTag               4096

@interface BIDPresidentDetailController : UITableViewController <UITextFieldDelegate> @property (strong, nonatomic) BIDPresident *president; @property (strong, nonatomic) NSArray *fieldLabels; @property (strong, nonatomic) NSMutableDictionary *tempValues; @property (strong, nonatomic) UITextField *currentTextField; - (IBAction)cancel:(id)sender; - (IBAction)save:(id)sender; - (IBAction)textFieldDone:(id)sender; @end

在BIDPresidentDetailController中定義了一個style為grouped的table,且這個table有4行,分別記錄了當前president的名字,擔任總統的開始年份,擔任總統的結束年份和來自哪個政黨。上面的代碼中,前5個常量分別表示了這些信息:
#define kNumberOfEditableRows  4 \\ 有4行可編輯的行
#define kNameRowIndex                0 \\ president名字
#define kFromYearRowIndex    1 \\ 開始年份
#define kToYearRowIndex      2 \\ 結束年份
#define kPartyIndex                  3 \\ 政黨

最后一個常量kLabelTag在之后的code中會有解釋,它的作用是取回cell中的UILabel,看之后的代碼就會明白。

在這里我們引入了一個新的協議<UITextFieldDelegate>。

*president:指向當前的總統,這個就是我們上面提到的那個錯誤,現在把那個錯誤彌補了。
*fieldLabels:是一個Array,里面保存了與4個常量對應的label,kNameRowIndex對應fieldLabels中index為0的label,kFromYearRowIndex對應fieldLabels中index為1的label,以此類推,具體如何實現還是看之后的代碼吧,這樣比較直觀。
*tempValues:是一個mutable dictionary,它將臨時保存用戶修改過的值,因為當一個值被修改后(例如任職的開始年份),我們並不喜歡它直接反映到最終的list中,因為一旦用於點擊了Cancel按鈕,所有的修改都將作廢,因此我們先保存在tempValues中,如果之后用戶點擊的是Save按鈕,那么才會將值保存到list中。
*currentTextField:當用戶點擊一個BIDPresidentDetailController的text field時,currentTextField將指向那個text field。我們必須擁有一個currentTextField的原因有點復雜,當我們完成對一個text field修改的時候,我們可能會跳轉到另一個text field,此時UITextField的delegate方法textFieldDidEndEditing方法將被觸發,textFieldDidEndEditing有當前的textfield參數傳人,可以知道是哪個textfield在編輯,我們可以保存最新值到tempValues中,但是當我們點擊Save按鈕的時候,BIDPresidentDetailController將會出棧並顯示BIDPresidentsViewController,那么此時我們沒有機會得到是那個textfield在編輯,因此我們也沒有辦法保存最新的值到tempValues中,基於這種情況,我們定義了currentTextField,這樣就可以知道最后編輯的那個textfield,也可以獲得其值,這樣就可以保存最新的值到tempValues中去了。

說了一大段了理論,大家是不是看的雲里霧里的,我也說的雲里霧里的,還是看具體的實現吧,這樣可以撥開雲霧見月明。

打開BIDPresidentDetailController.m,添加如下代碼

#import "BIDPresidentDetailController.h"
#import "BIDPresident.h"

@implementation BIDPresidentDetailController
@synthesize president; @synthesize fieldLabels; @synthesize tempValues; @synthesize currentTextField; - (IBAction)cancel:(id)sender { [self.navigationController popViewControllerAnimated:YES]; } - (IBAction)save:(id)sender { if (currentTextField != nil) { NSNumber *tagAsNum = [NSNumber numberWithInt:currentTextField.tag]; [tempValues setObject:currentTextField.text forKey:tagAsNum]; } for (NSNumber *key in [tempValues allKeys]) { switch ([key intValue]) { case kNameRowIndex: president.name = [tempValues objectForKey:key]; break; case kFromYearRowIndex: president.fromYear = [tempValues objectForKey:key]; break; case kToYearRowIndex: president.toYear = [tempValues objectForKey:key]; break; case kPartyIndex: president.party = [tempValues objectForKey:key]; break; } } [self.navigationController popViewControllerAnimated:YES]; NSArray *allControllers = self.navigationController.viewControllers; UITableViewController *parent = [allControllers lastObject]; [parent.tableView reloadData]; } - (IBAction)textFieldDone:(id)sender { [sender resignFirstResponder]; }

第一個實現的是cancel action方法,當用戶點擊Cancel按鈕時,會調用該方法。當cancel action被觸發后,當前的view會出棧(BIDPresidentDetailController出棧),之前的一個view會顯示出來(BIDPresidentsViewController會到棧的頂端顯示)。

第二個實現的是save action方法,當用戶點擊Save按鈕時,會調用該方法。save action的目的是保存用戶對於信息的修改,當save action被觸發后,位於tempValues中的值將被寫入到president中,但是有一種情況是例外的,如果用戶正在編輯一個textField,當編輯完后,立刻點擊Save按鈕(此時textField還是獲得焦點的,虛擬鍵盤也還在),那么當前正在被編輯的textField的內容還沒有保存到tempValues中,如果直接保存數據,當前textField中的內容將無法保存,因此為了解決這個問題,我們引入了之前所說的currentTextField,在save action中,我們首先判斷currentTextField是否為nil,如果不是,則說明當前有textField正在被編輯,我們就通過currentTextField來獲得當前編輯的textField的內容,並把值保存進tempValues中。
if (currentTextField != nil) {
        NSNumber *tagAsNum = [NSNumber numberWithInt:currentTextField.tag];
        [tempValues setObject:currentTextField.text forKey:tagAsNum];
}

之后的快速遍歷tempValues中的每一個值,將其保存到president中。tempValues的類型是NSMutableDictionary,它的key是行號,即每個textField所處的在第幾行的行號,通過行號獲得tempValues中對應的值,並賦給president。
for (NSNumber *key in [tempValues allKeys]) {
    switch ([key intValue]) {
        case kNameRowIndex:
            president.name = [tempValues objectForKey:key];
            break;
            ......
        default:
            break;
    }
}

之后就是當前的view出棧,父view提升為第一個view顯示
[self.navigationControllerpopViewControllerAnimated:YES];

我們還需要做一件事情,因為我們可能對信息進行了修改,因此必須重新load父view中的數據(BIDPresidentsViewController中的數據)。代碼中首先列舉出所有navigation中的viewController,其實也就只有一個,因此可以用lastObject獲得,然后重新載入數據即可。
NSArray *allControllers = self.navigationController.viewControllers;
UITableViewController *parent = [allControllers lastObject]; // 只有一個controller,如果換成
objectAtIndex:0也是對的
[parent.tableView reloadData];

第三個實現的是textFieldDone action方法,當完成對一個textField內容修改后,點擊虛擬鍵盤上面的Done按鈕,使虛擬鍵盤消失。

繼續添加如下代碼

#pragma mark -
- (void)viewDidLoad { [super viewDidLoad]; NSArray *array = [[NSArray alloc] initWithObjects:@"Name:", @"From:", @"To:", @"Party:", nil]; self.fieldLabels = array; UIBarButtonItem *cancelButton = [[UIBarButtonItem alloc] initWithTitle:@"Cancel" style:UIBarButtonItemStylePlain target:self action:@selector(cancel:)]; self.navigationItem.leftBarButtonItem = cancelButton; UIBarButtonItem *saveButton = [[UIBarButtonItem alloc] initWithTitle:@"Save" style:UIBarButtonItemStyleDone target:self action:@selector(save:)]; self.navigationItem.rightBarButtonItem = saveButton; NSMutableDictionary *dict = [[NSMutableDictionary alloc] init]; self.tempValues = dict; }

首先創建4個string,並賦給fieldLabels。接着創建了2個按鈕,cancelButton和saveButton。cancelButton中定義了它調用的action為cancel,並且它放置的位置為navigationbar的左邊。同樣saveButton中定義了它調用的action為save,並且它放置的位置為navigationbar的右邊。最后我們創建了一個NSMutableDictionary對象,並使tempValues指向這個對象。

接着添加代碼

#pragma mark -
#pragma mark Table Data Source Methods
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { return kNumberOfEditableRows; } - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { static NSString *PresidentCellIdentifier = @"PresidentCellIdentifier"; UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:PresidentCellIdentifier]; if (cell == nil) { cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:PresidentCellIdentifier];
UILabel
*label = [[UILabel alloc] initWithFrame:CGRectMake(10, 10, 75, 25)]; label.textAlignment = UITextAlignmentRight; label.tag = kLabelTag; label.font = [UIFont boldSystemFontOfSize:14]; [cell.contentView addSubview:label]; UITextField *textField = [[UITextField alloc] initWithFrame:CGRectMake(90, 12, 200, 25)]; textField.clearsOnBeginEditing = NO; [textField setDelegate:self]; textField.returnKeyType = UIReturnKeyDone; [textField addTarget:self action:@selector(textFieldDone:) forControlEvents:UIControlEventEditingDidEndOnExit]; [cell.contentView addSubview:textField]; } NSUInteger row = [indexPath row]; UILabel *label = (UILabel *)[cell viewWithTag:kLabelTag]; UITextField *textField = nil; for (UIView *oneView in cell.contentView.subviews) { if([oneView isMemberOfClass:[UITextField class]]) textField = (UITextField *)oneView; } label.text = [fieldLabels objectAtIndex:row]; NSNumber *rowAsNum = [NSNumber numberWithInt:row]; switch (row) { case kNameRowIndex: if ([[tempValues allKeys] containsObject:rowAsNum]) textField.text = [tempValues objectForKey:rowAsNum]; else textField.text = president.name; break; case kFromYearRowIndex: if ([[tempValues allKeys] containsObject:rowAsNum]) textField.text = [tempValues objectForKey:rowAsNum]; else textField.text = president.fromYear; break; case kToYearRowIndex: if ([[tempValues allKeys] containsObject:rowAsNum]) textField.text = [tempValues objectForKey:rowAsNum]; else textField.text = president.toYear; break; case kPartyIndex: if ([[tempValues allKeys] containsObject:rowAsNum]) textField.text = [tempValues objectForKey:rowAsNum]; else textField.text = president.party; break; default: break; } if (currentTextField == textField) { currentTextField = nil; } textField.tag = row; return cell; }

tableView:numberOfRowsInSection就不解釋了,和之前的一樣,重點看一下tableView:cellForRowAtIndexPath。

首先,我們再明確一次這個方法被調用的時機,定table view中的cell要出現時,會調用該方法來創建或者取回cell,也就是說cell是一個一個單獨創建的或者取回的,他們不是一下子創建完,必須一個一個創建,好了記住這一點后再看代碼就會比較容易理解。

代碼一開始還是和以前的例子一樣定義identifier,然后試着取回cell,如果不行就創建cell
static NSString *PresidentCellIdentifier = @"PresidentCellIdentifier";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:PresidentCellIdentifier];
if (cell == nil) {
    cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:PresidentCellIdentifier];
這些就一筆帶過了,之后的代碼才是重點。

在創建cell的同時,還創建了一個UILabel和UITextField。創建UILabel時,我們指定了它的位置,文字是右對齊的,tag是kLabelTag(可以利用這個kLabelTag取回cell中的label),設置了字體,最后將label添加到cell中。
UILabel *label = [[UILabelalloc] initWithFrame:CGRectMake(10, 10, 75, 25)];
label.textAlignment = UITextAlignmentRight;
label.tag = kLabelTag;
label.font = [UIFont boldSystemFontOfSize:14];
[cell.contentView addSubview:label];

創建UITextField時,指定了它的位置,設置其獲得焦點后原有的內容不消失,設置self作為它的delegate(textField是在BIDPresidentDetailController上創建的,BIDPresidentDetailController也是它的delegate文件,它的協議方法都在這個文件中實現),當點擊textField時,會有虛擬鍵盤彈出,將虛擬鍵盤的Return鍵設置成Done鍵,接着為textField的Did End On Exit事件制定action為textFieldDone(addTarget是說明textFieldDone在哪里,用self就是說在BIDPresidentDetailController中),最后將textField添加到cell中。
UITextField *textField = [[UITextField alloc] initWithFrame:CGRectMake(90, 12, 200, 25)];
textField.clearsOnBeginEditing = NO;
[textField setDelegate:self];
textField.returnKeyType = UIReturnKeyDone;
[textField addTarget:self action:@selector(textFieldDone:) forControlEvents:UIControlEventEditingDidEndOnExit];
[cell.contentView addSubview:textField]

注意,在創建textField的時候,我們並沒有為其添加tag,我們使用遍歷cell中所用subviews的方式來找到textField的。接着看下面的代碼,通過indexPath我們可以知道當前的cell是table view中的第幾個
NSUInteger row = [indexPath row];

接着就是得到當前cell中的label和textField,為他們賦值。因為cell中只有一個label的tag為kLabelTag,基於這點,我們可以用過tag來找到label
UILabel *label = (UILabel *)[cell viewWithTag:kLabelTag];
在cell中也只有一個textField,所以只要遍歷cell中包含的所有view,並判斷view的類型是不是UITextField,就可以找到textField
UITextField *textField = nil;
for (UIView *oneView in cell.contentView.subviews) {
    if([oneView isMemberOfClass:[UITextField class]])
        textField = (UITextField *)oneView;
}

找到了label和textField就為他們賦值,label的賦值相對簡單,我們已經知道是第幾個cell,那么就可以在fieldLabels中找到對應的值賦給label
label.text = [fieldLabels objectAtIndex:row];

textField相對來說比較復雜些,我們首先要判斷當前的內容是不是被修改過,如果修改過,那么我們要從tempValues中取值,如果沒有修改過,則從president對象中取值(之后的代碼會有對tempValues的賦值,看來就明白了,這里稍微想一想原理,應該可以理解)
if ([[tempValues allKeyscontainsObject:rowAsNum])
    textField.text = [tempValues objectForKey:rowAsNum];
else
    textField.text = president.name;

接着是判斷當前的textField是否是正在編輯的textField(currentTextField),如果是就將currentTextField設為nil,因為textField的內容已經發生了變化,里面包含的都是最新的值,所有currentTextField就不需要了。
if (currentTextField == textField) {
    currentTextField = nil;
}

最后設置textField的tag為row,並返回cell
textField.tag = row;
return cell;

繼續添加代碼

#pragma mark -
#pragma mark Table Delegate Methods
- (NSIndexPath *)tableView:(UITableView *)tableView willSelectRowAtIndexPath:(NSIndexPath *)indexPath { return nil; } #pragma mark Text Field Delegate Methods
- (void)textFieldDidBeginEditing:(UITextField *)textField { self.currentTextField = textField; } - (void)textFieldDidEndEditing:(UITextField *)textField { NSNumber *tagAsNum = [NSNumber numberWithInt:textField.tag]; [tempValues setObject:textField.text forKey:tagAsNum]; }

第一個是tableview的delegate方法,返回nil表示不能選擇table view中的cell。

之后的2個都是textField的delegate方法,第一個textFieldDidBeginEditing,當textField變成first responder時(獲得焦點,彈出虛擬鍵盤時)會觸發這個方法,在這個方法中,我們將currentTextField指向了當前的textField,這個就能夠保證我們在點擊Save按鈕時,可以得到正確的值。

第二個是textFieldDidEndEditing方法,當textField失去焦點或者點選了虛擬鍵盤上的Done按鈕就會觸發該方法,在這個方法中,首先獲得textField的tag,然后為tempValues賦值,值是當前textField的text,key是textField的tag。

至此BIDPresidentDetailController中所有的代碼都添加完成了,大家看懂了嗎?沒看懂的話多看幾遍,然后加上自己的理解,應該能夠搞明白的,相信自己,相信奇跡吧!

最后打開BIDFirstLevelController.m,添加如下代碼

#import "BIDFirstLevelController.h"
#import "BIDSecondLevelViewController.h"
#import "BIDDisclosureButtonController.h"
#import "BIDCheckListController.h"
#import "BIDRowControlsController.h"
#import "BIDMoveMeController.h"
#import "BIDDeleteMeController.h"
#import "BIDPresidentsViewController.h"

@implementation BIDFirstLevelController

@synthesize controllers;

- (void)viewDidLoad {
    [super viewDidLoad];
    self.title = @"First Level";
    NSMutableArray *array = [[NSMutableArray alloc] init];    
    
    // Disclosure Button
    BIDDisclosureButtonController *disclosureButtonController = [[BIDDisclosureButtonController alloc]
                                                                 initWithStyle:UITableViewStylePlain];
    disclosureButtonController.title = @"Disclosure Buttons";
    disclosureButtonController.rowImage = [UIImage imageNamed:@"disclosureButtonControllerIcon.png"];
    [array addObject:disclosureButtonController];
    
    // Checklist
    BIDCheckListController *checkListController = [[BIDCheckListController alloc]
                                                   initWithStyle:UITableViewStylePlain];
    checkListController.title = @"Check One";
    checkListController.rowImage = [UIImage imageNamed:@"checkmarkControllerIcon.png"];
    [array addObject:checkListController];    
    
    // Row Control
    BIDRowControlsController *rowControlsController = [[BIDRowControlsController alloc]
                                                       initWithStyle:UITableViewStylePlain];
    rowControlsController.title = @"Row Control";
    rowControlsController.rowImage = [UIImage imageNamed:@"rowControlsIcon.png"];
    [array addObject:rowControlsController];
    
    // Move Control
    BIDMoveMeController *moveMeController = [[BIDMoveMeController alloc]
                                             initWithStyle:UITableViewStylePlain];
    moveMeController.title = @"Move Me";
    moveMeController.rowImage = [UIImage imageNamed:@"moveMeIcon.png"];
    [array addObject:moveMeController];
    
    // Delete Me
    BIDDeleteMeController *deleteMeController = [[BIDDeleteMeController alloc]
                                                 initWithStyle:UITableViewStylePlain];
    deleteMeController.title = @"Delete Me";
    deleteMeController.rowImage = [UIImage imageNamed:@"deleteMeIcon.png"];
    [array addObject:deleteMeController];
    
    // BIDPresident View/Edit
    BIDPresidentsViewController *presidentsViewController = [[BIDPresidentsViewController alloc] initWithStyle:UITableViewStylePlain]; presidentsViewController.title = @"Detail Edit"; presidentsViewController.rowImage = [UIImage imageNamed:@"detailEditIcon.png"]; [array addObject:presidentsViewController];
    
    self.controllers = array;
}

好了,終於可以編譯運行了,效果如下

選擇最后的Detail Edit,切換到BIDPresidentsViewController

隨便選擇一個總統的名字,切換到BIDPresidentDetailController

隨便選擇一個textField,會出現虛擬鍵盤,且會顯示Done按鈕

隨便編輯一下內容,然后點擊Done按鈕,鍵盤消失

點擊Save按鈕保存修改,或者點擊Cancel按鈕,放棄修改,這里我點擊的是Save按鈕

 

ok,大致功能就是這些,但是還是有可以改進的地方,如果你打開你iphone上的通訊錄,選擇一條記錄進行編輯,彈出的虛擬鍵盤上顯示的是return按鈕,點擊return按鈕,光標會移動到下一個textField中,這個是怎么實現的呢?其實沒那么困難,下面就來實現一下。

打開BIDPresidentDetailController.m,在tableView:cellForRowAtIndexPath方法中,將下面的這句話刪除

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    static NSString *PresidentCellIdentifier = @"PresidentCellIdentifier";
    
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:PresidentCellIdentifier];
    if (cell == nil) {
        cell = [[UITableViewCell alloc]
                initWithStyle:UITableViewCellStyleDefault
                reuseIdentifier:PresidentCellIdentifier];
        
        UILabel *label = [[UILabel alloc] initWithFrame:CGRectMake(10, 10, 75, 25)];
        label.textAlignment = UITextAlignmentRight;
        label.tag = kLabelTag;
        label.font = [UIFont boldSystemFontOfSize:14];
        [cell.contentView addSubview:label];
        
        UITextField *textField = [[UITextField alloc] initWithFrame:CGRectMake(90, 12, 200, 25)];
        textField.clearsOnBeginEditing = NO;
        [textField setDelegate:self];
        textField.returnKeyType = UIReturnKeyDone;
        [textField addTarget:self action:@selector(textFieldDone:) forControlEvents:UIControlEventEditingDidEndOnExit];
        [cell.contentView addSubview:textField];
    }
    NSUInteger row = [indexPath row];
......

這樣虛擬鍵盤就不會顯示Done按鈕了,會顯示默認的return按鈕。然后找到textFieldDone方法,進行如下修改

- (IBAction)textFieldDone:(id)sender
{
    [sender resignFirstResponder]; UITableViewCell *cell = (UITableViewCell *)[[sender superview] superview]; UITableView *table = (UITableView *)[cell superview]; NSIndexPath *textFieldIndexPath = [table indexPathForCell:cell]; NSUInteger row = [textFieldIndexPath row]; row++; if (row >= kNumberOfEditableRows) { row = 0; } NSIndexPath *newPath = [NSIndexPath indexPathForRow:row inSection:0]; UITableViewCell *nextCell = [self.tableView cellForRowAtIndexPath:newPath]; UITextField *nextField = nil; for (UIView *oneView in nextCell.contentView.subviews) { if([oneView isMemberOfClass:[UITextField class]]) nextField = (UITextField *)oneView; } [nextField becomeFirstResponder];
}

有一點需要明確,cell本身是不知道自己處在哪一行的,textFieldDone也沒有indexPath參數傳進來告知現在是哪一行,因此我們要另辟蹊徑。令人欣慰的是,table view知道當前是哪個cell正在響應,因此我們需要先得到table view。觸發textFieldDone的是textField,它的superview是contentView,contextView的superview是UITableViewCell,cell的superview是table view,好了,就這么找到table view了
UITableViewCell *cell = (UITableViewCell *)[[sender superview] superview];
UITableView *table = (UITableView *)[cell superview];

通過table view的indexPathForCell方法,可以得到indexPath,有了indexPath就可以知道當前的cell是哪一行的,最最關鍵的東西得到后,之后的代碼就很簡單了,大家應該可以很容易的理解。再次編譯code,並選擇一個preisdent進行編輯

這次出現的return按鈕,試着點擊return按鈕,光標會在4個textField之間循環跳躍,很是活潑。

好了,這個例子終於結束了,很不容易。

 

Nav_All 

 

P.S. 做一個預告吧,之后的例子我將升級成iOS6.0來完成,xcode也將升級,望各位留意,不用新的東西更不是時代的潮流啊,必須與時俱進,謝謝大家!

 

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM