首頁  >  文章  >  後端開發  >  iOS 5 故事板入門(4)

iOS 5 故事板入門(4)

黄舟
黄舟原創
2017-01-20 10:26:281300瀏覽

讓 AddPlayer 視窗動起來

現在,我們先忽略「Game」行,只考慮使用者輸入的玩家名稱。
當使用者觸碰 Cancel 按鈕,視窗會關閉,使用者輸入的資料都會遺失。這部分是 OK 的。在委託物件(Players 視窗)收到「didcancel」訊息時,會簡單地關閉 AddPlayer 視窗。

當使用者觸摸 done 按鈕時,我們應該建立新的 Player 物件並設定它的屬性。然後告訴委託對像我們新增了新的玩家,他應該刷新介面。

因此, PlayerDetailsViewController.m中的done 方法變成:

- (IBAction)done:(id)sender {
        Player *player = [[Player alloc] init];
        player.name = self.nameTextField.text;
        player.game = @"Chess";
        player.rating = 1;
        [self.delegate playerDetailsViewController:self         didAddPlayer:player];
}

   

我們需要導入頭檔:

方法現在創建了新的Player物件並將其傳送給委託對象。目前在PlayerDetailsViewController.h檔案中的委託協定中的需要修改為:

@class Player;  
@protocol PlayerDetailsViewControllerDelegate <NSObject>
- (void)playerDetailsViewControllerDidCancel:   (PlayerDetailsViewController *)controller;
- (void)playerDetailsViewController:   (PlayerDetailsViewController *)controller    didAddPlayer:(Player *)player;
@end

   

didSave 方法定義去掉了,我們用一個 didAddPlayer 方法取代了。接下來的時期是在

 PlayersViewController.m 中實作此方法:

  - (void)playerDetailsViewController:   (PlayerDetailsViewController *)controller    didAddPlayer:(Player *)player{
        [self.players addObject:player];
        NSIndexPath *indexPath =       [NSIndexPath indexPathForRow:[self.players count] - 1         inSection:0];
        [self.tableView insertRowsAtIndexPaths:       [NSArray arrayWithObject:indexPath]         withRowAnimation:UITableViewRowAnimationAutomatic];
        [self dismissViewControllerAnimated:YES completion:nil];
}

   

首先將新的 Player 物件加入 players 陣列。然後告訴table view 增加新的行(在底部),因為 table view 和 資料來源必須保持同步。我們也可以用[self.tableView reloadData] ,但以動畫的方式插入一個新行顯得更好。 UITableViewRowAnimationAutomatic是 iOS 5 中出現的新常數,它會自動選擇適當的動畫,這取決於你在何處插入行,非常方便。

運行程序,你應該可以加入新的玩家到列表中了!

你可能會懷疑故事板的表現會有問題。但一次加載一整個 storyboard 沒什麼了不起的。故事板並不會直接初始化所有的viewController,它只會初始化 initial view controller。因為我們的 initial view controller s是一個TabBarController,所以它包含的兩個 view controller 也被載入(Players View Controller和第二個viewcontroller)。

其他的 ViewController 不會被初始化,一直到你 “segue到”它們。一旦你關閉這些 ViewController,它們就會被釋放,因此只有目前使用的 ViewController 會留在記憶體中,就如你曾經使用的一個個的nib 檔案。

讓我們來做個實驗。將此方法加到 PlayerDetailsViewController.m 中:

- (id)initWithCoder:(NSCoder *)aDecoder {
        if ((self = [super initWithCoder:aDecoder])) {
               NSLog(@"init PlayerDetailsViewController");
        }
        return self;
 }
- (void)dealloc {
        NSLog(@"dealloc PlayerDetailsViewController");
}

   

我們覆寫了 initWithCoder 和 dealloc方法以輸出一些資訊。再次執行程序,開啟 Add Player 視窗。可以看到 ViewController 直到此時才 alloc。關閉視窗(Cancel 或 Done 按鈕),可以看到delloc 輸出的內容。再次打開窗口,也可以看到 initWithCoder 輸出的內容。這充分說明 Viewcontroller 是按需載入的,就如使用nib 方式一般。

還有一點,靜態 cell 只能用於 UITableViewController。故事板編輯器允許你在一般的UIViewController 中的 TableView 上使用靜態 cell,但執行時就不行了。原因是 UITableViewController 為靜態cell 提供了特殊的資料來源機制。 Xcode 甚至會發出警告,禁止你編譯這類專案:

 「Illegal Configuration: Statictable views are only valid when embedded in UITableViewController instances」(靜態的 table view 只對 UITableViewController 有效)。

模板 cell 則可以在常規的 ViewController 中使用。當然IB 中不行。因此,如果你想使用模板 cell 和靜態 cell,都必須使用故事板。

你可能想同時在一個 table view 中使用靜態 cell 和動態cell,但 SDK 不支援。如果你確實想這樣做,請參考 這裡。

注意:如果你的視窗有太多的靜態 cell ——超過了螢幕所能容納的空間——你可以在故事板編輯器中透過滑鼠或觸控板的滾動手勢來股東視圖。這個功能不是很直觀,但很有用。

Game Picker 視窗

輕擊 Add Player 視窗上的 Game 儲存格,會開啟一個列表,讓使用者選擇遊戲。因此需要增加一個Table View Controller。這次,我們用 Push 的方式而不是 Modal 的方式。

拖一個新的 Table View Controller 到故事板。從Add Player 視窗中選擇 Game 儲存格(注意選取整個 cell,而不是選取上面的 Lable),按住 ctrl 鍵,拖曳到新的TableViewController 上。這將創建一個 segue。選擇 Push 類型的 segue,並命名該 segue

為 “PickGame”。

雙擊導航條,將標題修改為「Choose Game」。模板 cell 的style 設定為 Basic,重用 ID 設定為“ GameCell”,如下圖所示:

新建一個 UITableViewController 子類,名為GamePickerViewController。別忘了將故事板中的 TableViewController 的 Identifier 設定為 GamePickerViewController.

先让我们为新场景准备一些数据。在GamePickerViewController.h 中添加实例变量:

@interface GamePickerViewController : UITableViewController {
     NSArray * games;
}

   

在 GamePickerViewController.m 的viewDidLoad 方法中:

- (void)viewDidLoad {
        [super viewDidLoad];
        games = [NSArray arrayWithObjects:
              @"Angry Birds",
              @"Chess",
              @"Russian Roulette",
              @"Spin the Bottle",
              @"Texas Hold’em Poker",
              @"Tic-Tac-Toe",
              nil];
}

   

因为在 viewDidUnload 中初始化games 数组,所以必须在 viewDidUnload 中进行释放:

- (void)viewDidUnload {
        [super viewDidUnload];
        games = nil;
}

   

尽管这个窗口的 viewDidUnload实际上永远不会调用(我们永远也不会用另一个视图来覆盖它),但保持 release/alloc 平衡是一种良好的做法。

修改 TableView 的数据源方法:

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
        return 1;
}
- (NSInteger)tableView:(UITableView *)tableView    numberOfRowsInSection:(NSInteger)section {
        return [games count];
}  
- (UITableViewCell *)tableView:(UITableView *)tableView    cellForRowAtIndexPath:(NSIndexPath *)indexPath {
        UITableViewCell *cell = [tableView       dequeueReusableCellWithIdentifier:@"GameCell"];
        cell.textLabel.text = [games objectAtIndex:indexPath.row];
        return cell;
}

   

关于数据源就这么多。运行程序,轻击 Game 行,Choose Game 窗口会出现。点击任何行都不会有任何动作。当然,由于窗口是以Push 方式呈现的,你可以点击导航栏的返回按钮返回 Add Player 窗口。

iOS 5 故事板入門(4)

这很好,不是吗?我们不需要写任何呈现新窗口的代码。只是从静态 cell 拖拽了一条线到新窗口而已。(注意当你点击Game 行是, table view 的委托方法即PlayerDetailsViewController的 didSelectRowAtIndexPath是会被调用的,请不要在其中加入任何代码,以免造成混乱)。

当然,目前新窗口还没有什么作用。 我们必须在 GamePickerViewController.h中定义一个新的委托协议使他能返回一些数据:

@class GamePickerViewController;  
@protocol GamePickerViewControllerDelegate <NSObject>
- (void)gamePickerViewController:   (GamePickerViewController *)controller    didSelectGame:(NSString*)game;
@end  
@interface GamePickerViewController : UITableViewController  
@property (nonatomic, weak) id <GamePickerViewControllerDelegate> delegate;
@property (nonatomic, strong) NSString *game;  
@end

   

修改 GamePickerViewController.m为:

@implementation GamePickerViewController {
        NSArray *games;
        NSUInteger selectedIndex;
}
@synthesize delegate;
@synthesize game;

添加了一个新的实例变量 selectedIndex,以及属性合成语句。

在 viewDidLoad 方法末增加:

selectedIndex = [games indexOfObject:self.game];

   

用户选定的游戏名称将保存到 self.game 中。selectedIndex则是 game 位于 games 数组中的索引。该索引用于在 TableView 中用一个对钩图标来标出所选 cell。因此,self.game 必须在视图加载之前就必须指定一个值。这可以通过prepareForSegue 方法进行,这个方法在 viewDidLoad 之前调用。

修改 cellForRowAtIndexPathto 方法为:

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
        UITableViewCell *cell = [tableView       dequeueReusableCellWithIdentifier:@"GameCell"];
        cell.textLabel.text = [games objectAtIndex:indexPath.row];
        if (indexPath.row == selectedIndex)
               cell.accessoryType =            UITableViewCellAccessoryCheckmark;
        else
               cell.accessoryType = UITableViewCellAccessoryNone;
        return cell;
}

   

在当前选定的游戏名称后面标志一个对钩图标。这对于用户来说应该是需要的。

修改 didSelectRowAtIndexPath方法如下:

- (void)tableView:(UITableView *)tableView    didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
        [tableView deselectRowAtIndexPath:indexPath animated:YES];
        if (selectedIndex != NSNotFound)      {
               UITableViewCell *cell = [tableView            cellForRowAtIndexPath:[NSIndexPath              
               indexPathForRow:selectedIndex inSection:0]];
               cell.accessoryType = UITableViewCellAccessoryNone;
        }
        selectedIndex = indexPath.row;
        UITableViewCell *cell =       [tableView cellForRowAtIndexPath:indexPath];
        cell.accessoryType = UITableViewCellAccessoryCheckmark;
        NSString *theGame = [games objectAtIndex:indexPath.row];
        [self.delegate gamePickerViewController:self       didSelectGame:theGame];
}

   

首先反选改行。这将使单元格从高亮的蓝色变回正常的白色。然后移掉或添加单元格末尾的对钩标志(依赖于选中状态)。最终,将用于已选的游戏名称通过委托协议返回给委托对象。

运行程序进行测试。从游戏列表中点击一个游戏。该行末自动添加一个对钩。点击另一个游戏名称,对钩随之移动到新的选择。当你点击行时,窗口应该关闭,但什么也没发生。因为实际上我们还没有将委托对象连接进来。

#import "GamePickerViewController.h"

   

在 @interface 语句中添加协议:

@interface PlayerDetailsViewController : UITableViewController <GamePickerViewControllerDelegate>
   
在 PlayerDetailsViewController.m,添加prepareForSegue 方法:
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
        if ([segue.identifier isEqualToString:@"PickGame"]) {
               GamePickerViewController *gamePickerViewController =            segue.destinationViewController;
               gamePickerViewController.delegate = self;
               gamePickerViewController.game = game;
        }
}

   

就如我们前面所做的一样。这次的目标场景是 game picker 窗口。记住,该方法发生在GamePickerViewController 被初始化之后,视图被加载之前。

 “game” 变量是新加的。它是一个实例变量:

@implementation PlayerDetailsViewController {
        NSString *game;
}

   

该变量用于存储用户选定的游戏,因此我们可以把它放到 Player 对象中。我们可以给它一个默认值。通过initWithCoder 方法是个不错的选择:

- (id)initWithCoder:(NSCoder *)aDecoder {
        if ((self = [super initWithCoder:aDecoder])) {
               NSLog(@"init PlayerDetailsViewController");
               game = @"Chess";
        }
        return self;
}

   

如果你用过 nib 文件,initWithCoder 你应该很熟悉。在故事板中,这些方法同样存在:initWithCoder、awakeFromNib和viewDidLoad。你可以把故事板看成是 nib 文件的集合,再加上它们之间的转换和关系。但是故事板中的 view controller 仍然是用同样的方法编码和解码。

修改 viewDidLoad ,让 Game 单元格显示游戏名:

- (void)viewDidLoad {
        [super viewDidLoad];
        self.detailLabel.text = game;
}

接下来实现委托方法:

#pragma mark - GamePickerViewControllerDelegate  
- (void)gamePickerViewController:   (GamePickerViewController *)controller    didSelectGame:(NSString*)theGame {
        game = theGame;
        self.detailLabel.text = game;
        [self.navigationController popViewControllerAnimated:YES];
}

很简单,将新游戏名称赋给我们的实例变量 game 以及 cell 的Label,然后关闭游戏选择窗口。由于它是一个 Push 式 segue,从导航控制器的栈顶中将它弹出即可。

在 done 方法中,我们将已选游戏放入 Player 对象:

- (IBAction)done:(id)sender {
        Player *player = [[Player alloc] init];
        player.name = self.nameTextField.text;
        player.game = game;
        player.rating = 1;
        [self.delegate playerDetailsViewController:self didAddPlayer:player];
}

好了,我们的游戏选择窗口就完成了!

接下来的内容

这是 示例工程源代码。

恭喜你,现在你已经基本掌握了故事板的使用,能够用 segue 在多个view controller 中导航及转换。

关于更多 iOS 5 中故事板的学习内容,请查看我们的新书iOS 5 教程,其中还包含了:

  • 修改 PlayerDetailsViewController 以便对 Player 对象进行编辑。

  • 如何創建多個outgoing segue,如何使你的 ViewController 能夠復用,能處理多個 incoming segue。

  • 如何從 disclosure 按鈕、手勢及任意事件呼叫 segue。

  • 定制 segue——而不僅僅使用標準的Push/Modal式動畫!

  • 如何在 iPad 中使用故事板,包括 split-view controller 以及 popover。

  • 最後,如何手動加載故事板,並在app 中使用多個故事板。

以上就是iOS 5 故事板入門(4)的內容,更多相關內容請關注PHP中文網(www.php.cn)!


陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn