[Résolu] une custom cell avec iboutlets et drawRect ?

LouLou Membre
novembre 2014 modifié dans API UIKit #1

Salut,


 


j'aimerais mettre un fond dans une custom cell, et utiliser les iboutlets pour changer le texte etc. Mais:


 


EDIT: je voudrais utiliser CoreGraphics pour customizer ma cell, or les méthodes draw... n'affichent pas le code. En suivant les conseils, j'essaie de faire une sous-classe de layer, et de l'ajouter dans ma custom cell, avec une méthode drawInContext... mais le code ne s'affiche pas. La cell est blanche, le texte est bien updaté, mais rien niveau CoreGraphics... 


 


il vaut mieux passer directement à  ce post plus bas, avec le code : lien


 


_______________


 


Code:



@synthesize ibLabel;

-(void) addBackground{
UIView *view = [[UIView alloc] initWithFrame:self.frame];
CAGradientLayer *gradient = [CAGradientLayer layer];
gradient.frame = view.bounds;
gradient.colors = [NSArray arrayWithObjects:(id)[[UIColor whiteColor] CGColor], (id)[[UIColor blackColor] CGColor], nil];
[view.layer insertSublayer:gradient atIndex:0];

[self addSubview:view];
//[self.layer addSublayer:view.layer];//pariel que addSubview
}

- (void)awakeFromNib {
//self.backgroundColor = [UIColor clearColor]; //ne marche pas
//[self addBackground];
self.ibLabel.text = @hello;//ne s'affiche pas quand je met addBackgorund
}

- je peux utiliser drawRect dans une custom class si cette custom class est initialisée comme la "backgroundview" de la cell, la cell entière est une UITableViewCell et sa backgroundView est ma custom class, mais dans ce cas, je n'ai plus accès aux iboutlets:


 


Code :



- (UITableViewCell *)tableView:(UITableView*)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@Cell forIndexPath:indexPath];

//mais dans ce cas, la custom cell change uniquement le fond de ma cell, je n'ai pas accès aux IBOutlets
if (![cell.backgroundView isKindOfClass:[MyCell class]]) {
cell.backgroundView = [[MyCell alloc] init];
}

return cell;
}

> faut-il que je crée une class pour le .xib avec les labels et iboutlets, et une autre class avec drawRect uniquement pour le background? C'est pas un peu bizarre d'avoir 2 classes pour une custom cell? 


 


Merci ;-)


Réponses

  • Joanna CarterJoanna Carter Membre, Modérateur
    Tu ne peux rien faire avec le UITableViewCell soi-même. Tous ce qu'il faut faire doit être fait avec le contentView, dont tu peux ajouter les couches ou tu peux insérer une sous-vue à  l'index 0
  • CéroceCéroce Membre, Modérateur
    Franchement, je ne travaille jamais ainsi. Si je sous-classe UITableViewCell, c'est pour pouvoir la définir entièrement dans un xib. De fait, si j'ai besoin de personnaliser le fond, j'utilise une UIImageView.

    Pour la deuxième question, il faut utiliser -[UITableView registerNib/Class:forReuseIdentifier:]. Dans le xib définissant la cellule, change la classe de la cellule en ta classe, puis tire les outlets.
  • LouLou Membre
    novembre 2014 modifié #4

    D'accord, merci, alors j'utilise les layers plutôt que drawRect. Mais :


    EDIT: problème de taille réglé.


     


  • Joanna CarterJoanna Carter Membre, Modérateur
    novembre 2014 modifié #5
    C'est parce que tu as ajouté la sous-vue à  la cellule. Comme je disais, il la faut l'introduire à  l'index 0 du contentView de la cellule
  • Joanna CarterJoanna Carter Membre, Modérateur
    novembre 2014 modifié #6

    En plus, ne fais ni l'addition ni l'insertion des vues ou des couches dana la méthode cellForRowAtIndexPath - les cellules renvoyées par dequeueReusableCellWithIdentifier peuvent déjà  avoir les vues ou couches de leurs dernière utilisation.


    Il vaut mieux sous-classer une cellue et de mettre le code de création des sous-vues et couches dans la méthode awakeFromNib


  • LouLou Membre
    novembre 2014 modifié #7

    EDIT: problème de taille réglé.


  • Joanna CarterJoanna Carter Membre, Modérateur
    Tu as fais une sous-classe de UITableViewCell ?
  • LouLou Membre
    novembre 2014 modifié #9

    Alright, parfait, merci beaucoup ça marche  :)


     


    Voilà  le code :)



    @synthesize ibLabel;

    -(void) addBackground{
    UIView *view = [[UIView alloc] initWithFrame:CGRectMake(0,
    self.frame.origin.y,
    self.frame.size.width,
    self.frame.size.height)];
    CAGradientLayer *gradient = [CAGradientLayer layer];
    gradient.frame = view.bounds;
    gradient.colors = [NSArray arrayWithObjects:(id)[[UIColor whiteColor] CGColor], (id)[[UIColor blackColor] CGColor], nil];
    [view.layer insertSublayer:gradient atIndex:0];

    [self.contentView insertSubview:view atIndex:0];
    }

    - (void)awakeFromNib {
    [self addBackground];
    }

  • LouLou Membre
    novembre 2014 modifié #10

    EDIT: > comment utiliser CoreGraphics (comme le code ci-dessous) et l'afficher dans une layer, pour customiser la cell?   


     


    Joanna saurais-tu comment ajouter le code qui serait normalement dans drawRect:, à  l'intérieur d'une layer que je pourrais mettre dans ma custom cell class ?


    J'ai trouvé un exemple dans la doc, mais la ligne (stripe) est dessinée dans un "context", je ne sais pas bien comment je pourrais mettre ça dans une layer, et ensuite la mettre dans une subView de ma custom cell : (le lien de la doc pour faire le drapeau américain : lien



    -(void)addLine{
    CGContextRef context = UIGraphicsGetCurrentContext();

    CGRect stripeRect = CGRectMake (0, 0, self.frame.size.width, 17);
    CGLayerRef stripeLayer = CGLayerCreateWithContext (context, stripeRect.size, NULL);
    CGContextRef myLayerContext1 = CGLayerGetContext (stripeLayer);
    CGContextSetRGBFillColor (myLayerContext1, 1, 0 , 0, 1);
    CGContextFillRect (myLayerContext1, stripeRect);

    CGContextSaveGState(context);
    CGContextDrawLayerAtPoint (context, CGPointMake(0, self.frame.size.height-10), stripeLayer);
    CGContextRestoreGState(context);

    CGLayerRelease(stripeLayer);
    }

    De plus, j'ai lu dans un message sur SO, que on peut utiliser drawRect: dans une custom cell, et drawRect est bien appelé quand je met un NSLog, mais rien n'est dessiné... alors, quelle est la bonne réponse? :)


    Merci


  • BooleanneBooleanne Membre
    novembre 2014 modifié #11

    Bonjour Lou,


     


    il y a une raison pour laquelle tu veux absolument un layer supplémentaire ?


    Si oui, tu peux créer une sous-classe de CALayer, que tu ajoutes à  l'init de la cell, et tu mets ton code dans le DrawInContext ?


    Si tu utilises un xib, pourquoi tu ne sous-classes pas ta backgroundView pour y dessiner ce que tu veux ?


  • LouLou Membre
    novembre 2014 modifié #12

    Salut Booleanne, 


    en fait je ne sais pas comment faire, je voudrais simplement pouvoir utiliser le iboutlet depuis le uiviewController, et sous-classer la cell pour pouvoir mettre le design que je veux. Il y a plein d'exemple avec Core Graphics dans la méthode draw..., mais rien ne s'affichait, donc j'ai suivi les conseils de Joanna et j'ai fait des sublayers. :) Or je n'arrive pas à  appeler les méthodes draw... de cette subLayer.


     


    Pour tes deux solutions :


    La uiView, je ne sais pas bien comment faire, seulement le initWithFrame sort dans la console, le drawRect ou autres tests n'apparaissent pas.


    Pour le layer, j'ai juste mis drawInContext mais rien ne s'affiche non plus dans la console, aurais-tu un exemple à  me montrer?


     


    Voilà  le code :



    #import "MyCell.h"

    #pragma BackgroundView

    @interface MyBV : UIView
    @end

    @implementation MyBV

    - (id)initWithFrame:(CGRect)frame
    {
    self = [super initWithFrame:frame];
    if (self) {
    NSLog(@initWithFrame BV);
    }
    return self;
    }

    - (void)layoutSubviews {
    NSLog(@layoutSubviews);
    }

    -(void) drawRect:(CGRect)rect{
    NSLog(@in MyBV drawRect);
    }
    @end


    #pragma SubLayer

    @interface MySubLayer : CALayer
    @end

    @implementation MySubLayer

    //la methode init...

    -(void) drawInContext:(CGContextRef)ctx{
    NSLog(@dans mySubLayer draw);
    CGContextRef context = ctx;//UIGraphicsGetCurrentContext();

    CGRect stripeRect = CGRectMake (0, 0, 100, 17);//self.frame.size.width
    CGLayerRef stripeLayer = CGLayerCreateWithContext (context, stripeRect.size, NULL);
    CGContextRef myLayerContext1 = CGLayerGetContext (stripeLayer);
    CGContextSetRGBFillColor (myLayerContext1, 1, 0 , 0, 1);
    CGContextFillRect (myLayerContext1, stripeRect);

    CGContextSaveGState(context);
    CGContextDrawLayerAtPoint (context, CGPointMake(0, 100-10), stripeLayer);//self.frame.size.height
    CGContextRestoreGState(context);

    CGLayerRelease(stripeLayer);
    }
    @end


    #pragma MyCell
    @implementation MyCell
    @synthesize ibLabel;

    - (void)awakeFromNib {
    MySubLayer *mySubLayer = [[MySubLayer alloc] init];
    [self.layer addSublayer:mySubLayer];

    MyBV *myBV = [[MyBV alloc] init];
    [self addSubview:myBV];

    self.ibLabel.text = @hello;
    }

  • Ton sublayer, je l'ajouterais non pas à  ta cell, mais à  ta MyBV, et le backgroundView, en IBOutlet, c'est ta MyBV.


  • Joanna CarterJoanna Carter Membre, Modérateur

    Lou, ce que tu voulais, c'est comme l'image ci-jointe ?


     


  • LouLou Membre
    novembre 2014 modifié #15

    >EDIT: Merci, mais je ne parviens toujours pas à  afficher le rectangle rouge dans ma cell (le code de mon subLayer avec drawInContext...)   :)



    - (void)awakeFromNib {
    [self addBackground];

    MySubLayer *mySubLayer = [[MySubLayer alloc] init];
    MyBV *myBV = [[MyBV alloc] init];

    [myBV.layer addSublayer:mySubLayer];
    self.backgroundView = myBV;

    Joanna, je voudrais rajouter un rectangle rouge sur toute la largeur en bas de la cell, mais c'est surtout pour tester et savoir comment utiliser les drawRect/InContext dans mon code.


     


    > Pour l'instant, mySubLayer, avec le drawInContext, ne m'affiche rien...


  • Joanna CarterJoanna Carter Membre, Modérateur
    novembre 2014 modifié #16

    C'est pourquoi que tu veux dessiner dans une couche ?


     


    Moi, j'ai fait un projet d'essai dans laquel je peux remplacer le gradient avec n'importe quelle vue sans aucune ligne de code


     


    Qu'est-ce que tu veux exactement ?


  • LouLou Membre
    novembre 2014 modifié #17

    > En fait, je voudrais simplement utiliser les méthodes draw... pour utiliser CoreGraphics dans une custom cell.


    Par exemple, en créant une ligne rouge assez épaisse (un rectangle) mais je ne parviens pas à  utiliser les méthodes draw... dans cette custom cell. 


     


     


    J'ai réussi à  le faire uniquement quand je crée une sous-classe et que je fais : "cell.backgroundView = myCell;"


    Mais j'aimerais mettre le design, et les iboutlets dans le même code, c'est-à -dire tout rénuir dans ma cell : le code customisé avec CoreGraphics pour le design, et changer le texte et autres data grâce au UIViewController. (donc avoir une sous-classe de ma custom cell dans le uiViewController, comme c'est le cas actuellement, mais je ne parviens pas à  jouer sur le design)


     


    Certains messages sur SO disent qu'on peut utiliser drawRect dans la custom cell, et Joanna tu me dis qu'il vaut mieux faire des sublayers...


    -pour le drawRect:


    je n'y arrive pas, drawRect ne s'affiche pas, alors j'essaie de le mettre dans une sous-classe comme suggéré plus haut


    - dans une sous-classe de layer, drawInContext n'est pas appelé. Le init de cette sous-classe est bien appelé, mais pas sa fonction draw...


     


    Donc... je suis bloqué. :)


    Je ne souhaite pas tout faire dans le xib car je voudrais utiliser CoreGraphics.


  • Joanna CarterJoanna Carter Membre, Modérateur
    novembre 2014 modifié #18

    Si je t'ai bien entendu, pour dessiner sur une cellule, il ne faut que sous-classer UIView en implémentant la méthode drawRect comme tu veux. Puis, dans le xib, tu ajoutes un UIView comme sous-vue et le mettre en place sur le contentView avec les contraintes d'autolayout, tu sélectionnes le UIView, tu choisis la palette "Identity Inspector" et tu saisis le nom de ta classe au lieu de UIView. Il n'est pas nécessaire de toucher les couches.




  • Je ne souhaite pas tout faire dans le xib car je voudrais utiliser CoreGraphics.




     


     


    Comme te l'explique Johanna très bien, en sous-classant  ta view, tu pourras utiliser CoreGraphic, dans le drawRect. Dans tous les cas tu n'as pas vraiment le choix, c'est de cette façon que tu peux dessiner tes vues librement.


    Les sublayers, on les utilise quand on a besoin de différents calques pour une vue.

  • LouLou Membre
    décembre 2014 modifié #20

    Merci,


    alors le résultat :


     


    avec UIVIew, aucun problème finalement, une subView dans la custom cell, et le drawRect, marchent très bien.


     


    Pour tester, j'ai voulu faire la même chose, avec un layer, et j'ai trouvé ce site qui résume (lien) la démarche :


    1/ soit on init un CALayer, on set le delegate (dans self, ou dans myBV), et on setNeedsDisplay, et on utilise drawLayer:inContext:


    2/ soit on crée une sous-classe de CALayer, et on utilise drawInContext:


     


    Pour le 1/ et 2/ : j'ai un exc_bad_access


    Pour le 1/ : c'est dans la custom cell : quand j'ajoute le layer ou la custom view à  l'intérieur du contentView


    Pour le 2/, c'est dans le uiviewController, à  la ligne "dequeueReusableCellWithIdentifier"


     


    Le code :



    - (void)awakeFromNib {

    MyBV *myBV = [[MyBV alloc] initWithFrame:self.frame];
    //MySubLayer *layer = [[MySubLayer alloc] initWithLayer:[CALayer layer]];
    //MySubLayer *layer = [[MySubLayer alloc] init];

    [self.contentView addSubview:myBV];

    CALayer *l = [CALayer layer];
    l.delegate = self;
    [self.contentView.layer addSublayer:l];
    //[myBV.layer addSublayer:layer];
    [l setNeedsDisplay];

    Et je ne sais pas comment trouver le bug suite à  un exc_bad_access.


     


    Voilà  :) Si vous avez une idée, alors je serais intéressé de savoir ce qui se cache derrière ce bug.


    Sinon, j'utiliserai uniquement les uiview sans trop comprendre le problème :)


  • Je ne suis pas sure, mais ton erreur vient peut-être du fait que tu demandes au CALayer de se redessiner, alors qu'il n'est pas affiché. 


    Il faut plutôt mettre ton setNeedsDisplay à  l'appel de la Cell (dans le CellForRowAtIndexPath).


  • Joanna CarterJoanna Carter Membre, Modérateur
    décembre 2014 modifié #22

    De ce que j'entends de l'article, je crois que, si tu utilises les layers pour un tel simple scénario, c'est un cas d'optimisation précoce ; contentes-toi d'utiliser la subView 


  • Je pencherai pour la même explication que booléanne.


  • J'ai testé comme ça :



    @implementation MyCell

    - (void)drawLayer:(CALayer *)layer inContext:(CGContextRef)context {
    NSLog(@>>> drawLayer dans SELF);
    }

    - (void)awakeFromNib {
    CALayer *l = [CALayer layer];
    l.delegate = self;
    [self.contentView.layer addSublayer:l];


    //

    - (UITableViewCell *)tableView:(UITableView*)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    MyCell *cell = (MyCell*)[tableView dequeueReusableCellWithIdentifier:@Cell forIndexPath:indexPath]; //bug exc_bad_access

    Mais il y a toujours l'erreur exc_bad_access...


  • As-tu un projet test que tu pourrais nous envoyer pour que l'on teste ?




  • J'ai testé comme ça :



    @implementation MyCell

    - (void)drawLayer:(CALayer *)layer inContext:(CGContextRef)context {
    NSLog(@>>> drawLayer dans SELF);
    }

    - (void)awakeFromNib {
    CALayer *l = [CALayer layer];
    l.delegate = self;
    [self.contentView.layer addSublayer:l];


    //

    - (UITableViewCell *)tableView:(UITableView*)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    MyCell *cell = (MyCell*)[tableView dequeueReusableCellWithIdentifier:@Cell forIndexPath:indexPath]; //bug exc_bad_access

    Mais il y a toujours l'erreur exc_bad_access...




     


     


    Je crois que tu ne t'y prends pas comme il faut. Je confirme déjà  ce que te dis Johanna : inutile de t'embêter avec un CALayer. Mais comme j'ai l'impression que tu y tiens vraiment, dans un premier temps, supprime ce que tu as mis dans awakeFromNib. 


    Tu peux aussi supprimer ton drawlayer, il est appelé dans ton CALayer, pas dans ta cell.


     


    Ensuite, si tu as toujours ta classe bgView, ajoute-lui un CALayer en variable. A l'init de ton bgView, alloue ton CALayer et ajoute-le à  ta bgView;


     


    Après ça :


    MyCell *cell = (MyCell*)[tableView dequeueReusableCellWithIdentifier:@Cell forIndexPath:indexPath];


    [cell.contentView addSubview:bgView];


     


    Normalement, si je n'ai rien oublié, ça doit fonctionner. Mais du coup, pourquoi tu ne te contentes pas du drawRect de ta view ?


    Si c'est juste pour t'entraà®ner, le meilleur moyen n'est pas de l'utiliser dans une cell.

  • Salut Booleanne, 


    non je n'y tiens pas forcément :) C'est pour comprendre, c'est tout. D'après ce que j'ai pu lire, une layer peut être rajoutée à  une view, et on peut empiler les layer dans une view. Mais les méthodes de la layer ne sont pas appelées, alors je voulais comprendre pourquoi :)


     


    J'ai essayé ce que tu as dit, mais seulement les "init..." s'affichent, et le drawRect de la view. Je n'ai pas les draw... du layer avec ce code dans le MasterViewController :



    #pragma SubLayer
    @interface MySubLayer : CALayer
    @end

    @implementation MySubLayer

    -(id)init{
    self = [super init];
    if (self) {
    NSLog(@init in MySubLayer);
    }
    return self;
    }

    - (id)initWithLayer:(id)layer
    {
    self = [super initWithLayer:layer];
    if (self) {
    NSLog(@initWithLayer MySublayer);
    }
    return self;
    }

    -(void)drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx{
    NSLog(@drawLayer sublayer);
    }

    -(void) drawInContext:(CGContextRef)ctx{
    NSLog(@---> mySubLayer : drawInContext);
    }
    @end








    @interface MyBV : UIView
    @property (strong, nonatomic) CALayer* myLayer;
    @end


    @implementation MyBV
    - (id)initWithFrame:(CGRect)frame
    {
    self = [super initWithFrame:frame];
    if (self) {
    NSLog(@initWithFrame MyBV);
    //self.myLayer = ;
    [self.layer addSublayer: [[MySubLayer alloc] init]];//self.myLayer];
    }
    return self;
    }

    - (void) drawRect:(CGRect)rect{
    NSLog(@drawRect dans MyBV);
    }
    - (void)layoutSubviews {
    //NSLog(@layoutSubviews);
    }
    @end






    @implementation MasterViewController

    ...

    - (UITableViewCell *)tableView:(UITableView*)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    MyCell *cell = (MyCell*)[tableView dequeueReusableCellWithIdentifier:@Cell forIndexPath:indexPath];
    MyBV *myBV = [[MyBV alloc] initWithFrame:cell.frame];
    [cell.contentView addSubview:myBV];
    [cell setNeedsDisplay];

    Donc je vais rester avec les subview :)


     


    Merci pour vos réponses 





  • [self.layer addSublayer: [[MySubLayer alloc] init]];//self.myLayer];



     


    Le reste du code me paraà®t plus cohérent, maintenant. Mais à  priori ton sublayer n'a pas de frame.

  • Super, c'est top. Merci beaucoup


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