298 votes

Comment charger des UITableViewCells personnalisés à partir de fichiers Xib ?

La question est simple : Comment chargez-vous les UITableViewCell à partir de fichiers Xib ? En procédant ainsi, vous pouvez utiliser Interface Builder pour concevoir vos cellules. La réponse n'est apparemment pas simple en raison de problèmes de gestion de la mémoire. Ce fil mentionne le problème et suggère une solution, mais il s'agit d'une version pré-NDA et il n'y a pas de code. Voici une long fil qui discute de la question sans apporter de réponse définitive.

Voici quelques codes que j'ai utilisés :

static NSString *CellIdentifier = @"MyCellIdentifier";

MyCell *cell = (MyCell *)[tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
    NSArray *nib = [[NSBundle mainBundle] loadNibNamed:CellIdentifier owner:self options:nil];
    cell = (MyCell *)[nib objectAtIndex:0];
}

Pour utiliser ce code, créez MyCell.m/.h, une nouvelle sous-classe du fichier UITableViewCell et ajouter IBOutlets pour les composants que vous souhaitez. Créez ensuite un nouveau fichier "Empty XIB". Ouvrez le fichier Xib dans IB, ajoutez un fichier UITableViewCell définissez son identifiant à "MyCellIdentifier" et sa classe à MyCell et ajoutez vos composants. Enfin, connectez l'objet IBOutlets aux composants. Notez que nous n'avons pas défini le propriétaire du fichier dans IB.

D'autres méthodes préconisent la définition du propriétaire du fichier et préviennent des fuites de mémoire si le Xib n'est pas chargé via une classe d'usine supplémentaire. J'ai testé les méthodes ci-dessus sous Instruments/Leaks et je n'ai constaté aucune fuite de mémoire.

Alors, quelle est la façon canonique de charger des cellules à partir de Xibs ? Doit-on définir le propriétaire du fichier ? Avons-nous besoin d'une usine ? Si oui, à quoi ressemble le code de la fabrique ? S'il existe plusieurs solutions, précisons les avantages et les inconvénients de chacune d'entre elles...

2 votes

Quelqu'un peut-il modifier le sujet pour poser réellement la question, c'est-à-dire : "Comment charger des UITableViewCells personnalisées à partir de fichiers Xib ?" (Ignorez si cela n'est tout simplement pas possible sur stackoverflow).

1 votes

Pour iOS 5 et au-delà, c'est la solution : stackoverflow.com/questions/15591364/ ce qui est la même chose que la solution de Giuseppe.

0 votes

Note rapide, réponse plus simple (milieu 2013) ici stackoverflow.com/questions/15378788/ jamihash

306voto

giuseppe Points 1207

La bonne solution est la suivante :

- (void)viewDidLoad
{
    [super viewDidLoad];
    UINib *nib = [UINib nibWithNibName:@"ItemCell" bundle:nil];
    [[self tableView] registerNib:nib forCellReuseIdentifier:@"ItemCell"];
}

-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    // Create an instance of ItemCell
    PointsItemCell *cell = [tableView dequeueReusableCellWithIdentifier:@"ItemCell"];

    return cell;
}

0 votes

Est-ce que cela va casser les applications iOS5 ? Je n'ai sincèrement jamais vu UINib

0 votes

@AdamWaite L'enregistrement des fichiers NIB fonctionne pour iOS 5 et les versions ultérieures, ce qui ne casse pas les applications iOS 5. Et UINib existe même depuis iOS 4.

0 votes

Pour un bon exemple, consultez le dépôt git référencé dans la première réponse ici : stackoverflow.com/questions/18746929/

291voto

bentford Points 9981

Voici deux méthodes que l L'auteur original déclare qu'il a été recommandé par un ingénieur de l'IB .

Voir l'article en question pour plus de détails. Je préfère la méthode n°2 car elle me semble plus simple.

Méthode n° 1 :

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"BDCustomCell"];
    if (cell == nil) {
        // Create a temporary UIViewController to instantiate the custom cell.
        UIViewController *temporaryController = [[UIViewController alloc] initWithNibName:@"BDCustomCell" bundle:nil];
        // Grab a pointer to the custom cell.
        cell = (BDCustomCell *)temporaryController.view;
        [[cell retain] autorelease];
        // Release the temporary UIViewController.
        [temporaryController release];
    }

    return cell;
}

Méthode n°2 :

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"BDCustomCell"];
    if (cell == nil) {
        // Load the top-level objects from the custom cell XIB.
        NSArray *topLevelObjects = [[NSBundle mainBundle] loadNibNamed:@"BDCustomCell" owner:self options:nil];
        // Grab a pointer to the first object (presumably the custom cell, as that's all the XIB should contain).
        cell = [topLevelObjects objectAtIndex:0];
    }

    return cell;
}

Mise à jour (2014) : La méthode n° 2 est toujours valable, mais il n'existe plus de documentation à son sujet. Elle se trouvait autrefois dans le documents officiels mais il est maintenant supprimé au profit des storyboards.

J'ai posté un exemple fonctionnel sur Github :
https://github.com/bentford/NibTableCellExample

modifier pour Swift 4.2

override func viewDidLoad() {
    super.viewDidLoad()

    // Do any additional setup after loading the view.
    self.tblContacts.register(UINib(nibName: CellNames.ContactsCell, bundle: nil), forCellReuseIdentifier: MyIdentifier)
}

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {

    let cell = tableView.dequeueReusableCell(withIdentifier: MyIdentifier, for: indexPath) as! ContactsCell

    return cell
}

1 votes

Pour la méthode 1, ne devriez-vous pas faire quelque chose comme "cell = (BDCustomCell *)[[temporaryController.view retain] autorelease] ;" afin que la cellule ne soit pas libérée lorsque le contrôleur temporaire est libéré ?

0 votes

Hm. La documentation qui parle de #2 vous dit toujours de définir le propriétaire de la cellule dans le fichier XIB, à une classe de contrôleur connue. Peut-être que le fait de définir le propriétaire pendant le chargement n'a pas d'importance.

0 votes

@OscarGoldman Le propriétaire de la cellule dans le fichier XIB est une classe (c'est-à-dire le type de propriétaire.) Le propriétaire de la cellule dans loadNibNamed:owner:options : est un objet du type spécifié dans le XIB.

33voto

vilcsak Points 361

J'ai pris la réponse de Shawn Craver et l'ai nettoyée un peu.

BBCell.h :

#import <UIKit/UIKit.h>

@interface BBCell : UITableViewCell {
}

+ (BBCell *)cellFromNibNamed:(NSString *)nibName;

@end

BBCell.m :

#import "BBCell.h"

@implementation BBCell

+ (BBCell *)cellFromNibNamed:(NSString *)nibName {
    NSArray *nibContents = [[NSBundle mainBundle] loadNibNamed:nibName owner:self options:NULL];
    NSEnumerator *nibEnumerator = [nibContents objectEnumerator];
    BBCell *customCell = nil;
    NSObject* nibItem = nil;
    while ((nibItem = [nibEnumerator nextObject]) != nil) {
        if ([nibItem isKindOfClass:[BBCell class]]) {
            customCell = (BBCell *)nibItem;
            break; // we have a winner
        }
    }
    return customCell;
}

@end

Je fais en sorte que toutes mes UITableViewCell soient des sous-classes de BBCell, puis je remplace la fonction standard

cell = [[[BBDetailCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"BBDetailCell"] autorelease];

avec :

cell = (BBDetailCell *)[BBDetailCell cellFromNibNamed:@"BBDetailCell"];

16voto

funroll Points 4014

J'ai utilisé les produits bentford Méthode n° 2 :

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"BDCustomCell"];
    if (cell == nil) {
        // Load the top-level objects from the custom cell XIB.
        NSArray *topLevelObjects = [[NSBundle mainBundle] loadNibNamed:@"BDCustomCell" owner:self options:nil];
        // Grab a pointer to the first object (presumably the custom cell, as that's all the XIB should contain).
        cell = [topLevelObjects objectAtIndex:0];
    }

    return cell;
}

Cela fonctionne, mais attention aux connexions avec Propriétaire du fichier dans votre fichier .xib personnalisé UITableViewCell.

En passant owner:self dans votre loadNibNamed vous définissez le paramètre UITableViewController en tant que propriétaire du dossier de votre UITableViewCell .

Si vous effectuez un glisser-déposer vers le fichier d'en-tête dans IB pour configurer les actions et les sorties, il les configurera par défaut en tant que propriétaire du fichier.

Sur loadNibNamed:owner:options le code d'Apple essaiera de définir les propriétés de votre carte de crédit. UITableViewController puisque c'est le propriétaire. Mais vous n'avez pas ces propriétés définies à cet endroit, et vous obtenez donc une erreur sur le fait qu'il s'agit d'une propriété de l'utilisateur. conforme à la codification des valeurs clés :

*** Terminating app due to uncaught exception 'NSUnknownKeyException', reason:     '[<MyUITableViewController 0x6a383b0> setValue:forUndefinedKey:]: this class is not key value coding-compliant for the key myLabel.'

Si un événement est déclenché à la place, vous obtiendrez une NSInvalidArgumentException :

-[MyUITableViewController switchValueDidChange:]: unrecognized selector sent to instance 0x8e9acd0
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[MyUITableViewController switchValueDidChange:]: unrecognized selector sent to instance 0x8e9acd0'
*** First throw call stack:
(0x1903052 0x15eed0a 0x1904ced 0x1869f00 0x1869ce2 0x1904ec9 0x5885c2 0x58855a 0x62db76 0x62e03f 0x77fa6c 0x24e86d 0x18d7966 0x18d7407 0x183a7c0 0x1839db4 0x1839ccb 0x1f8b879 0x1f8b93e 0x585a9b 0xb904d 0x2c75)
terminate called throwing an exceptionCurrent language:  auto; currently objective-c

Une solution de contournement facile consiste à faire pointer les connexions d'Interface Builder vers l'interface de l'utilisateur. UITableViewCell au lieu du Propriétaire du fichier :

  1. Cliquez avec le bouton droit de la souris sur le propriétaire du fichier pour faire apparaître la liste des connexions.
  2. Faites une capture d'écran avec Command-Shift-4 (faites glisser pour sélectionner la zone à capturer).
  3. x les connexions du propriétaire du fichier
  4. Cliquez avec le bouton droit de la souris sur la cellule UITableCell dans la hiérarchie des objets et ajoutez à nouveau les connexions.

0 votes

J'ai eu le problème que vous avez mentionné, mais comment faire pour que les connexions soient dirigées vers le UITableViewCell au lieu du propriétaire du fichier ? Je ne comprends pas vos étapes, par exemple pourquoi il est nécessaire de faire une capture d'écran ? et lorsque j'ai cliqué sur le bouton d'ajout à côté de la prise, rien ne se passe

0 votes

@xuhuanze J'ai suggéré de faire une capture d'écran afin d'avoir une trace des éléments auxquels le propriétaire du fichier était déjà connecté. Vous pouvez ensuite recréer ces mêmes connexions. Vous devez faire un glisser-déposer pour ajouter les connexions, et non pas un simple clic.

0 votes

Merci beaucoup, j'avais le problème "cette classe n'est pas conforme au codage de la valeur de la clé" et je l'ai résolu grâce à votre aide. Je veux dire aux autres, que vous devriez également changer une classe de votre UITableViewCell à votre classe, que vous utilisez comme une classe de cellule personnalisée.

14voto

webstersx Points 241

J'ai décidé de poster ce message car je n'aime aucune de ces réponses - les choses peuvent toujours être plus simples et c'est de loin la manière la plus concise que j'ai trouvée.

1. Créez votre Xib dans Interface Builder comme vous le souhaitez.

  • Définir le propriétaire du fichier à la classe NSObject
  • Ajoutez un UITableViewCell et définissez sa classe à MyTableViewCellSubclass -- si votre IB se plante (ce qui arrive dans Xcode > 4 au moment où j'écris ces lignes), utilisez simplement un UIView de l'interface dans Xcode 4 si vous l'avez encore sous la main
  • Disposez vos subviews à l'intérieur de cette cellule et attachez vos connexions IBOutlet à votre @interface dans le fichier .h ou .m (le .m est ma préférence).

2. Dans votre sous-classe UIViewController ou UITableViewController

@implementation ViewController

static NSString *cellIdentifier = @"MyCellIdentier";

- (void) viewDidLoad {

    ...
    [self.tableView registerNib:[UINib nibWithNibName:@"MyTableViewCellSubclass" bundle:nil] forCellReuseIdentifier:cellIdentifier];
}

- (UITableViewCell*) tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    MyTableViewCellSubclass *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier];

    ...

    return cell;
}

3. Dans votre sous-classe MyTableViewCellSubclass

- (id) initWithCoder:(NSCoder *)aDecoder {
    if (self = [super initWithCoder:aDecoder]) {
        ...
    }

    return self;
}

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X