web-dev-qa-db-fra.com

Dans un storyboard, comment créer une cellule personnalisée à utiliser avec plusieurs contrôleurs?

J'essaie d'utiliser des storyboards dans une application sur laquelle je travaille. Dans l'application, il y a Listes et tilisateurs et chacun contient une collection de l'autre (membres d'une liste, listes appartenant à un utilisateur). Donc, en conséquence, j’ai ListCell et UserCell classes. L'objectif est de pouvoir les réutiliser dans l'application (c'est-à-dire dans n'importe lequel de mes contrôleurs tableview).

C'est là que je rencontre un problème.

Comment créer une cellule tableview personnalisée dans le storyboard pouvant être réutilisée dans n'importe quel contrôleur de vue?

Voici les choses spécifiques que j'ai essayées jusqu'à présent.

  • Dans le contrôleur n ° 1, une cellule prototype a été ajoutée, définissez la classe sur ma sous-classe UITableViewCell, définissez l'ID de réutilisation, ajoutez les étiquettes et connectez-les aux prises de la classe. Dans le contrôleur n ° 2, ajouté une cellule prototype vide, définissez-la sur la même classe et réutilisez l'identifiant comme auparavant. Lorsqu'il est exécuté, les étiquettes n'apparaissent jamais lorsque les cellules sont affichées dans le contrôleur n ° 2. Fonctionne bien dans le contrôleur n ° 1.

  • Conçu chaque type de cellule dans un NIB différent et connecté à la classe de cellule appropriée. Dans le storyboard, ajout d'un prototype de cellule vide et définition de sa classe et de son identifiant de réutilisation pour faire référence à ma classe de cellule. Dans les méthodes viewDidLoad des contrôleurs, enregistrez ces fichiers NIB pour l'identifiant de réutilisation. Lorsqu’il est montré, les cellules des deux contrôleurs étaient vides comme le prototype.

  • Les prototypes conservés dans les deux contrôleurs vident et définissent la classe et réutilisent l'identifiant dans ma classe de cellule. Construit l'interface utilisateur des cellules entièrement en code. Les cellules fonctionnent parfaitement dans tous les contrôleurs.

Dans le second cas, je soupçonne que le prototype l'emporte toujours sur la carte NIB et si je tuais les cellules du prototype, l'enregistrement de ma NIB pour l'identifiant de réutilisation fonctionnerait. Mais dans ce cas, je ne serais pas en mesure de configurer des séparations des cellules vers d'autres images, ce qui est vraiment l'intérêt d'utiliser des storyboards.

En fin de compte, je souhaite deux choses: câbler des flux basés sur la table dans le storyboard et définir les dispositions de cellules visuellement plutôt que dans le code. Je ne vois pas comment obtenir les deux à ce jour.

216
Cliff W

Si je comprends bien, vous voulez:

  1. Concevez une cellule dans IB pouvant être utilisée dans plusieurs scènes de scénario.
  2. Configurez des séquences de story-board uniques à partir de cette cellule, en fonction de la scène dans laquelle elle se trouve.

Malheureusement, il n'y a actuellement aucun moyen de le faire. Pour comprendre pourquoi vos tentatives précédentes n'ont pas fonctionné, vous devez en savoir plus sur le fonctionnement des storyboards et des cellules de vue de table prototype. (Si vous ne vous souciez pas de pourquoi ces autres tentatives n'ont pas fonctionné, n'hésitez pas à partir maintenant. Je n'ai aucune solution de contournement magique à vous proposer. , autre que de suggérer que vous déposiez un bogue.)

Un storyboard n'est, par essence, guère plus qu'une collection de fichiers .xib. Lorsque vous chargez un contrôleur d'affichage de table dont certaines cellules prototypes proviennent d'un storyboard, voici ce qui se passe:

  • Chaque cellule prototype est en fait sa propre mini-pointe intégrée. Ainsi, lorsque le contrôleur de vue de table est en cours de chargement, il parcourt chacune des nibs de la cellule prototype et appelle -[UITableView registerNib:forCellReuseIdentifier:].
  • La vue tableau demande au contrôleur les cellules.
  • Vous appelez probablement -[UITableView dequeueReusableCellWithIdentifier:]
  • Lorsque vous demandez une cellule avec un identifiant de réutilisation donné, elle vérifie si elle possède un nib enregistré. Si c'est le cas, il instancie une instance de cette cellule. Ceci est composé des étapes suivantes:

    1. Regardez la classe de la cellule, telle que définie dans la pointe de la cellule. Appelez [[CellClass alloc] initWithCoder:].
    2. La méthode -initWithCoder: passe en revue, ajoute des sous-vues et définit les propriétés définies dans le nib. (IBOutlets'attrape probablement ici aussi, bien que je ne l'aie pas testé; cela peut arriver dans -awakeFromNib)
  • Vous configurez votre cellule comme vous le souhaitez.

Il est important de noter ici qu’il existe une distinction entre la classe de la cellule et l’aspect visuel de la cellule. Vous pouvez créer deux cellules prototypes distinctes de la même classe, mais avec leurs sous-vues présentées de manière complètement différente. En fait, si vous utilisez les styles par défaut UITableViewCell, c'est exactement ce qui se passe. Le style "Par défaut" et le style "Sous-titre", par exemple, sont tous deux représentés par la même classe UITableViewCell.

Ceci est important: La classe de la cellule n'a pas de corrélation un-à-un avec un hiérarchie de vues. La hiérarchie des vues est entièrement déterminée par le contenu de la cellule prototype enregistrée avec ce contrôleur particulier.

Notez également que l'identificateur de réutilisation de la cellule n'a pas été enregistré dans un dispensaire de cellules global. L'identificateur de réutilisation n'est utilisé que dans le contexte d'une seule instance UITableView.


Étant donné ces informations, regardons ce qui s’est passé lors de vos tentatives précédentes.

Dans le contrôleur n ° 1, vous avez ajouté une cellule prototype, défini la classe sur ma sous-classe UITableViewCell, défini l'ID de réutilisation, ajouté les étiquettes et les avez connectées aux prises de la classe. Dans le contrôleur n ° 2, ajouté une cellule prototype vide, définissez-la sur la même classe et réutilisez l'identifiant comme auparavant. Lorsqu'il est exécuté, les étiquettes n'apparaissent jamais lorsque les cellules sont affichées dans le contrôleur n ° 2. Fonctionne bien dans le contrôleur n ° 1.

Ceci est prévu. Bien que les deux cellules aient la même classe, la hiérarchie de vues transmise à la cellule du contrôleur n ° 2 était entièrement dépourvue de sous-vues. Vous avez donc une cellule vide, qui correspond exactement à ce que vous avez mis dans le prototype.

Conçu chaque type de cellule dans un NIB différent et connecté à la classe de cellule appropriée. Dans le storyboard, ajout d'un prototype de cellule vide et définition de sa classe et de son identifiant de réutilisation pour faire référence à ma classe de cellule. Dans les méthodes viewDidLoad des contrôleurs, ces fichiers NIB ont été enregistrés pour l'identifiant de réutilisation. Lorsqu’il est montré, les cellules des deux contrôleurs étaient vides comme le prototype.

Encore une fois, cela est prévu. L'identifiant de réutilisation n'étant pas partagé entre les scènes de scénario et les plumes, le fait que toutes ces cellules distinctes aient le même identifiant de réutilisation n'avait pas de sens. La cellule que vous récupérez de la table a une apparence qui correspond à la cellule prototype de la scène du scénario.

Cette solution était proche, cependant. Comme vous l'avez noté, vous pouvez simplement appeler -[UITableView registerNib:forCellReuseIdentifier:] par programme, en passant le UINib contenant la cellule, et vous obtiendrez cette même cellule. (Ce n'est pas parce que le prototype "annulait" la pointe; vous n'aviez tout simplement pas enregistré la pointe avec la table, donc il regardait toujours la pointe intégrée dans le storyboard.) Malheureusement, cette approche présente un défaut - il n'y a aucun moyen de brancher les légendes du storyboard à une cellule dans une pointe autonome.

Les prototypes conservés dans les deux contrôleurs vident et définissent la classe et réutilisent l'identifiant dans ma classe de cellule. Construit l'interface utilisateur des cellules entièrement en code. Les cellules fonctionnent parfaitement dans tous les contrôleurs.

Naturellement. Espérons que ce n'est pas surprenant.


Donc, c'est pourquoi cela n'a pas fonctionné. Vous pouvez concevoir vos cellules en nibs autonomes et les utiliser dans plusieurs scènes de storyboard. vous ne pouvez tout simplement pas actuellement raccorder les légendes du storyboard à ces cellules. J'espère cependant que vous avez appris quelque chose en lisant ceci.

204
BJ Homer

Malgré l'excellente réponse de BJ Homer, j'ai l'impression d'avoir une solution. En ce qui concerne mes tests, cela fonctionne.

Concept: Créez une classe personnalisée pour la cellule xib. Là, vous pouvez attendre un événement tactile et effectuer la séquence par programmation. Tout ce dont nous avons besoin maintenant est d’une référence au contrôleur exécutant la Segue. Ma solution est de le définir dans tableView:cellForRowAtIndexPath:.

Exemple

J'ai un DetailedTaskCell.xib contenant une cellule de tableau que j'aimerais utiliser dans plusieurs affichages de tableaux:

DetailedTaskCell.xib

Il existe une classe personnalisée TaskGuessTableCell pour cette cellule:

enter image description here

C'est là que se passe la magie.

// TaskGuessTableCell.h
#import <Foundation/Foundation.h>

@interface TaskGuessTableCell : UITableViewCell
@property (nonatomic, weak) UIViewController *controller;
@end

// TashGuessTableCell.m
#import "TaskGuessTableCell.h"

@implementation TaskGuessTableCell

@synthesize controller;

- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
    NSIndexPath *path = [controller.tableView indexPathForCell:self];
    [controller.tableView selectRowAtIndexPath:path animated:NO scrollPosition:UITableViewScrollPositionNone];
    [controller performSegueWithIdentifier:@"FinishedTask" sender:controller];
    [super touchesEnded:touches withEvent:event];
}

@end

J'ai plusieurs séquences mais elles ont toutes le même nom: "FinishedTask". Si vous avez besoin d'être flexible ici, je suggère d'ajouter une autre propriété.

Le ViewController ressemble à ceci:

// LogbookViewController.m
#import "LogbookViewController.h"
#import "TaskGuessTableCell.h"

@implementation LogbookViewController

- (void)viewDidLoad
{
    [super viewDidLoad]

    // register custom nib
    [self.tableView registerNib:[UINib nibWithNibName:@"DetailedTaskCell" bundle:[NSBundle mainBundle]] forCellReuseIdentifier:@"DetailedTaskCell"];
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    TaskGuessTableCell *cell;

    cell = [tableView dequeueReusableCellWithIdentifier:@"DetailedTaskCell"];
    cell.controller = self; // <-- the line that matters
    // if you added the seque property to the cell class, set that one here
    // cell.segue = @"TheSegueYouNeedToTrigger";
    cell.taskTitle.text  = [entry title];
    // set other outlet values etc. ...

    return cell;
}

- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
    if([[segue identifier] isEqualToString:@"FinishedTask"])
    {
        // do what you have to do, as usual
    }

}

@end

Il existe peut-être des moyens plus élégants d’atteindre le même objectif, mais cela fonctionne! :)

58
ericteubert

Je cherchais ceci et j'ai trouvé cette réponse par Richard Venable. Ça marche pour moi.

iOS 5 inclut une nouvelle méthode sur UITableView: registerNib: forCellReuseIdentifier:

Pour l'utiliser, placez une UITableViewCell dans un nib. Ce doit être le seul objet racine de la nib.

Vous pouvez enregistrer le nib après avoir chargé votre tableView, puis lorsque vous appelez dequeueReusableCellWithIdentifier: avec l'identifiant de cellule, il le tirera du nib, comme si vous aviez utilisé une cellule prototype de Storyboard.

16
Odrakir

Swift 3

BJ Homer a donné une excellente explication. Cela m'aide à comprendre le concept. Pour make a custom cell reusable in storyboard, qui peut être utilisé dans n'importe quel TableViewController, nous devons mix the Storyboard and xib approcher. Supposons que nous ayons une cellule nommée CustomCell qui doit être utilisée dans TableViewControllerOne et TableViewControllerTwo. Je le fais par étapes.
1. Fichier> Nouveau> Cliquez sur Fichier> Sélectionner la classe Cocoa Touch> cliquez sur Suivant> Donnez le nom de votre classe (par exemple CustomCell). > sélectionnez Sous-classe comme UITableVieCell> Cochez la case Créer également un fichier XIB et appuyez sur Suivant.
2. Personnalisez la cellule comme vous le souhaitez et définissez l’identifiant dans l’inspecteur d’attributs pour la cellule. Ici, nous allons définir comme suit: CellIdentifier. Cet identifiant sera utilisé dans votre ViewController pour identifier et réutiliser la cellule.
3. Il ne reste plus qu'à register this cell dans notre ViewController viewDidLoad. Pas besoin de méthode d'initialisation.
4. Nous pouvons maintenant utiliser cette cellule personnalisée dans n'importe quelle tableView.

Dans TableViewControllerOne

let reuseIdentifier = "CellIdentifier"

override func viewDidLoad() {
super.viewDidLoad()
tableView.register(UINib(nibName: "CustomCell", bundle: nil), forCellReuseIdentifier: reuseIdentifier)
} 

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCell(withIdentifier:reuseIdentifier, for: indexPath) as! CustomCell
    return cell!
}
10
Kunal Kumar

BJ Homer a donné une excellente explication de ce qui se passe.

D'un point de vue pratique, j'ajouterais que, étant donné que vous ne pouvez pas avoir de cellules en tant que xibs ET que vous vous connectiez, la meilleure solution consiste à conserver la cellule en tant que xib. , et vos segments sont susceptibles d’être différents de vos différents contrôleurs. Vous pouvez définir la séquence directement de votre contrôleur de vue table vers le contrôleur suivant et l'exécuter en code. .

Une autre remarque est que le fait d’avoir votre cellule en tant que fichier xib séparé vous empêche de connecter des actions, etc. directement au contrôleur de vue sous forme de tableau (de toute façon, je n’ai pas trouvé la solution. Vous ne pouvez pas définir le propriétaire du fichier comme ayant un sens. ) Je travaille autour de cela en définissant un protocole auquel le contrôleur de vue de la cellule de la cellule est censé se conformer et en ajoutant le contrôleur en tant que propriété faible, similaire à un délégué, dans cellForRowAtIndexPath.

10
jrturton

J'ai trouvé un moyen de charger la cellule pour le même VC, non testé pour les segues. Cela pourrait être une solution de contournement pour créer la cellule dans une nib distincte

Supposons que vous disposiez d'un VC et de 2 tableaux et que vous souhaitiez concevoir une cellule dans le storyboard et l'utiliser dans les deux tableaux.

(ex: une table et un champ de recherche avec un UISearchController avec une table pour les résultats et vous voulez utiliser la même cellule dans les deux)

Lorsque le contrôleur demande la cellule, procédez comme suit:

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    static NSString * identifier = @"CELL_ID";

    ContactsCell *cell = [self.YOURTABLEVIEW dequeueReusableCellWithIdentifier:identifier];
  // Ignore the "tableView" argument
}

Et ici vous avez votre cellule du storyboard

5
João Nunes