AddPlayer ウィンドウを移動させます
ここでは、「Game」行を無視し、ユーザーが入力したプレーヤー名のみを考慮しましょう。
ユーザーが「キャンセル」ボタンに触れると、ウィンドウが閉じられ、ユーザーが入力したデータはすべて失われます。この部分はOKです。デリゲート オブジェクト (Players ウィンドウ) が「didcancel」メッセージを受信すると、AddPlayer ウィンドウは単純に閉じられます。
ユーザーが完了ボタンに触れたら、新しい Player オブジェクトを作成し、そのプロパティを設定する必要があります。次に、新しいプレーヤーを追加したのでインターフェースを更新する必要があることをデリゲート オブジェクトに伝えます。 : Player オブジェクトを取得し、それをデリゲート オブジェクトに送信します。 PlayerDetailsViewController.h ファイル内の現在の委任プロトコルを次のように変更する必要があります:
- (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]; }
didSave メソッド定義が削除され、didAddPlayer メソッドに置き換えられました。次のステップは、このメソッドを
PlayersViewController.m に実装することです:
@class Player; @protocol PlayerDetailsViewControllerDelegate <NSObject> - (void)playerDetailsViewControllerDidCancel: (PlayerDetailsViewController *)controller; - (void)playerDetailsViewController: (PlayerDetailsViewController *)controller didAddPlayer:(Player *)player; @end
まず、新しい Player オブジェクトを player 配列に追加します。次に、テーブル ビューとデータ ソースの同期を維持する必要があるため、テーブル ビューに新しい行 (一番下) を追加するように指示します。 [self.tableView reloadData] を使用することもできますが、新しい行の挿入をアニメーション化する方が良いでしょう。 UITableViewRowAnimationAutomatic は、iOS 5 で登場した新しい定数です。行の挿入位置に応じて、適切なアニメーションを自動的に選択してくれるので、非常に便利です。
プログラムを実行すると、リストに新しいプレーヤーを追加できるはずです。
ストーリーボードにパフォーマンスの問題があるのではないかと疑われるかもしれません。しかし、ストーリーボード全体を一度にロードすることは特別なことではありません。ストーリーボードはすべての viewController を直接初期化するのではなく、最初の ViewController のみを初期化します。最初のビュー コントローラは TabBarController であるため、それに含まれる 2 つのビュー コントローラ (プレーヤー ビュー コントローラと 2 番目のビュー コントローラ) もロードされます。
他の ViewController は、「セグエ」するまで初期化されません。これらの ViewController を閉じると解放されるため、使用した nib ファイルと同様に、現在使用されている ViewController のみがメモリに残ります。
実験してみましょう。このメソッドを PlayerDetailsViewController.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]; }
initWithCoder メソッドと dealloc メソッドをオーバーライドして、情報を出力します。プログラムを再度実行し、「プレーヤーの追加」ウィンドウを開きます。この時点まで ViewController が割り当てを行っていないことがわかります。ウィンドウ ([キャンセル] または [完了] ボタン) を閉じると、dloc の出力が表示されます。もう一度ウィンドウを開くと、initWithCoder によって出力された内容も確認できます。これは、nib メソッドを使用する場合と同様に、Viewcontroller がオンデマンドでロードされることを完全に示しています。
もう 1 つ、静的セルは UITableViewController でのみ使用できます。ストーリーボード エディターを使用すると、通常の UIViewController の TableView で静的セルを使用できますが、実行時は使用できません。その理由は、UITableViewController が静的セル用の特別なデータ ソース メカニズムを提供するためです。 Xcode は、そのようなプロジェクトのコンパイルを禁止する警告も発行します:
「不正な構成: 静的ビューは UITableViewController インスタンスに埋め込まれている場合にのみ有効です」 (静的テーブル ビューは UITableViewController に対してのみ有効です)。
テンプレートセルは通常のViewControllerで使用できます。もちろんIBではありません。したがって、テンプレート セルと静的セルを使用する場合は、ストーリーボードを使用する必要があります。
テーブルビューで静的セルと動的セルを同時に使用したい場合がありますが、SDK はそれをサポートしていません。どうしてもやりたい場合はこちらを参考にしてください。
注: ウィンドウに静的セルが多すぎる場合 (画面に収まる量を超えている場合)、マウスまたはトラックパッドのスクロール ジェスチャを使用して、ストーリーボード エディターでビューを共有できます。この機能はあまり直感的ではありませんが、便利です。
ゲームピッカーウィンドウ
「プレーヤーの追加」ウィンドウで「ゲーム」セルをタップすると、ユーザーがゲームを選択するためのリストが開きます。したがって、テーブル ビュー コントローラーを追加する必要があります。今回は、Modal メソッドの代わりに Push メソッドを使用します。
新しいテーブル ビュー コントローラーをストーリーボードにドラッグします。 [プレーヤーの追加] ウィンドウからゲーム セルを選択し (上のラベルではなくセル全体が選択されることに注意してください)、Ctrl キーを押したまま新しい TableViewController にドラッグします。これによりセグエが作成されます。 Pushタイプのセグエを選択し、セグエ
に「PickGame」という名前を付けます。
ナビゲーションバーをダブルクリックし、タイトルを「ゲームの選択」に変更します。以下の図に示すように、テンプレート セルのスタイルは Basic に設定され、再利用 ID は「GameCell」に設定されます。
GamePickerViewController という名前の新しい UITableViewController サブクラスを作成します。ストーリーボード内の TableViewController の識別子を 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 窗口。
这很好,不是吗?我们不需要写任何呈现新窗口的代码。只是从静态 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 对象进行编辑。
複数の送信セグエを作成する方法、ViewController を再利用可能にし、複数の受信セグエを処理する方法。
開示ボタン、ジェスチャー、その他のイベントからセグエを呼び出す方法。
標準のプッシュ/モーダルスタイルアニメーションを使用するだけでなく、カスタマイズされたセグエ!
分割ビューコントローラーやポップオーバーなど、iPad でストーリーボードを使用する方法。
最後に、ストーリーボードを手動でロードし、アプリで複数のストーリーボードを使用する方法について説明します。
上記は iOS 5 ストーリーボード紹介 (4) の内容です。その他の関連コンテンツについては、PHP 中国語 Web サイト (www.php.cn) をご覧ください。