Récupérer la taille des custom cells

Bonjour,


 


je suis en train de coder une collectionView avec des custom cells.


J'ai besoin de connaà®tre la taille de ces custom cells, que j'indique dans la méthode delegate de collection view.


 


Comme je ne veux pas coder en dur la taille de ma custom cell, j'ai procédé comme ceci.



#import "MyCell.h"

static CGSize _size ;

@interface MyCell ()
@property (weak, nonatomic) IBOutlet UIButton *button;

@end


@implementation MyCell

+ (CGSize)size
{
if (_size.height == 0
||
_size.width == 0)
{
[[NSBundle mainBundle] loadNibNamed:NSStringFromClass([self class])
owner:[[self alloc] init]
options:nil] ;
}

return _size ;
}

- (void)awakeFromNib
{
_size = self.frame.size ;
}

@end

Je voulais avoir votre avis.


Est-ce une bonne façon de faire ?


 


Comme ça, je ne verrais qu'une autre solution : charger tous mes nibs au lancement de l'app.


 


En fait, le problème auquel j'ai été confronté, c'est que la collection view appelle la méthode delegate pour connaà®tre la taille de la cell, le xib n'a pas encore été réveillé. Donc, le code suivant plante :



#import "MyCell.h"

static CGSize _size ;

@interface MyCell ()
@property (weak, nonatomic) IBOutlet UIButton *button;

@end


@implementation MyCell

+ (CGSize)size
{
// if (_size.height == 0
// ||
// _size.width == 0)
// {
// [[NSBundle mainBundle] loadNibNamed:NSStringFromClass([self class])
// owner:[[self alloc] init]
// options:nil] ;
// }

return _size ;
}

- (void)awakeFromNib
{
_size = self.frame.size ;
}

@end

D'où ma solution.


Réponses

  • AliGatorAliGator Membre, Modérateur
    octobre 2014 modifié #2
    Alors, à  part l'implémentation du awakeFromNib, c'est presque une bonne façon de faire que de charger le XIB qu'une seule fois et garder la valeur de côté.

    L'inconvénient de ta solution est qu'elle réaffecte size à  chaque fois qu'une cellule est instanciée, que ce soit par ton loadNibNamed:owner:options: ou par la CollectionView.

    L'autre inconvénient est que charger un XIB juste pour récupérer la taille, c'est un peu lourd. En plus tu instancies une cell avec alloc+init pour en faire le file's Owner... d'une autre cell que tu vas instancier avec le XIB, donc 2 cells (une par alloc/init, l'autre par XIB) juste pour ça !

    Mais en même temps, le fait de charger la taille par le XIB, ça te garantis que tu as la bonne taille, celle issue du XIB, et pas une taille mise dans une constante que tu oublierais de changer dans ton code si un jour tu changes ton XIB... donc c'est quand même utile...


    Moi ce que je fais dans ces cas-là , c'est que je prend le meilleur des 2 mondes : chargement du XIB en DEBUG pour extraire la taille du XIB, et utilisation d'une constante en RELEASE.
    static CGFloat const kCellSize = (CGSize){ 320, 200 }; // Par exemple

    + (CGSize)cellSize
    {
    #if DEBUG
    // En debug, on vérifie que notre constante est bien consistante avec la taille de la cellule dans le XIB
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
    NSArray* rootViews = [[NSBundle mainBundle] loadNibNamed:NSStringFromClass([self class])
    owner:nil
    options:nil];
    CGSize sizeFromXib = [rootViews firstObject].frame.size;
    // Si la constante ne colle pas avec la taille du XIB, on lève une assertion pour penser à  mettre à  jour la constante
    NSAssert(sizeFromXib == kCellSize, @Your cell has changed its size in the XIB but the constant was not upda });
    ted in the code");
    #endif
    // Dans tous les cas, DEBUG ou RELEASE, on retourne la constante déclarée dans le code.
    return kCellSize;
    }
    Après, c'est une façon de faire comme une autre, mais c'est une pratique que j'aime bien et que j'utilise souvent.
  • T'es sûr de cette ligne ?



    UIView* rootView = [[NSBundle mainBundle] loadNibNamed:NSStringFromClass([self class])
    owner:nil
    options:nil];

    ça paraà®t être en contradiction avec 



    //
    // UINibLoading.h
    // UIKit
    //
    // Copyright (c) 2005-2014 Apple Inc. All rights reserved.
    //

    #import <Foundation/Foundation.h>
    #import <UIKit/UIKitDefines.h>

    UIKIT_EXTERN NSString * const UINibExternalObjects NS_AVAILABLE_IOS(3_0);

    @interface NSBundle(UINibLoadingAdditions)
    - (NSArray *)loadNibNamed:(NSString *)name owner:(id)owner options:(NSDictionary *)options;
    @end

    @interface NSObject(UINibLoadingAdditions)
    - (void)awakeFromNib;
    - (void)prepareForInterfaceBuilder NS_AVAILABLE_IOS(8_0);
    @end

    UIKIT_EXTERN NSString * const UINibProxiedObjectsKey NS_DEPRECATED_IOS(2_0, 3_0);

  • Merci de ta réponse !


     


    Voici une version améliorée !



    #import "MyCell.h"
    static NSValue * _size ;

    @interface MyCell ()
    @property (weak, nonatomic) IBOutlet UIButton *button;

    @end


    @implementation MyCell


    - (void)setUpWithColor:(UIColor *)color
    {
    self.button.backgroundColor = color;
    }


    + (CGSize)size
    {
    if (!_size)
    {
    NSArray * objects = [[NSBundle mainBundle] loadNibNamed:NSStringFromClass([self class])
    owner:nil
    options:nil] ;
    MyCell * cell = objects[0] ;

    _size = [NSValue valueWithCGSize:cell.frame.size] ;
    }

    return [_size CGSizeValue] ;
    }

    - (void)awakeFromNib
    {
    [super awakeFromNib] ;
    }

    @end
  • AliGatorAliGator Membre, Modérateur

    T'es sûr de cette ligne ?


    UIView* rootView = [[NSBundle mainBundle] loadNibNamed:NSStringFromClass([self class])
    owner:nil
    options:nil];

    En effet petite boulette en tapant le code à  la volée dans le forum. J'ai corrigé mon message d'origine.
  • AliGatorAliGator Membre, Modérateur

    Voici une version améliorée !


    #import "MyCell.h"
    static NSValue * _size ;

    @interface MyCell ()
    @property (weak, nonatomic) IBOutlet UIButton *button;

    @end


    @implementation MyCell


    - (void)setUpWithColor:(UIColor *)color
    {
    self.button.backgroundColor = color;
    }


    + (CGSize)size
    {
    if (!_size)
    {
    NSArray * objects = [[NSBundle mainBundle] loadNibNamed:NSStringFromClass([self class])
    owner:nil
    options:nil] ;
    MyCell * cell = objects[0] ;

    _size = [NSValue valueWithCGSize:cell.frame.size] ;
    }

    return [_size CGSizeValue] ;
    }

    - (void)awakeFromNib
    {
    [super awakeFromNib] ;
    }

    @end

    Heu pour moi c'est une version dégradée de la mienne que tu as là  ^^

    - Utilisation de NSValue au lieu d'utiliser directement CGSize : mais pourquoi donc ?!
    - Surcharge de awakeFromNib pour rien
    - Non thread-safe (pas de dispatch_once, utilisation d'un objet en variable globale " ze truc à  éviter " et non d'une constante et d'un type non-objet
    - Pas de valeur const pour optimiser la version release
  • Pourquoi il fait éviter les objets en variable globale ? J'ai passé à  NSValue pour pouvoir tester nil.


    Je voulais faire un test sur les attributs de classe, à  base de

    + (void)setColor:(UIColor *)Color

    qui interagirait avec un objet en variable globale.


    J'ai l'impression que ça bugue....


    Je testerai ça demain.


    Merci de vos retours :)
  • AliGatorAliGator Membre, Modérateur
    Heu tu veux dire que ça ne te choque pas d'avoir une variable globale ? ???

    Oulà  faut réviser d'urgence tes cours, en particulier les risques avec le multithreading et les accès concurrents aux variables globales (risques de crash assurés, et journées de debug impossibles en perspectives jusqu'à  te rendre compte que des bugs aléatoires viennent de là  !). Avec les variables globales, dont l'accès concurrent n'est pas protégées, tu peux préparer tes bons de réduction pour les tubes d'aspirine.

    On peut déclarer une constante en globale (car elle ne sera que lue), mais une variable globale (qui peut donc être écrite et lue de manière potentiellement concurrente, et générer des race conditions), c'est le mal absolu, tu risques d'avoir des bugs bien prise de tête et aléatoires !
  • Mais pourquoi boudiou y'a pas de @property de classe en objective-c !!!
  • AliGatorAliGator Membre, Modérateur
    Pour les mêmes raisons qu'on évite autant que faire se peut d'avoir des variables de classe en Ruby ? (Problématiques levées dans les cas d'héritage, impacts sur la mutabilité d'une variable changeable par une instance alors qu'elle est utilisée par une autre, etc...) ?


    En même temps je ne vois de toute façons pas le rapport avec ta problématique, en quoi cela t'ai t'aiderait dans ton cas ? En quoi l'utilisation d'une static affectée une seule fois de façon threadsafe avec un dispatch_once ne solutionne pas ton problème ? Ou même mieux l'utilisation d'une constante comme dans mon code ?
  • Du coup j'ai fait comme ça :



    //
    //
    /**************************************/
    #pragma mark - Constants
    /**************************************/


    static CGSize _sizeContentView ;








    @implementation MyCBDGenericModalVC






    //
    //
    /**************************************/
    #pragma mark - Size
    /**************************************/
    + (CGSize)sizeContentView
    {

    /*
    The following code will be executed only once
    */
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
    NSArray* rootViews = [[NSBundle mainBundle] loadNibNamed:NSStringFromClass([self class])
    owner:nil
    options:nil];
    UIView * mainView = (UIView *)[rootViews firstObject] ;
    UIView * contentView ;

    for (UIView * subview in [mainView subviews])
    {
    if (subview.tag == 42)
    {
    contentView = subview ;
    break ;
    }
    }

    _sizeContentView = contentView.frame.size ;
    }) ;


    return _sizeContentView ;
    }

    @end
  • Dans mon cas, j'ai dû remplace 



    NSStringFromClass([self class]) // ou plutot NSStringFromClass(self) vu que c'est une méthode de classe

    par le nom en dur, car la méthode est appelée depuis une classe fille et c'est le nom de la classe fille qui est retourné... et qu'il n'y a pas de xib avec ce nom !!


  • En fait, c'est toute la ligne



    NSArray* rootViews = [[NSBundle mainBundle] loadNibNamed:NSStringFromClass([self class])
    owner:nil
    options:nil];

    qui faisait râler xcode, à  cause de IBOutlets je pense. Si le owner est nil, il n'arrive pas à  connecter les outlets, je pense.


    Du coup, j'ai remis le crado [[self alloc] init]


  • AliGatorAliGator Membre, Modérateur
    Non j'ai plein de fois passé nil comme owner à  cette methode alors que le XIB avait des Outlets et jamais aucun problème (au contraire c'est prevu pour). L'erreur venait d'ailleurs dans ton code. Et le hack crado qui marche "sans qu'on sache trop pourquoi" (mais qui a des effets de bords) n'est pas une solution ;-)
  • Apparemment, quand les outlets sont connectés au file's owner, ça bugue. C'est le cas des view controllers.


     


    ça marche si les outlets sont connectés à  un objet. C'est les cas des custom cells.


     


    cf. http://stackoverflow.com/a/5479906/1670830 et en particulier le commentaire liké 7 fois


  • AliGatorAliGator Membre, Modérateur
    Je ne comprends pas, de toute façon si tu fais un XIB dans le but de l'extraire via "-[NSBundle loadNibNamed:owner:options:]" (et de récupérer les rootViews de ce XIB ensuite par code) " alors pourquoi mettre un File's Owner ?
  • colas_colas_ Membre
    octobre 2014 modifié #17

    En fait, j'ai un xib qui est lié à  un VC.


     


    Mais, j'ai besoin, connaà®tre de "l'extraire à  vide" pour gérer une méthode +(CGSize)size (cf. début du post --> en fait, j'ai voulu adapté ces méthodes à  un xib qui n'est pas une custom cell).


Connectez-vous ou Inscrivez-vous pour répondre.