Bindings Core Data et NSTableView avec des group rows
Myotis
Membre
Bonjour à tous.
Alors mon "problème" est un peu plus vaste que ce qui est écrit dans le titre mais il vallait mieux faire court. Je vais retracer toute la réflexion et le cheminement que j'ai eut avec cette problématique pour vous permettre de bien la cerner.
Commençons donc par le commencement. J'ai une application Mac dans laquelle j'ai une table view (conséquente mais avec une seule colonne) qui liste des items qui possèdent chacun un timestamp. Ces items sont donc listés du plus récent au plus ancien. Ce sont des entités Core Data et mon application doit faire des mises à jours fréquentes de données (ajout, suppression, modification) et j'aimerai évidemment les répercuter instantanément sur cette table view.
Voilà donc pour la problématique de base.
Développant également pour iOS, la classe NSFetchedResultController aurait été exactement ce dont j'aurai eut besoin ... malheureusement elle est inexistante sur OS X...
Je suis donc parti à la recherche d'une solution alternative et évidemment les bindings semblent être cette solution.
J'ai donc un NSArrayController bindé à Core Data et ma table view pioche ses données grace à des bindings sur le contenu géré par cet array controller.
Mais voilà , j'ai besoin de "group rows" (les cellules flottantes, à la manière des sections sur iOS) et c'est là que ce situe le problème.
Chaque group row affiche un string de la forme "Jour XX mois année" et évidemment ces cellules sont calculées en fonction des timestamp des entités que je dois afficher.
Pour me permettre d'avoir ces group rows j'ai créé une classe qui hérite de NSArrayController et dans sa méthode (NSArray *)arrangeObjects:(NSArray *)objects je procède à des opérations qui me permettent de trier mon contenu et j'en profite pour insérer mes sections dans le tableau de retour.
Tout fonctionne donc dans le meilleur des mondes à une exception prêt ... cette solution n'est pas viable. Je m'explique. La classe NSArrayController dispose d'un ensemble de méthodes tel que addObject: et addObjects. Sauf que pour me signaler qu'un nouvel objet a été inséré dans Core Data, c'est la méthode setContent:(NSArray *)content qui est appelée. Cette méthode va remplacer "l'ancien" contenu par le nouveau (c'est à dire : toutes mes entités, pour faire simple) et donc elle va réexécuter la méthode arrangeObjects: . Donc effectivement ma table view est constamment à jour mais c'est bien trop lourd de trier et d'insérer les sections à chaque modification de ma base.
Bref! Voilà donc où j'en suis. Ma solution serait viable si je pouvais faire en sorte que Core Data fasse appelle aux méthodes "addObject:" et consort plutôt que de recharger tout le contenu de mon array controller.
Maintenant voici une liste de petites questions :
- pensez vous que c'est une erreur de ma part si Core Data recharge TOUT le contenu? Si oui, d'où cela pourrait-il venir?
- est ce que vous voyez une autre approche qui permettrait de binder une table view à Core Data tout en ayant des sections?
J'espère qu'avec tout ça je vous ai donné suffisamment d'informations. Je peux toujours en donner plus si nécessaire.
Alors mon "problème" est un peu plus vaste que ce qui est écrit dans le titre mais il vallait mieux faire court. Je vais retracer toute la réflexion et le cheminement que j'ai eut avec cette problématique pour vous permettre de bien la cerner.
Commençons donc par le commencement. J'ai une application Mac dans laquelle j'ai une table view (conséquente mais avec une seule colonne) qui liste des items qui possèdent chacun un timestamp. Ces items sont donc listés du plus récent au plus ancien. Ce sont des entités Core Data et mon application doit faire des mises à jours fréquentes de données (ajout, suppression, modification) et j'aimerai évidemment les répercuter instantanément sur cette table view.
Voilà donc pour la problématique de base.
Développant également pour iOS, la classe NSFetchedResultController aurait été exactement ce dont j'aurai eut besoin ... malheureusement elle est inexistante sur OS X...
Je suis donc parti à la recherche d'une solution alternative et évidemment les bindings semblent être cette solution.
J'ai donc un NSArrayController bindé à Core Data et ma table view pioche ses données grace à des bindings sur le contenu géré par cet array controller.
Mais voilà , j'ai besoin de "group rows" (les cellules flottantes, à la manière des sections sur iOS) et c'est là que ce situe le problème.
Chaque group row affiche un string de la forme "Jour XX mois année" et évidemment ces cellules sont calculées en fonction des timestamp des entités que je dois afficher.
Pour me permettre d'avoir ces group rows j'ai créé une classe qui hérite de NSArrayController et dans sa méthode (NSArray *)arrangeObjects:(NSArray *)objects je procède à des opérations qui me permettent de trier mon contenu et j'en profite pour insérer mes sections dans le tableau de retour.
Tout fonctionne donc dans le meilleur des mondes à une exception prêt ... cette solution n'est pas viable. Je m'explique. La classe NSArrayController dispose d'un ensemble de méthodes tel que addObject: et addObjects. Sauf que pour me signaler qu'un nouvel objet a été inséré dans Core Data, c'est la méthode setContent:(NSArray *)content qui est appelée. Cette méthode va remplacer "l'ancien" contenu par le nouveau (c'est à dire : toutes mes entités, pour faire simple) et donc elle va réexécuter la méthode arrangeObjects: . Donc effectivement ma table view est constamment à jour mais c'est bien trop lourd de trier et d'insérer les sections à chaque modification de ma base.
Bref! Voilà donc où j'en suis. Ma solution serait viable si je pouvais faire en sorte que Core Data fasse appelle aux méthodes "addObject:" et consort plutôt que de recharger tout le contenu de mon array controller.
Maintenant voici une liste de petites questions :
- pensez vous que c'est une erreur de ma part si Core Data recharge TOUT le contenu? Si oui, d'où cela pourrait-il venir?
- est ce que vous voyez une autre approche qui permettrait de binder une table view à Core Data tout en ayant des sections?
J'espère qu'avec tout ça je vous ai donné suffisamment d'informations. Je peux toujours en donner plus si nécessaire.
Connectez-vous ou Inscrivez-vous pour répondre.
Réponses
1) les Bindings sont basés sur le Key-Value Observing. De fait, le modèle n'a jamais à signaler une modification, il suffit d'appeler les méthodes de notifications adéquates dans les setters:
-[willChangeValueForKey:] et -[didChangeValueForKey:].
(Les setters Core Data et les propriétés @synthétisées le font de manière transparente).
2) D'expérience, il ne faut jamais sous-classer NSArrayController. C'est trop compliqué, on n'a aucune idée de la manière dont c'est implémenté. Il y a toujours une autre manière de faire.
3) Les bindings des NSTableColumns sont limités; ça ne convient que pour les cas courants (85% du temps).
La bonne vieille méthode du data source me semble plus indiquée. Utilise le Key-Value Observing pour savoir quand les objets Core Data ont été modifiés, et mets à jour le minimum dans la table.
Du coup je vais peut-être dire une grosse bêtise mais : pourquoi ne pas créer une entité Core Data qui imite ton principe des sections.
Quelque chose comme : Section<->>Detail ?
1 ligne dans section te permet donc de faire le lien à n lignes dans détail. Section devient alors l'élément principal de ton bindings.
C'est juste une piste, si ça se trouve elle ne répond pas à ton problème. Dans ce cas, désolé pour le bruit :-)
Et dans les méthodes du delegate il n'y a pas d'indexPath (section + row) mais seulement un UInteger row donc aucun moyen direct de savoir dans quel section se trouve cette cellule.
Pour l'instant je vais suivre le conseil de Céroce et utiliser le KVO. Je vous tiendrais au courant de mon avancement.
Merci pour vos réponses!
Quand on binde NSTableColumn, c'est le NSArrayController qui sert de data source; le binding est assez limité. Comme l'indique Myotis, il n'y a pas de notion de Section dans une NSTableView.
À la limite, NSOutlineView est plus proche de UITableView.
Ah vi... Ca me revient. J'avais fait un truc similaire à une époque et effectivement j'utilisais NSOutlineView. Merci pour ces précisions.
J'ai donc finalement opté pour du KVO. Ce qui me permet d'avoir la flexibilité dont j'avais besoin et qui imite (dans les grandes lignes) le comportement du NSFetchedResultController sur iOS.
Bref, merci Céroce! /wink.png' class='bbc_emoticon' alt=';)' />