AddPlayer 창을 이동시키세요
지금은 "Game" 줄을 무시하고 사용자가 입력한 플레이어 이름만 고려하겠습니다.
사용자가 취소 버튼을 터치하면 창이 닫히고 사용자가 입력한 모든 데이터가 손실됩니다. 이 부분은 괜찮습니다. 대리자 개체(Players 창)가 "didcancel" 메시지를 받으면 AddPlayer 창이 닫힙니다.
사용자가 완료 버튼을 터치하면 새 플레이어 개체를 만들고 해당 속성을 설정해야 합니다. 그런 다음 새 플레이어를 추가했으며 인터페이스를 새로 고쳐야 한다고 위임 개체에 알립니다.
따라서 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]; }
헤더 파일을 가져와야 합니다.
#import "Player.h"
done 메소드는 이제 새 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 개체를 추가합니다. 그런 다음 테이블 뷰와 데이터 소스가 동기화 상태를 유지해야 하므로 테이블 뷰에 새 행(하단)을 추가하도록 지시합니다. [self.tableView reloadData]를 사용할 수도 있지만 새 행 삽입에 애니메이션을 적용하는 것이 더 좋습니다. UITableViewRowAnimationAutomatic은 iOS 5에 등장한 새로운 상수입니다. 행을 삽입하는 위치에 따라 적절한 애니메이션을 자동으로 선택하므로 매우 편리합니다.
프로그램을 실행하면 목록에 새로운 플레이어를 추가할 수 있습니다!
스토리보드에 성능 문제가 있는 것으로 의심할 수 있습니다. 그러나 전체 스토리보드를 한 번에 로드하는 것은 결코 멋진 일이 아닙니다. 스토리보드는 모든 viewController를 직접 초기화하지 않고 초기 뷰 컨트롤러만 초기화합니다. 초기 뷰 컨트롤러는 TabBarController이기 때문에 여기에 포함된 두 개의 뷰 컨트롤러(Players View Controller와 두 번째 뷰 컨트롤러)도 로드됩니다.
다른 ViewController는 "segue"할 때까지 초기화되지 않습니다. ViewController를 닫으면 해제되므로 사용한 nib 파일과 마찬가지로 현재 사용되는 ViewController만 메모리에 유지됩니다.
실험을 해보자. PlayerDetailsViewController.m에 이 메소드를 추가합니다:
- (id)initWithCoder:(NSCoder *)aDecoder { if ((self = [super initWithCoder:aDecoder])) { NSLog(@"init PlayerDetailsViewController"); } return self; } - (void)dealloc { NSLog(@"dealloc PlayerDetailsViewController"); }
initWithCoder 및 dealloc 메소드를 재정의하여 일부 정보를 출력합니다. 프로그램을 다시 실행하고 플레이어 추가 창을 엽니다. 이 시점까지는 ViewController가 할당되지 않았음을 알 수 있습니다. 창을 닫으면(취소 또는 완료 버튼) delloc의 출력을 볼 수 있습니다. 창을 다시 열면 initWithCoder가 출력한 콘텐츠도 볼 수 있습니다. 이는 nib 메서드를 사용하는 것처럼 Viewcontroller가 요청 시 로드된다는 것을 완전히 보여줍니다.
한 가지 더, 정적 셀은 UITableViewController에서만 사용할 수 있습니다. 스토리보드 편집기를 사용하면 일반 UIViewController의 TableView에서 정적 셀을 사용할 수 있지만 런타임에는 사용할 수 없습니다. 그 이유는 UITableViewController가 정적 셀에 대한 특별한 데이터 소스 메커니즘을 제공하기 때문입니다. Xcode는 이러한 프로젝트를 컴파일하지 못하도록 경고도 표시합니다.
"잘못된 구성: 정적 테이블 보기는 UITableViewController 인스턴스에 포함될 때만 유효합니다."(정적 테이블 보기는 UITableViewController에만 유효합니다.)
템플릿 셀은 일반 ViewController에서 사용할 수 있습니다. 물론 IB에서는 그렇지 않습니다. 따라서 템플릿 셀과 정적 셀을 사용하려면 스토리보드를 사용해야 합니다.
테이블 뷰에서 정적 셀과 동적 셀을 동시에 사용하고 싶을 수도 있지만 SDK에서는 이를 지원하지 않습니다. 꼭 해보고 싶다면 여기를 참고해주세요.
참고: 창에 정적 셀이 너무 많으면(화면이 수용할 수 있는 것보다 많은 경우) 마우스나 트랙패드 스크롤 동작을 사용하여 스토리보드 편집기에서 보기를 공유할 수 있습니다. 이 기능은 그다지 직관적이지는 않지만 유용합니다.
게임 선택 창
플레이어 추가 창에서 게임 셀을 탭하면 사용자가 게임을 선택할 수 있는 목록이 열립니다. 따라서 Table View Controller를 추가해야 합니다. 이번에는 Modal 방식이 아닌 Push 방식을 사용하겠습니다.
새 Table View Controller를 스토리보드로 드래그하세요. 플레이어 추가 창에서 게임 셀을 선택하고(위의 레이블이 아닌 전체 셀이 선택됨에 유의) Ctrl 키를 누른 채 새 TableViewController로 드래그합니다. 그러면 segue가 생성됩니다. Push 유형 segue를 선택하고 segue
이름을 "PickGame"으로 지정합니다.
탐색 표시줄을 두 번 클릭하고 제목을 "게임 선택"으로 변경합니다. 다음 그림과 같이 템플릿 셀의 스타일은 Basic으로 설정되고 재사용 ID는 "GameCell"로 설정됩니다.
라는 이름의 새 UITableViewController 하위 클래스를 만듭니다. GamePickerViewController. 스토리보드에서 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 对象进行编辑。
여러 개의 나가는 segue를 만드는 방법, ViewController를 재사용 가능하게 만들고 여러 개의 들어오는 segue를 처리하는 방법.
공개 버튼, 동작 및 모든 이벤트에서 segue를 호출하는 방법.
표준 푸시/모달 스타일 애니메이션을 사용하는 대신 사용자 정의 segue를 사용하세요!
분할 뷰 컨트롤러 및 팝오버를 포함하여 iPad에서 스토리보드를 사용하는 방법입니다.
마지막으로 앱에서 스토리보드를 수동으로 로드하고 여러 스토리보드를 사용하는 방법입니다.
위 내용은 iOS 5 스토리보드 소개(4) 내용입니다. 더 많은 관련 내용은 PHP 중국어 홈페이지(www.php.cn)를 참고해주세요!