Faire bouger la fenêtre AddPlayer
Pour l'instant, ignorons la ligne "Game" et ne considérons que le nom du joueur saisi par l'utilisateur.
Lorsque l'utilisateur touche le bouton Annuler, la fenêtre sera fermée et toutes les données saisies par l'utilisateur seront perdues. Cette partie est OK. Lorsque l'objet délégué (fenêtre Players) reçoit le message "didcancel", la fenêtre AddPlayer est simplement fermée.
Lorsque l'utilisateur appuie sur le bouton Terminé, nous devons créer un nouvel objet Player et définir ses propriétés. Dites ensuite à l'objet délégué que nous avons ajouté un nouveau joueur et qu'il doit actualiser l'interface.
Par conséquent, la méthode done dans PlayerDetailsViewController.m devient :
- (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]; }
Nous devons importer le fichier d'en-tête :
#import "Player.h"
la méthode done crée désormais un nouvel objet Player et l'envoie à l'objet délégué. Le protocole de délégation actuel dans le fichier PlayerDetailsViewController.h doit être modifié comme suit :
@class Player; @protocol PlayerDetailsViewControllerDelegate <NSObject> - (void)playerDetailsViewControllerDidCancel: (PlayerDetailsViewController *)controller; - (void)playerDetailsViewController: (PlayerDetailsViewController *)controller didAddPlayer:(Player *)player; @end
La définition de la méthode didSave a été supprimée et nous l'avons remplacée. avec une méthode didAddPlayer. L'étape suivante consiste à implémenter cette méthode dans
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]; }
Ajoutez d'abord un nouveau joueur à l'objet tableau des joueurs. . Dites ensuite à la vue tabulaire d’ajouter la nouvelle ligne (en bas), car la vue tabulaire et la source de données doivent rester synchronisées. On pourrait aussi utiliser [self.tableView reloadData], mais il serait préférable d'animer l'insertion d'une nouvelle ligne. UITableViewRowAnimationAutomatic est une nouvelle constante apparue dans iOS 5. Elle sélectionnera automatiquement l'animation appropriée en fonction de l'endroit où vous insérez la ligne, ce qui est très pratique.
Exécutez le programme et vous devriez pouvoir ajouter de nouveaux joueurs à la liste !
Vous soupçonnez peut-être qu'il y a des problèmes de performances avec le storyboard. Mais charger un storyboard entier en même temps n’a rien d’extraordinaire. Le storyboard n'initialise pas directement tous les viewControllers, il initialise uniquement le contrôleur de vue initial. Étant donné que notre contrôleur de vue initial est un TabBarController, les deux contrôleurs de vue qu'il contient sont également chargés (le Players View Controller et le deuxième contrôleur de vue).
Les autres ViewControllers ne seront pas initialisés tant que vous ne les aurez pas « enchaînés ». Une fois que vous fermez ces ViewControllers, ils sont libérés, de sorte que seul le ViewController actuellement utilisé restera en mémoire, tout comme les fichiers nib que vous avez utilisés.
Faisons une expérience. Ajoutez cette méthode à PlayerDetailsViewController.m :
- (id)initWithCoder:(NSCoder *)aDecoder { if ((self = [super initWithCoder:aDecoder])) { NSLog(@"init PlayerDetailsViewController"); } return self; } - (void)dealloc { NSLog(@"dealloc PlayerDetailsViewController"); }
Nous remplaçons les méthodes initWithCoder et dealloc pour afficher certaines informations. Exécutez à nouveau le programme et ouvrez la fenêtre Ajouter un lecteur. Vous pouvez voir que le ViewController n'a pas été alloué jusqu'à présent. Fermez la fenêtre (bouton Annuler ou Terminé) et vous pourrez voir la sortie de Delloc. Ouvrez à nouveau la fenêtre et vous pourrez également voir le contenu généré par initWithCoder. Cela démontre pleinement que le Viewcontroller est chargé à la demande, tout comme l'utilisation de la méthode nib.
Encore une chose, les cellules statiques ne peuvent être utilisées que dans UITableViewController. L'éditeur de storyboard vous permet d'utiliser des cellules statiques sur un TableView dans un UIViewController standard, mais pas au moment de l'exécution. La raison en est que UITableViewController fournit un mécanisme de source de données spécial pour les cellules statiques. Xcode émettra même un avertissement pour vous interdire de compiler de tels projets :
"Configuration illégale : les vues statiques ne sont valides que lorsqu'elles sont intégrées dans des instances de UITableViewController" (les vues de table statiques ne sont valables que pour UITableViewController).
Les cellules modèles peuvent être utilisées dans ViewController standard. Bien sûr pas en IB. Par conséquent, si vous souhaitez utiliser des cellules modèles et des cellules statiques, vous devez utiliser des storyboards.
Vous souhaiterez peut-être utiliser simultanément des cellules statiques et des cellules dynamiques dans une vue tabulaire, mais le SDK ne le prend pas en charge. Si vous voulez vraiment faire cela, veuillez vous référer ici.
Remarque : Si votre fenêtre comporte trop de cellules statiques - plus que ce que l'écran peut en accueillir - vous pouvez utiliser les gestes de défilement de la souris ou du trackpad pour partager la vue dans l'éditeur de storyboard. Cette fonctionnalité n'est pas très intuitive, mais elle est utile.
Fenêtre de sélection de jeu
Appuyez sur la cellule Jeu dans la fenêtre Ajouter un joueur et une liste s'ouvrira permettant à l'utilisateur de sélectionner un jeu. Par conséquent, un contrôleur de vue de table doit être ajouté. Cette fois, nous utilisons la méthode Push au lieu de la méthode Modal.
Faites glisser un nouveau contrôleur de vue de table vers le storyboard. Sélectionnez la cellule Jeu dans la fenêtre Ajouter un joueur (notez que la cellule entière est sélectionnée, pas l'étiquette ci-dessus), maintenez la touche Ctrl enfoncée et faites-la glisser vers le nouveau TableViewController. Cela créera une transition. Sélectionnez la séquence de type Push et nommez la séquence
comme « PickGame ».
Double-cliquez sur la barre de navigation et changez le titre en « Choisir un jeu ». Le style de la cellule du modèle est défini sur Basic et l'ID de réutilisation est défini sur "GameCell", comme le montre la figure suivante :
Créez une nouvelle sous-classe UITableViewController nommée GamePickerViewController. N'oubliez pas de définir l'identifiant de TableViewController dans le storyboard sur 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 对象进行编辑。
Comment créer plusieurs séquences sortantes, comment rendre votre ViewController réutilisable et gérer plusieurs séquences entrantes.
Comment appeler à partir du bouton de divulgation, du geste et de tout événement.
Enchaînement personnalisé - au lieu de simplement utiliser une animation de style Push/Modal standard !
Comment utiliser les storyboards sur iPad, y compris le contrôleur à vue partagée et le popover.
Enfin, comment charger manuellement des storyboards et utiliser plusieurs storyboards dans l'application.
Ce qui précède est le contenu de l'introduction du storyboard iOS 5 (4). Pour plus de contenu connexe, veuillez faire attention au site Web PHP chinois (www.php.cn) !