Binding en code dans une tableView (view-based)

Bonjour à  tous,


 


J'ai crée une view " standard " (avec une tableView et quelques boutons), associée à  une viewController. L'objectif est de pouvoir la réutiliser, à  chaque fois avec un contenu différent: le contenu de la table change, les actions associées aux boutons aussi.


J'ai donc créé 3 sous classes de la viewController. Dans chaque cas, le contenu vient de CoreData, via un arrayController, et chaque sousclasse est configurée pour aller récupérer les données dans une entité différente.


Le côté positif: maximiser le code pouvant être réutilisé.


Le côté négatif: pas possible d'utiliser les bindings dans IB: les 3 viewControllers s'appuient sur la même view et comme le Model Key Path est différent selon les entités...


Du coup, je me suis dis que j'allais joyeusement " binder " en code, d'autant que le contenu de la table est simple et identique pour les 3 cas:


colonne1: une image


colonne 2: un texte


colonne 3: une image


 


Pas de pb pour le binding entre la table et l'arraycontroller:


        tableView.bind(NSContentBinding, to: dataController, withKeyPath: "arrangedObjects", options: nil)


        tableView.bind(NSSelectionIndexesBinding, to: dataController, withKeyPath:"selectionIndexes", options: nil)


        tableView.bind(NSSortDescriptorsBinding, to: dataController, withKeyPath: "sortDescriptors", options: nil)


 


Les données sont bien là , mais n'apparaissent bien sûr pas comme elles devraient:


https://www.dropbox.com/s/ttluaraphpw03k2/Snap1.png?dl=0


 


Mais comment réaliser le binding entre les contrôles et les TableCellViews? En gros, comment lier, pour chaque objet contenu dans l'ArrayController, les propriétés qui m'intéressent. D'autant que les images sont obtenues par un ValueTransformer (qui marche bien par ailleurs, j'ai testé)


Dans IB, la marche à  suivre est simple. On binde vers Table Cell View, on renseigne le Model Key Path et éventuellement le Value Transformer:


https://www.dropbox.com/s/dstejjxa49mw00w/Snap2.png?dl=0


 


Mais en code, c'est beaucoup plus flou et je n'ai jusqu'alors pas trouvé la solution.


La documentation Apple (https://developer.apple.com/library/mac/documentation/Cocoa/Reference/CocoaBindingsRef/CocoaBindingsRef.html#//apple_ref/doc/uid/10000189i), liste les classes supportant le binding. On y retrouve NSTableView, mais pas NSTableCellView (qui est pourtant explicitement ce vers quoi le binding est fait sur la copie d'écran précédente).


J'ai fait de nombreux essais, que j'ai par exemple trouvé sur StackOverflow mais rien ne fonctionne:


  • Mettre un bind dans la fonction du délégué 

func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView?

=>La fonction n'est jamais appelée...


 


  • Créer un outlet de la Table Cell View pour pouvoir invoquer dans ViewDidLoad:

cellView.imageView?.bind(NSValueBinding, to: cellView, withKeyPath: "objectValue.url", options: [NSValueTransformerBindingOption: fileToIconVT()])

=> mais ça plante parce que cellView reste mystérieusement à  nil, ce qui est bizarre puisqu'avec Swift, la déclaration:



 @IBOutlet weak var tcv1: NSTableCellView!

​devrait l'empêcher de l'être...


 


Auriez-vous des pistes?


 


Joshua


Mots clés:

Réponses

  • Autre idée :


    Tu bindes ta vue à  des @property de ton VC et ton VC a un objet delegate qui lui donne "column1", "column2", etc.


    Il faudra faire attention pour le binding reste dynamique à  implémenter les méthodes `keyPathsForValuesAffectingValueForKey:` (cf. https://developer.apple.com/library/ios/documentation/Cocoa/Conceptual/KeyValueObserving/Articles/KVODependentKeys.html#//apple_ref/doc/uid/20002179-BAJEAIEE)


     


    Sinon, ma première réaction à  ton idée : ça m'a pas l'air très YAGNI tout ça !


     


    PS : Désolé je ne réponds à  tes questions...


  • Merci pour ta réponse rapide (même si elle ne réponds pas à  mes questions!)


    Ton approche est astucieuse, il faut que j'explore ça !


     


    Je ne sais pas si c'est YAGNI, mais en tout cas, ça se veut DRY: pas envie de programmer 3 fois la même chose!


    Mais je suis toujours preneur d'approches différentes: comment aurais-tu fais?


  • Bin en fait, je ne connais pas trop ton problème mais j'ai le sentiment (d'après mon expérience) que la factorisation d'interfaces c'est rarement une bonne idée (sauf pour les composants).


     


    Je pense que j'aurais essayé de faire trois VCs distincts, quitte à  ce qu'ils utilisent tous les trois un composant en commun.


    L'avantage de cette approche YAGNI, c'est que tu vas pas passer 1000 ans à  mettre en place ta solution générique (même si c'est super parce que du coup tu vas apprendre plein de trucs). Surtout que dans 2 jours, ton interface aura changé et tout ton boulot sera à  refaire (ou pas :p). Et puis, du coup aussi tu pourras avoir des interfaces légèrement différentes.


     


    Si tu veux vraiment que les interfaces soient les mêmes, tu peux connecter un même fichier .xib à  différents VCs ! Peut-être que c'est ça la solution, avec en plus un object "moteur" commun pour ces controllers.


  • Certes, les 3 VC distincts connectés à  une même interface, possible... Sauf que le problème des bindings n'en sera pas résolu pour autant, le seul moyen de les faire étant apparement via IB (cf question initiale)...


    Ce qui renvoie à  ta solution de contournement. Et d'après ce que j'ai compris (ou pas? ;-), elle me semble justement nécessiter un VC parent... Non?


     


    J


  • Oui c'est une solution ou tu as un VC parent.


     


    Est-ce que tu ne pourrais pas faire un truc du genre ?


    0) tu ajoutes des catégories à  tes objets modèle :


    @property NSString *stringForFirstColumn;


    @property NSString *stringForSecondColumn; 


    etc.


    - (void) createObjectWithFirstString:(NSString *)firstString second:(NSString *)secondString etc.


    1) Ton VC parent a une @property publique (désolé je ne parle pas le Swift) arrayController


    2) Quand tu crées le VC enfant, tu crées par la même occasion le arrayController que tu connecte à  ton modèle


  • On peut dire que tu ne manques pas de ressources!


    C'est finaud... je vais explorer cette piste, merci de tes lumières!


    (et quand même envoyer un message à  Apple pour demander des éclaircissements sur cette histoire de bindings de TableViewCell...)


     


    Josh


  • Je ne sais pas si je comprends bien la question, mais il est assez facile de rafraà®chir la liste d'une tableView. Il suffit de refaire la liste. Après, c'est vrai que j'utilise les connexions IB pour que cela fonctionne.


     


    Par exemple, dans mes synthés, la mise à  jour de la liste en fonction de la famille de sons (extrait) :



    else {
    liste1Filtre = [[NSMutableArray alloc]init];
    listeURLFiltre = [[NSMutableArray alloc]init];
    BOOL onPeutLeMettre;
    nbChar = (int) [familyToneName length];
    for (int i = 0; i < [liste1 count]; i++){
    onPeutLeMettre = NO;
    for (int j = 0; j < nbChar; j++){
    if(([[liste1 objectAtIndex:i]length] > nbChar)&&([[liste1 objectAtIndex:i]characterAtIndex:j] == [familyToneName characterAtIndex:j])){
    onPeutLeMettre = YES;
    }
    else {onPeutLeMettre = NO;
    break;
    }
    }//fin for j nbChar

    if (onPeutLeMettre == YES){
    [liste1Filtre addObject:[liste1 objectAtIndex:i]];
    [listeURLFiltre addObject:[listeURL objectAtIndex:i]];
    }//fin si on peut le mettre
    }//fin for i liste1
    if ([liste1Filtre count] > 0){
    liste1 = [[NSMutableArray alloc]initWithArray:liste1Filtre];
    listeURL = [[NSMutableArray alloc]initWithArray:listeURLFiltre];
    [tableView1 reloadData];
    }

    Je ne sais pas si ça t'aide...


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