Adapter la hauteur d'une ligne dans un NSTableView

colas_colas_ Membre
septembre 2013 modifié dans API AppKit #1

Bonjour,


 


J'ai une NSTableView mais le texte à  afficher est des fois long et des fois court.


Je voudrais donc que les "cases" de ma NSTableView soient plus ou moins hautes pour montrer tout le texte.


 


(PS : ma NSTableView est liée à  un NSArrayController, mais éventuellement, je peux la datasourcer à  la main)


 


Avez-vous une idée de comment faire ?


 


En cherchant sur le web, j'ai trouvé ceci : 



cell.textLabel.numberOfLines = 0;
cell.textLabel.lineBreakMode = UILineBreakModeWordWrap;

(cf. http://stackoverflow.com/questions/5964448/ios-uitableview-cells-with-multiple-lines)


 


... que je devrais adapter pour osx.


 


Ma question à  ce niveau est : à  quel endroit du code puis insérer ce code ?


Dans une méthode du delegate ?


 


Y a-t-il une autre manière de faire ?


 


Merci !


Réponses

  • LeChatNoirLeChatNoir Membre, Modérateur

    ben je dirai dans ton datasource :


    - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath


     


    Et il faudra surement régler la hauteur de ta ligne dans ton delegate :



    tableView:heightForRowAtIndexPath:


  •  


    Et il faudra surement régler la hauteur de ta ligne dans ton delegate :


    tableView:heightForRowAtIndexPath:

     




     


    Merci !


    je vais essayer de juste toucher au delegate !

  • LeChatNoirLeChatNoir Membre, Modérateur

    vaz y chaton :)


  • AliGatorAliGator Membre, Modérateur

    On a pas déjà  évoqué les meilleures manières de comment résoudre ce pb dans d'autres threads ? (recherche ?)


     


    C'est un cas assez classique, en utilisant les méthodes de "NSString UIKit Additions" pour connaà®tre la taille que prendra ton texte à  l'écran (en largeur et hauteur) tu peux en déduire la hauteur de chaque cellule (suffit d'ajouter les marges que tu as entre ton UILabel et les bords de ta cellule) et implémenter heightForRowAtIndexPath.


     


    J'ai déjà  parlé il me semble de tous les tenants et aboutissants autour de cette problématique : suggestion d'implémentation, comment récupérer automatiquement les marges depuis un XIB si tes cells sont dans un XIB custom, comment mettre en cache ces données de marges, mettre en cache ces données de hauteur pour éviter de les recalculer à  chaque scroll / reuse, ...


  • colas_colas_ Membre
    septembre 2013 modifié #6

    Si je veux suivre l'idée de http://stackoverflow.com/questions/5964448/ios-uitableview-cells-with-multiple-lines, comment puis-je modifier les attributs des cell de ma NSTableView ?


     


    PS @Ali : Je n'ai pas trouvé tes posts...


  • Puisqu'on est dans Cocoa, ça devrait être la méthode:


    - (void)tableView:(NSTableView *)aTableView willDisplayCell:(id)aCell forTableColumn:(NSTableColumn *)aTableColumn row:(NSInteger)rowIndex


  • LeChatNoirLeChatNoir Membre, Modérateur

    oups, sorry....


  • Voici la réponse, basée sur ce post :


     


    http://stackoverflow.com/questions/7504546/view-based-nstableview-with-rows-that-have-dynamic-heights


     


     


    Idée : on calcule tout à  la main.


    Mais, on fait ce calcul sur une NSTextFieldCell qui est créée à  part juste pour ce besoin !


     


    Ici, la tableView est remplie via un NSArrayController.


     


     


     


    Code dans le delegate :



    @interface MenuDelegate ()

    @property (nonatomic, weak) NSArrayController * theArrayController ;

    @property (nonatomic, strong) NSTextFieldCell * anExtraTextFieldCell ;
    @property (nonatomic) NSRect tallRect ;

    @end



    @implementation MenuDelegate



    #pragma mark - Initialisations


    - (id)initwithArrayController:(NSArrayController *)theArrayController;
    {
    self = [super init] ;

    if (self)
    {
    self.theArrayController = theArrayController ;

    self.anExtraTextFieldCell = [[NSTextFieldCell alloc] init] ;
    }

    return self ;
    }


    puis


     




    - (CGFloat) tableView:(NSTableView *)tableView
    heightOfRow:(NSInteger)row {
    // Grab the fully prepared cell with our content filled in. Note that in IB the cell's Layout is set to Wraps.


    if (!self.tallRect.size.width)
    {
    NSTableColumn * firstColum = tableView.tableColumns[0] ;
    self.tallRect = NSMakeRect(0, 0, firstColum.width, CGFLOAT_MAX);
    }


    NSString * content = [self.theArrayController.arrangedObjects[row] valueForKey:@title] ;
    self.anExtraTextFieldCell.stringValue = content ;

    CGFloat result = [self.anExtraTextFieldCell cellSizeForBounds:self.tallRect].height + 5;


    if (result < [tableView rowHeight])
    {
    result = [tableView rowHeight] ;
    }

    return result ;
    }
  • AliGatorAliGator Membre, Modérateur
    En bref : exactement ce que j'explique dans mes posts.
  • colas_colas_ Membre
    septembre 2013 modifié #12


    En bref : exactement ce que j'explique dans mes posts.




     


    "exactement" est un peu exagéré  ::)


     


    Tu ne décris pas le mécanisme d'avoir une NSCell indépendante du NSTableView sous la main



    @property (nonatomic, strong) NSTextFieldCell * anExtraTextFieldCell ; 

    pour faire le calcul de la hauteur qu'il faut choisir.


     


     


    Cela ne t'enlève pas le mérite de tes nombreuses réponses. J'avais besoin d'une aide plus concrète car je n'avais jamais utilisé la classe NSCell : le mécanisme d'envoyer un NSRect à  NSCell pour obtenir une NSSize, je ne l'aurai pas deviné (entre autres). L'occasion de te remercier grandement pour toutes les contributions que tu fais ici, toujours complètes et agréables à  lire !  :D  Merci !


  • AliGatorAliGator Membre, Modérateur


    "exactement" est un peu exagéré  ::)


     


    Tu ne décris pas le mécanisme d'avoir une NSCell indépendante du NSTableView sous la main



    @property (nonatomic, strong) NSTextFieldCell * anExtraTextFieldCell ; 

    pour faire le calcul de la hauteur qu'il faut choisir.




     


    Bah si, enfin j'explique l'équivalent sous iOS.


     


    Et j'explique comment rendre ça plus propre même avec un dispatch_once pour n'instancier cette cellule depuis un UINib que quand c'est nécessaire (lazy-loading) et comment plutôt que garder une "UITableViewCell* dummyCellForSize" complète en mémoire il est mieux une fois qu'on l'a chargé et qu'on a récupéré toutes les marges et qu'on les a stocké dans une petite structure maison, on peut relâcher la cellule.


     


    En gros

    + (CGSize)cellSizeForText:(NSString*)text
    {
      static CGSize margins = CGSizeZero;
      static UIFont* labelFont = nil;
      dispatch_once_t once;
      dispatch_once(once, ^{
        // Charger la UITableViewCell juste pour récupérer les infos de marges dynamiquement, seulement la première fois
        UINib* nib = [UINib nibWithNibName:@MySuperCell];
        MySuperCell* dummyCellForSize = [nib instanciateWithOwner:nil options];
        margins.width = dummyCellForSize.width - dummyCellForSize.textLabel.width; // différence entre largeur de la cell et largeur du label
        margins.height = dummyCellForSize.height - dummyCellForSize.textLabel.height; // différence entre hauteur de la cell et hauteur du label
        labelFont = dummyCellForSize.textLabel.font;
      });
     
      // On calcule la taille que prendra le texte une fois rendu à  l'écran
      CGSize textSize = [text sizeWithFont:labelFont ...];
      // On ajoute les marges (qu'on a calculé qu'une seule fois une fois pour toutes grace au dispatch_once) pour avoir la taille de la cell
      textSize.width += margins.width;
      textSize.height += margins.height;
      return textSize;
    }
    Bon c'est du code retapé en live et pas testé mais le principe est là  : la première fois qu'on appelle cellSizeForText, on instancie la cell à  l'aide du XIB, on récupère toutes les métriques dont on a besoin, les marges, la police, et pourquoi pas d'autres attributs qui nous seront nécessaires (le mode de wrapping du label par exemple, que je n'ai pas mis dans mon pseudo-code). Et une fois qu'on a ces éléments, plus besoin de la cellule (qui potentiellement prend de la place en mémoire, si elle a plein de sous-vues qui en plus contiennent des images ou autre, etc...), si la laisses s'autorelease, et tu ne gardes que les métriques comme margins ou labelFont, variables statiques donc dont tu garderas la valeur pour les appels suivants. Au final lors des autres appels tu auras déjà  toutes les métriques qu'il te faut pour passer de la taille de ton texte à  la taille de ta cellule, pas besoin de réinstancier une cellule depuis le XIB à  chaque fois. Et pas besoin de garder la cellule entière dans une propriété non plus.
  • colas_colas_ Membre
    septembre 2013 modifié #14

    Oui.


     


    Remarque que ma NSCell supplémentaire n'est instanciée qu'une seule fois par la classe déléguée. Ce qui n'est pas énorme (du tout) !


     


    Si on voulait faire une archi, on pourrait imaginer une méthode appartenant à  ta classe héritant de NSCell et qui prendrait en argument un texte et une largeur, et qui renverrait une hauteur.


  • AliGatorAliGator Membre, Modérateur


    Oui.


     


    Si on voulait faire une archi, on pourrait imaginer une méthode appartenant à  ta classe héritant de NSCell et qui prendrait en argument un texte et une largeur, et qui renverrait une hauteur.




    Heu... c'est exactement ce que je propose justement... Une méthode de classe de ma sous-classe de UITableViewCell (équivalent iOS de NSCell) qui renvoie la CGSize (et en effet je pourrais me contenter de la hauteur) en fonction du texte.


     


    N'instancier qu'une seule fois ta NSCell peut ne pas te sembler énorme, mais ça dépend ce que contient ta NSCell (si elle a plein de subviews qui sont instanciées et qui dans leur constructeur crée aussi des objets internes et tout...). Avoir plein de NSView (la cell et ses subviews) + potentiellement des NSArray ou autres données internes instanciées par ton contenu de ta NSCell ça peut vite prendre de la place alors que tu n'en as cure. Alors qu'une bête structure qui ne stoque que 2-3 valeurs (les marges, la police de caractère utilisée, ...) c'est quand même vachement plus léger.


     


    Au final j'explique dans mes posts toute l'architecture que j'utilise pour ça (et même en général j'ai une autre version si je veux optimiser je mets des constantes dans mon code et je fais ce bout de code qu'en debug avec un NSAssert pour vérifier que mes constantes sont consistantes avec le XIB, comme ça en Release c'est encore plus efficace et immédiat et en Debug il m'alerte tout de suite si j'ai modifié le XIB et oublié de modifier les constantes en conséquence...), mais bon j'ai déjà  débattu et expliqué les pours et les contres et les inconvénients de telle méthode et pourquoi je propose telle solution à  la place...

  • Autant pour moi !


  • AliGatorAliGator Membre, Modérateur

    PS : En voyant ta réponse je viens de relire la mienne à  tête reposée et réalise qu'elle est un peu "sèche", désolé, ce n'était pas le but hein ^^ ;)


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