Une cellule de vue de tableau ne connaît pas la vue de tableau à laquelle elle appartient et c'est très bien. En fait, c'est comme ça que ça devrait être. Cependant, les personnes novices en la matière sont souvent déconcertées par celui-ci. Par exemple, si l'utilisateur appuie sur un bouton dans une cellule de la vue tableau, comment obtenir le chemin d'index de la cellule pour pouvoir récupérer le modèle correspondant? Dans ce tutoriel, je vais vous montrer comment ne pas faire cela, comment faire habituellement et comment le faire avec style et élégance..
Lorsque l'utilisateur appuie sur une cellule de vue de tableau, celle-ci appelle tableView: didSelectRowAtIndexPath:
du UITableViewDelegate
protocole sur le délégué de la vue table. Cette méthode accepte deux arguments, la vue tableau et le chemin d'index de la cellule sélectionnée..
Le problème que nous allons aborder dans ce tutoriel est toutefois un peu plus complexe. Supposons que nous ayons une vue sous forme de tableau avec des cellules, chaque cellule contenant un bouton. Lorsque vous appuyez sur le bouton, une action est déclenchée. Dans l'action, nous devons extraire le modèle qui correspond à la position de la cellule dans la vue tableau. En d'autres termes, nous devons connaître le chemin d'index de la cellule. Comment pouvons-nous déduire le chemin d'index de la cellule si nous obtenons uniquement une référence au bouton qui a été exploité? C'est le problème que nous allons résoudre dans ce tutoriel.
Créez un nouveau projet dans Xcode en sélectionnant le Application à vue unique modèle de la liste des Application iOS modèles. Nommez le projet Blocs et cellules, ensemble Dispositifs à iPhone, et cliquez Suivant. Indiquez à Xcode où vous souhaitez stocker le projet et appuyez sur Créer.
Ouvrez le Navigateur de projet à gauche, sélectionnez le projet dans le Projet section, et définir la Cible de déploiement à iOS 6. Nous faisons cela pour nous assurer que nous pouvons exécuter l'application sur iOS 6 et iOS 7. La raison en sera expliquée plus tard dans ce tutoriel..
UITableViewCell
Sous-classeSélectionner Nouveau> Fichier… du Fichier menu et choisir Classe Objective-C de la liste des Cacao Touch modèles. Nommez la classe TPSButtonCell
et assurez-vous qu'il hérite de UITableViewCell
.
Ouvrez le fichier d’en-tête de la classe et déclarez deux prises, une UILabel
instance nommée titleLabel
et un UIButton
instance nommée actionButton
.
#importation@interface TPSButtonCell: UITableViewCell @property (faible, non atomique) IBOutlet UILabel * titleLabel; @property (faible, non atomique) IBOutlet UIButton * actionButton; @fin
Ouvrez le fichier d'en-tête du TPSViewController
classe et créer un point de vente nommé tableView
de type UITableView
. le TPSViewController
doit également adopter le UITableViewDataSource
et UITableViewDelegate
les protocoles.
#importation@interface TPSViewController: UIViewController @property (faible, non atomique) IBOutlet UITableView * tableView; @fin
Nous devons également examiner brièvement le fichier d'implémentation du contrôleur de vue. Ouvrez TPSViewController.m et déclarez une variable statique de type. NSString
que nous utiliserons comme identifiant de réutilisation pour les cellules dans la vue tableau.
#import "TPSViewController.h" @implementation TPSViewController statique NSString * CellIdentifier = @ "CellIdentifier"; //… // @fin
Ouvrez le scénariseur principal du projet, Main.Storyboard, et faites glisser une vue de tableau vers la vue du contrôleur de vue. Sélectionnez la vue table et connectez-la la source de données
et déléguer
prises avec l'instance de contrôleur de vue. La vue de tableau étant toujours sélectionnée, ouvrez le Inspecteur d'attributs et définir le nombre de Cellules Prototype à 1
. le Contenu attribut doit être réglé sur Prototypes dynamiques. Vous devriez maintenant voir un prototype de cellule dans la vue tableau.
Sélectionnez la cellule prototype et définissez son Classe à TPSButtonCell
dans le Inspecteur d'identité. La cellule étant toujours sélectionnée, ouvrez le Inspecteur d'attributs et mettre le Style attribuer à Douane et le Identifiant à CellIdentifier.
Faites glisser un UILabel
exemple de la Bibliothèque d'objets à la vue de contenu de la cellule et répétez cette étape pour un UIButton
exemple. Sélectionnez la cellule, ouvrez le Inspecteur de connexions, et connectez le titleLabel
et actionButton
points de vente avec leurs homologues dans la cellule prototype.
Avant de replonger dans le code, nous devons établir une connexion supplémentaire. Sélectionnez le contrôleur de vue, ouvrez le Inspecteur de connexions une fois de plus, et connectez le contrôleur de vue tableView
sortie avec la vue de la table dans le storyboard. Voilà pour l'interface utilisateur.
Peupler la vue de table avec quelques films notables qui ont été publiés en 2013. Dans le TPSViewController
classe, déclarer une propriété de type NSArray
et nommez-le la source de données
. La variable d'instance correspondante contiendra les films que nous montrerons dans la vue tableau. Peupler la source de données
avec une douzaine de films dans le contrôleur de vue viewDidLoad
méthode.
#import "TPSViewController.h" @interface TPSViewController () @property (fort, non atomique) NSArray * dataSource; @fin
- (void) viewDidLoad [super viewDidLoad]; // Configuration de la source de données self.dataSource = @ [@ @ "title": @ "Gravity", @ "year": @ (2013), @ @ "title": @ "12 Years a Slave", @ "année": @ (2013), @ @ "titre": @ "Avant minuit", @ "année": @ (2013), @ @ "titre": @ "American Hustle", @ "année ": @ (2013), @ @" titre ": @" Blackfish ", @" année ": @ (2013), @ @" titre ": @" Capitaine Phillips ", @" année ": @ (2013), @ @ "titre": @ "Nebraska", @ "année": @ (2013), @ @ "titre": @ "Rush", @ "année": @ (2013) , @ @ "title": @ "Frozen", @ "year": @ (2013), @ @ "title": @ "Star Trek Into Darkness", @ "year": @ (2013), @ @ "title": @ "The Conjuring", @ "year": @ (2013), @ @ "title": @ "Side Effects", @ "year": @ (2013), @ @ "titre": @ "L'attaque", @ "année": @ (2013), @ @ "titre": @ "Le Hobbit", @ "année": @ (2013), @ @ " titre ": @" nous sommes ce que nous sommes ", @" année ": @ (2013), @ @" titre ": @" quelque chose dans l'air ", @" année ": @ (2013)];
UITableViewDataSource
ProtocoleLa mise en œuvre de la UITableViewDataSource
le protocole est très facile. Nous avons seulement besoin de mettre en œuvre numberOfSectionsInTableView:
, tableView: numberOfRowsInSection:
, et tableView: cellForRowAtIndexPath:
.
- (NSInteger) numberOfSectionsInTableView: (UITableView *) tableView return self.dataSource? dix; - (NSInteger) tableView: (UITableView *) tableView numberOfRowsInSection: (NSInteger) section return self.dataSource? self.dataSource.count: 0; - (UITableViewCell *) tableView: (UITableView *) tableView cellForRowAtIndexPath: (NSIndexPath *) indexPath TPSButtonCell * cell = (TPSButtonCell *) [tableView dequeueReusableCellWithIdentifier: CellIdentifier pourIndexPath // Récupération de l'élément NSDictionary * item = [self.dataSource objectAtIndex: indexPath.row]; // Configurer la cellule de la vue tableau [cell.titleLabel setText: [NSString stringWithFormat: @ "% @ (% @)", item [@ "title"], item [@ "year"]]; [cell.actionButton addTarget: action propre: @selector (didTapButton :) pourControlEvents: UIControlEventTouchUpInside]; cellule de retour;
Dans tableView: cellForRowAtIndexPath:
, nous utilisons le même identifiant que celui que nous avons défini dans le scénarimage principal, CellIdentifier
, que nous avons déclaré plus tôt dans le tutoriel. Nous lançons la cellule sur une instance de TPSButtonCell
, récupérez l'élément correspondant dans la source de données et mettez à jour le titre de la cellule. Nous ajoutons également une cible et une action pour le UIControlEventTouchUpInside
événement du bouton.
N'oubliez pas d'ajouter une déclaration d'importation pour le TPSButtonCell
classe en haut de TPSViewController.m.
#import "TPSButtonCell.h"
Pour empêcher l’application de se bloquer lorsqu’un bouton est tapé, implémentez didTapButton:
comme indiqué ci-dessous.
- (void) didTapButton: (id) expéditeur NSLog (@ "% s", __PRETTY_FUNCTION__);
Générez le projet et exécutez-le dans le simulateur iOS pour voir ce que nous avons jusqu'à présent. Vous devriez voir une liste de films et appuyer sur le bouton à droite pour enregistrer un message sur la console Xcode. Génial. C'est l'heure du tutoriel.
Lorsque l'utilisateur appuie sur le bouton à droite, il envoie un message de didTapButton:
au contrôleur de vue. Vous devez presque toujours connaître le chemin d'index de la cellule de la table dans laquelle se trouve le bouton. Mais comment obtenir ce chemin d'index? Comme je l'ai mentionné, il y a trois approches possibles. Voyons d'abord comment ne pas le faire.
Jetez un coup d’œil à la mise en œuvre de didTapButton:
et essayer de découvrir ce qui ne va pas avec elle. Repères-tu le danger? Laissez-moi vous aider. Exécutez d'abord l'application sur iOS 7, puis sur iOS 6. Jetez un coup d'œil à ce que Xcode envoie à la console..
- (void) didTapButton: (id) sender // Rechercher une cellule de vue de tableau UITableViewCell * cell = (UITableViewCell *) [[[sender superview] superview] superview]; // Infer Index Path NSIndexPath * indexPath = [self.tableView indexPathForCell: cellule]; // Récupération de l'élément NSDictionary * item = [self.dataSource objectAtIndex: indexPath.row]; // Se connecter à la console NSLog (@ "% @", item [@ "title"]);
Le problème avec cette approche est qu’elle est sujette aux erreurs. Sur iOS 7, cette approche fonctionne très bien. Sur iOS 6, cependant, cela ne fonctionne pas. Pour que cela fonctionne sur iOS 6, vous devez implémenter la méthode comme indiqué ci-dessous. La hiérarchie de vues d'un certain nombre de communs UIView
sous-classes, telles que UITableView
, a changé dans iOS 7 et le résultat est que l'approche ci-dessus ne produit pas un résultat cohérent.
- (void) didTapButton: (id) sender // Rechercher une cellule de vue de tableau UITableViewCell * cell = (UITableViewCell *) [[sender superview] superview]; // Infer Index Path NSIndexPath * indexPath = [self.tableView indexPathForCell: cellule]; // Récupération de l'élément NSDictionary * item = [self.dataSource objectAtIndex: indexPath.row]; // Se connecter à la console NSLog (@ "% @", item [@ "title"]);
Ne pouvons-nous pas simplement vérifier si l'appareil exécute iOS 7? C'est une très bonne idée. Cependant, que ferez-vous lorsque iOS 8 modifiera la hiérarchie de la vue interne de UITableView
encore? Allez-vous patcher votre application chaque fois qu'une version majeure d'iOS est introduite? Et qu'en est-il de tous les utilisateurs qui ne mettent pas à niveau la dernière version (corrigée) de votre application? J'espère qu'il est clair que nous avons besoin d'une meilleure solution.
Une meilleure approche consiste à déduire le chemin d’index de la cellule dans la vue tableau en fonction de la position du expéditeur
, la UIButton
exemple, dans la vue tableau. Nous utilisons convertPoint: toView:
pour y parvenir. Cette méthode convertit le centre du bouton du système de coordonnées du bouton au système de coordonnées de la vue Table. Cela devient alors très facile. Nous appelons indexPathForRowAtPoint:
sur la table voir et passer pointInSuperview
à cela. Cela nous donne un chemin d’index que nous pouvons utiliser pour extraire l’élément correct de la source de données..
- (void) didTapButton: (id) sender // Transmettre l'expéditeur vers UIButton UIButton * button = (UIButton *) expéditeur; // Trouver un point dans la vue d'ensemble CGPoint pointInSuperview = [button.superview Aperçu convertPoint: button.center toView: self.tableView]; // Infer Index Path NSIndexPath * indexPath = [self.tableView indexPathForRowAtPoint: pointInSuperview]; // Récupération de l'élément NSDictionary * item = [self.dataSource objectAtIndex: indexPath.row]; // Se connecter à la console NSLog (@ "% @", item [@ "title"]);
Cette approche peut sembler lourde, mais en réalité elle ne l’est pas. C'est une approche qui n'est pas affectée par les changements dans la hiérarchie de vues de UITableView
et il peut être utilisé dans de nombreux scénarios, y compris dans les vues de collection.
Il existe une solution supplémentaire pour résoudre le problème, qui nécessite un peu plus de travail. Le résultat, cependant, est un affichage de l'Objective-C moderne. Commencez par consulter le fichier d’en-tête du TPSButtonCell
et déclarer une méthode publique nommée setDidTapButtonBlock:
qui accepte un bloc.
#importation@interface TPSButtonCell: UITableViewCell @property (faible, non atomique) IBOutlet UILabel * titleLabel; @property (faible, non atomique) IBOutlet UIButton * actionButton; - (void) setDidTapButtonBlock: (void (^) (expéditeur id)) didTapButtonBlock; @fin
Dans le dossier de mise en oeuvre de TPSButtonCell
créer une propriété privée nommée didTapButtonBlock
comme indiqué ci-dessous. Notez que la propriété attribuée est définie sur copie
, parce que les blocs doivent être copiés pour garder une trace de leur état capturé en dehors de la portée d'origine.
#import "TPSButtonCell.h" @interface TPSButtonCell () @property (copie non atomique) void (^ didTapButtonBlock) (id expéditeur); @fin
Au lieu d’ajouter une cible et une action pour le UIControlEventTouchUpInside
événement dans le contrôleur de vue tableView: cellForRowAtIndexPath:
, nous ajoutons une cible et une action dans awakeFromNib
dans le TPSButtonCell
classe elle-même.
- (vide) awakeFromNib [super awakeFromNib]; [self.actionButton addTarget: auto action: @selector (didTapButton :) pourControlEvents: UIControlEventTouchUpInside];
L'implémentation de didTapButton:
est trivial.
- (void) didTapButton: (id) expéditeur if (self.didTapButtonBlock) self.didTapButtonBlock (expéditeur);
Cela peut sembler beaucoup de travail pour un simple bouton, mais tenez vos chevaux jusqu'à ce que nous ayons refactorisé tableView: cellForRowAtIndexPath:
dans le TPSViewController
classe. Au lieu d'ajouter une cible et une action au bouton de la cellule, nous définissons la cellule didTapButtonBlock
. Obtenir une référence à l'élément correspondant de la source de données devient très, très facile. Cette solution est de loin la solution la plus élégante à ce problème.
- (UITableViewCell *) tableView: (UITableView *) tableView cellForRowAtIndexPath: (NSIndexPath *) indexPath TPSButtonCell * cell = (TPSButtonCell *) [tableView dequeueReusableCellWithIdentifier: CellIdentifier pourIndexPath: // Récupération de l'élément NSDictionary * item = [self.dataSource objectAtIndex: indexPath.row]; // Configurer la cellule de la vue tableau [cell.titleLabel setText: [NSString stringWithFormat: @ "% @ (% @)", item [@ "title"], item [@ "year"]]; [cell setDidTapButtonBlock: ^ (expéditeur de l'identifiant) NSLog (@ "% @", élément [@ "titre"]); ]; cellule de retour;
Même si le concept de blocs existe depuis des décennies, les développeurs de Cocoa ont dû attendre jusqu'en 2011. Les blocs peuvent faciliter la résolution de problèmes complexes et simplifier le code complexe. Depuis l'introduction des blocs, Apple en a largement fait usage dans ses propres API. Je vous encourage donc à suivre l'exemple d'Apple en tirant parti des blocs dans vos propres projets..