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

216

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

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

Comment créer une cellule de vue de table personnalisée dans le storyboard qui peut ê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, ajouté une cellule prototype, définissez la classe sur ma UITableViewCellsous - classe, 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 précédemment. Lorsqu'il s'exécute, les étiquettes n'apparaissent jamais lorsque les cellules sont affichées dans le contrôleur n ° 2. Fonctionne bien dans le contrôleur # 1.

  • Conçu chaque type de cellule dans un NIB différent et câblé à la classe de cellule appropriée. Dans le storyboard, ajouté une cellule prototype vide et défini sa classe et son identifiant de réutilisation pour faire référence à ma classe de cellule. Dans les viewDidLoadméthodes des contrôleurs , enregistrez ces fichiers NIB pour l'ID de réutilisation. Lorsque montré, les cellules des deux contrôleurs étaient vides comme le prototype.

  • Les prototypes conservés dans les deux contrôleurs sont vides et définissent la classe et réutilisent l'ID 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 deuxième cas, je soupçonne que le prototype remplace toujours la NIB et si j'ai tué les cellules du prototype, l'enregistrement de ma NIB pour l'ID de réutilisation fonctionnerait. Mais alors je ne serais pas en mesure de configurer des séquences des cellules vers d'autres images, ce qui est vraiment l'intérêt d'utiliser des storyboards.

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

Cliff W
la source

Réponses:

205

Si je comprends bien, vous voulez:

  1. Concevez une cellule dans IB qui peut être utilisée dans plusieurs scènes de storyboard.
  2. Configurez des séquences de storyboard uniques à partir de cette cellule, selon la scène dans laquelle se trouve la cellule.

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 tableau prototype. (Si vous ne vous souciez pas de la raison pour laquelle ces autres tentatives n'ont pas fonctionné, n'hésitez pas à partir maintenant. Je n'ai aucune solution de contournement magique pour vous, à part suggérer que vous signaliez un bogue.)

Un storyboard n'est, par essence, pas beaucoup plus qu'une collection de fichiers .xib. Lorsque vous chargez un contrôleur de vue de table contenant des cellules prototypes dans 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 et appels de la cellule prototype -[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é, il vérifie si elle a une plume enregistrée. Si c'est le cas, il instancie une instance de cette cellule. Il se compose des étapes suivantes:

    1. Regardez la classe de la cellule, telle que définie dans la pointe de la cellule. Appelle [[CellClass alloc] initWithCoder:].
    2. La -initWithCoder:méthode passe par et ajoute des sous-vues et définit les propriétés qui ont été définies dans la nib. ( IBOutletJe suis probablement aussi connecté ici, même si je n'ai pas testé cela; cela peut arriver en -awakeFromNib)
  • Vous configurez votre cellule comme vous le souhaitez.

La chose importante à noter ici est qu'il y a une distinction entre la classe de la cellule et l' apparence visuelle de la cellule. Vous pouvez créer deux cellules prototypes distinctes de la même classe, mais avec leurs sous-vues disposées de manière complètement différente. En fait, si vous utilisez les UITableViewCellstyles par défaut , c'est exactement ce qui se passe. Le style "Default" et le style "Subtitle", par exemple, sont tous deux représentés par la même UITableViewCellclasse.

Ceci est important : la classe de la cellule n'a pas de corrélation un à un avec une hiérarchie de vues particulière . La hiérarchie des vues est entièrement déterminée par ce qui se trouve dans la cellule prototype qui a été enregistrée avec ce contrôleur particulier.

Notez également que l'identifiant de réutilisation de la cellule n'a pas été enregistré dans certains dispensaires cellulaires mondiaux. L'identifiant de réutilisation n'est utilisé que dans le contexte d'une seule UITableViewinstance.


Compte tenu de ces informations, regardons ce qui s'est passé lors de vos tentatives ci-dessus.

Dans le contrôleur n ° 1, ajouté une cellule prototype, 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 précédemment. Lorsqu'il s'exécute, les étiquettes n'apparaissent jamais lorsque les cellules sont affichées dans le contrôleur n ° 2. Fonctionne bien dans le contrôleur # 1.

C'est attendu. 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, ce qui est exactement ce que vous avez mis dans le prototype.

Conçu chaque type de cellule dans un NIB différent et câblé à la classe de cellule appropriée. Dans le storyboard, ajouté une cellule prototype vide et défini sa classe et son identifiant de réutilisation pour faire référence à ma classe de cellule. Dans les méthodes viewDidLoad des contrôleurs, enregistré ces fichiers NIB pour l'ID de réutilisation. Lorsque 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'est pas partagé entre les scènes du storyboard ou les nibs, donc le fait que toutes ces cellules distinctes avaient le même identifiant de réutilisation n'a pas de sens. La cellule que vous récupérez de la table aura une apparence qui correspond à la cellule prototype dans cette scène du storyboard.

Cette solution était pourtant proche. Comme vous l'avez noté, vous pouvez simplement appeler par programme -[UITableView registerNib:forCellReuseIdentifier:], en passant UINibla cellule contenant la cellule, et vous récupérez cette même cellule. (Ce n'est pas parce que le prototype "remplaçait" la plume; vous n'aviez tout simplement pas enregistré la plume avec la table, donc il regardait toujours la plume intégrée dans le storyboard.) Malheureusement, il y a un défaut avec cette approche - il n'y a aucun moyen de connecter les séquences de storyboard à une cellule dans une plume autonome.

Les prototypes conservés dans les deux contrôleurs sont vides et définissent la classe et réutilisent l'ID 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 ne soit pas surprenant.


Voilà pourquoi cela n'a pas fonctionné. Vous pouvez concevoir vos cellules dans des plumes autonomes et les utiliser dans plusieurs scènes de storyboard; vous ne pouvez pas actuellement connecter de séquences de storyboard à ces cellules. Espérons que vous ayez appris quelque chose en lisant ceci.

BJ Homer
la source
Ah, je comprends. Vous avez cloué mon malentendu - la hiérarchie des vues est complètement indépendante de ma classe. Rétrospectivement évident! Merci pour la bonne réponse.
Cliff W
Il n'est plus impossible, semble-t-il: stackoverflow.com/questions/8574188/…
Rich Apodaca
7
@RichApodaca J'ai mentionné cette solution dans ma réponse. Mais ce n'est pas dans le storyboard; c'est dans une plume séparée. Vous ne pouviez donc pas câbler de séquences ou faire d'autres choses de storyboard. Par conséquent, il ne répond pas totalement à la question initiale.
BJ Homer
À partir de XCode8, la solution de contournement suivante semble fonctionner si vous voulez une solution de storyboard uniquement. Étape 1) Créez votre cellule prototype dans une vue de table dans ViewController # 1 et associez-la à la classe UITableViewCell personnalisée Étape 2) Copiez / collez cette cellule dans la vue de table de ViewController # 2. Au fil du temps, vous devrez vous rappeler de propager manuellement les mises à jour des copies de la cellule en supprimant les copies que vous avez faites dans le storyboard et en les collant dans le prototype mis à jour.
jengelsma
Excellente réponse, j'ai cependant une question de suivi:> "Un storyboard n'est, en substance, pas beaucoup plus qu'une collection de fichiers .xib" si tel est le cas, pourquoi est-il si difficile d'intégrer un xib dans un storyboard?
willcwf
58

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éer une classe personnalisée pour la cellule xib. Là, vous pouvez attendre un événement tactile et effectuer la séquence par programme. Maintenant, tout ce dont nous avons besoin est une référence au contrôleur effectuant la séquence. Ma solution est de l'installer tableView:cellForRowAtIndexPath:.

Exemple

J'ai une DetailedTaskCell.xibcellule contenant une table que j'aimerais utiliser dans plusieurs vues de table:

DetailTaskCell.xib

Il existe une classe personnalisée TaskGuessTableCellpour cette cellule:

entrez la description de l'image ici

C'est là que la magie opère.

// 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 Segues mais ils ont tous le même nom: "FinishedTask". Si vous devez être flexible ici, je vous 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 pourrait y avoir des façons plus élégantes de faire la même chose, mais - cela fonctionne! :)

ericteubert
la source
1
Merci, j'implémente cette approche dans mon projet. Vous pouvez remplacer cette méthode à la place afin de ne pas avoir à obtenir l'indexPath et à sélectionner la ligne vous-même: - (void) setSelected: (BOOL) selected animated: (BOOL) animated {[super setSelected: selected animated: animated]; if (sélectionné) [self.controller performSegueWithIdentifier: self.segue sender: self]; } Je pensais que super sélectionnerait la cellule lors de l'appel de [super touchesEnded: touches withEvent: event] ;. Savez-vous quand il est sélectionné s'il n'est pas là?
thejaz
9
Notez qu'avec cette solution, vous déclenchez la séquence à chaque fois qu'un contact se termine à l'intérieur d'une cellule. Cela inclut si vous faites simplement défiler la cellule, sans essayer de la sélectionner. Vous pourriez avoir plus de chance de passer outre -setSelected:la cellule et de déclencher la séquence uniquement lors de la transition de NOà YES.
BJ Homer
J'ai plus de chance avec setSelected:BJ. Merci. En effet, c'est une solution inélégante (cela semble mal), mais en même temps, cela fonctionne, donc je l'utilise jusqu'à ce que cela soit corrigé (ou quelque chose change dans la cour d'Apple).
Ben Kreeger
16

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

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

Pour l'utiliser, mettez un UITableViewCell dans une pointe. Ce doit être le seul objet racine dans la plume.

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

Odrakir
la source
10

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 connecter des séquences, le meilleur choix est d'avoir la cellule en tant que xib - les transitions sont beaucoup plus faciles à maintenir que les dispositions et les propriétés des cellules à plusieurs endroits , et vos séquences seront probablement différentes de vos différents contrôleurs de toute façon. Vous pouvez définir la séquence directement depuis votre contrôleur de vue de table vers le contrôleur suivant et l'exécuter en code. .

Une autre note est que le fait d'avoir votre cellule en tant que fichier xib séparé vous empêche de pouvoir connecter des actions, etc. directement au contrôleur de vue de table (je n'ai pas travaillé, de toute façon - vous ne pouvez pas définir le propriétaire du fichier comme quelque chose de significatif ). Je travaille autour de cela en définissant un protocole auquel le contrôleur de vue de table 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.

jrturton
la source
10

Swift 3

BJ Homer a donné une excellente explication, cela m'aide à comprendre le concept. To make a custom cell reusable in storyboard, qui peut être utilisé dans n'importe quel TableViewController que nous devons mix the Storyboard and xibapprocher. Supposons que nous ayons une cellule nommée as CustomCellqui doit être utilisée dans le TableViewControllerOneet 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 également créer 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 CellIdentifier. Cet identifiant sera utilisé dans votre ViewController pour identifier et réutiliser la cellule.
3. Il ne nous reste plus qu'àregister this cell dans notre ViewControllerviewDidLoad. 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!
}
Kunal Kumar
la source
5

J'ai trouvé un moyen de charger la cellule pour le même VC, non testé pour les séquences. Cela pourrait être une solution de contournement pour créer la cellule dans une pointe séparée

Disons que vous avez un VC et 2 tableaux et que vous souhaitez 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 souhaitez 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

João Nunes
la source
J'ai essayé cela et cela semble fonctionner TOUTEFOIS, les cellules ne sont jamais réutilisées. Le système crée et libère toujours de nouvelles cellules à chaque fois.
GilroyKilroy
Il s'agit de la même recommandation que celle trouvée dans Ajouter une barre de recherche à une vue de tableau avec des storyboards . Si vous êtes intéressé, vous trouverez une explication plus approfondie de cette solution (recherchez tableView:cellForRowAtIndexPath:).
Senseful
mais c'est moins de texte et répond à la question
João Nunes