Arborescences dans les NSUserDefaultsController
Omfraax
Membre
Bonsoir à tous.
En voulant faire le malin pour les préférences de mon appli, je me suis dit: "Tiens, tout marche bien comme ça, et si j'essayais de faire des préférences arborescentes", c'est à dire un truc comme ça
le but de la manoeuvre étant de pouvoir faire des bindings sur des keypaths du type foo.bar.item1
Ca marche quand j'accède aux préférences directement dans le code avec le standardUserDefaults, mais ça ne marche pas dans IB quand je binde avec le NSUserDefaultController.
Par contre, quand j'enlève cette arborescence en associant directement les valeurs à une clé simple de appDefaults, tous les bindings remarchent comme il faut.
Est-ce que tout simplement on ne peut pas utiliser les keypaths ramifiés avec NSUserDefaultController ou alors est-ce qu'il y a une astuce sioux ?
Merci beaucoup.
En voulant faire le malin pour les préférences de mon appli, je me suis dit: "Tiens, tout marche bien comme ça, et si j'essayais de faire des préférences arborescentes", c'est à dire un truc comme ça
<br />NSDictionary *bar=[NSDictionary dictionaryWithObjectsAndKeys: <br />[NSNumber numberWithInt:0],@"item1",<br />[NSNumber numberWithInt:1],@"item2",nil];<br /><br />NSDictionary *foo=[NSDictionary dictionaryWithObjectAndKeys: bar,@"bar",nil];<br /><br />NSDictionary *appDefaults=[NSDictionary dictionaryWithObjectAndKeys: foo,@"foo", nil];<br /><br />[defaults registerDefaults:appDefaults];<br />[[NSUserDefaultsController sharedUserDefaultsController] setInitialValues:appDefaults];<br />
le but de la manoeuvre étant de pouvoir faire des bindings sur des keypaths du type foo.bar.item1
Ca marche quand j'accède aux préférences directement dans le code avec le standardUserDefaults, mais ça ne marche pas dans IB quand je binde avec le NSUserDefaultController.
Par contre, quand j'enlève cette arborescence en associant directement les valeurs à une clé simple de appDefaults, tous les bindings remarchent comme il faut.
Est-ce que tout simplement on ne peut pas utiliser les keypaths ramifiés avec NSUserDefaultController ou alors est-ce qu'il y a une astuce sioux ?
Merci beaucoup.
Connectez-vous ou Inscrivez-vous pour répondre.
Réponses
+(void) initialize
{
// Registering initial values for preferences
NSUserDefaults * userDefaults=[NSUserDefaults standardUserDefaults];
NSData * colorData=[NSArchiver archivedDataWithRootObject: [NSColor purpleColor]];
NSDictionary * values=[NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithFloat:30.],@frameRotation,
[NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithFloat:18.],@fontSize,
colorData,@textColor,
nil], @textPrefs,
nil];
[userDefaults registerDefaults:values];
[[NSUserDefaultsController sharedUserDefaultsController] setInitialValues:values];
}
J'ai changé un peu la définition de mon appDefaults sans passer par des variables intermédiaires (en encapsulant directement les [NSDictionary dictionaryWithObjectsAndKeys:....]) pour avoir quelque chose de réellement identique à ton exemple mais ça ne marche toujours pas.
Cependant, j'ai une piste :
C'est peut-être encore une histoire d'initialisation car je déclare ces appDefaults dans le initialize d'une classe Preferences qui ne possède que des methodes de classe (un peu comme le Controller de ton exemple) alors que le reste de l'application est contrôlé par une classe GeneralController. J'ai bien instancié ces deux classes dans IB (les petits cubes bleus) pour que les +initialize des deux méthodes soit appelé. Mais je me suis aperçu en faisant des tests sur l'accès aux données de [NSUserDefaults standardUserDefaults] qu'elles sont null dans le -init du GeneralController mais qu'elles sont bien définies dans son -awakeFromNib.
Le problème pourrait venir de là mais je croyais pourtant que le nib appelait d'abord tous les initialize puis tous les inits, faisait ses connections et envoyait ensuite un awakeFromNib ?
Merci en tout cas pour tes réponses.
J'ai encore tout faux ? ??? :crackboom:-
Oui j'ai encore oublié de mettre le projet compatible avec XCode 3.1
cela est corrigé, cela doit marcher maintenant si tu es sous XCode 3.0
Je pense que c'est quelque chose comme cela. La méthode +(void)initialize doit être exécutée avant que le nib soit désarchivé. En général je le mets dans une classe qui contrôle l'application.
Je me suis inspiré d'un post trouvé sur :
http://www.cocoabuilder.com/archive/message/cocoa/2007/11/10/192782
En pratique :
Le début de mon fichier préférence (plist) :
Pour binder dans IB :
(Sa propriété onglet Atributes 'Automatically prepares contents' doit être coché)
Bind to : NSUserDefaultsController (NSUserDefaultsController)
Controller key : selection
Model Key Path : biddingPrefs
Bind to : biddingPrefsGroup
Controller key : selection
Model key path : agressiveness
Pour l'initialisation des préférences, je fais çà dans l'initialisation de l'application, car lecture d'un fichier tout prêt de défauts placé dans le bundle de l'application.
Le binding avec des Key Path n'a jamais marché dans mes essais.
Il y a plusieurs articles et tutoriels sur ce site concernant le binding des préférences, aucun ne rajoute d'object controllerÂ
L'exemple donné un peu au-dessus à ce post fonctionne.
J'ai fait mes initialisations dans une méthodes de classe de la classe Preferences, méthode que j'appelle dans le-initialize du controleur général de l'application (delegate de l'application et tout et tout).
Quand je fais des tests avec le code suivant dans le -init :
le premier renvoie (null) partout alors que le deuxième renvoie les bonnes valeurs !
Donc il ya une histoire de valueForKey et de valueForKeyPath, sachant que NSDictionary n'implémente pas valueForKeyPath
Peut-être qu'IB execute le premier choix quand il binde ?
Hillegass, page 203, Third Edition
"Each class is sent the initial message initialize before any other message. To ensure that your defaults are registered early, you will override +(void)initialize in AppController.m
Un message à une autre classe dans un +(void) initialize me paraà®t douteux, et de toutes façons inhabituel. Il faut éviter.
Mettre tout simplement les initialisations du Userdefaults dans le +(void) initialize de AppController sans message à une autre classe me paraà®t plus prudent.Â
Logique, ce dictionnaire n'a pas de clé dont le nom serait @foo.bar.items
valueForKeyPath est une méthode du protocole NSKeyValueCoding, auquel se conforme NSDictionary. Ainsi, on ne trouve pas directement dans la doc sur NSDictionary la méthode valueForKeyPath: , mais cette classe, ou une classe dont elle hérite, implémente néammoins cette méthode.
Quand au binding, il ne se met pas en observation du dictionnaire, mais en observation des setter de l'objet final "items" de l'objet "bar", ceci sans doute par l'intermédiaire d'un proxy.
Je suis désolé mais je suis encore sous 10.4 et donc XCode2 .
Je n'ai pas pu exploiter cet exemple.
Dans ma doc, il n'est pas dit que NSUserDefaultController adopte le protocole NSKeyValueCoding ou alors çà m'a échappé ce qui est fort possible.
Toujours est-il qu'en essayant de binder avec les keyPath directement dans IB, j'avais pour résultat la création d'une préférence libellée :
biddingPrefs.agressiveness
de type NSNumber
au lieu d'un Dictionary pour biddingPrefs avec un élément de type NSNumber nommé agressiveness
et il semble que ce soit aussi le cas pour Omfraax
Les bindings sont basés entièrement sur le Key-Value Coding et le Key-Value Observing. Je te conseille fortement de lire les docs d'Apple qui leur sont consacrés, parce que là , tu as loupé un éléphant dans un corridor.
Je dis bien "ça a l'air de marcher" puisqu'en y regardant de plus près, on remarque que le revertToInitialValues n'affecte ni la couleur ni la taille de police, autrement dit les paramètres inclus dans ce fameux Dictionary. Et effectivement, quand on regarde le fichier .plist, on voit bien des propriétés textPrefs.trucMuche probablement générés par IB quand les boutons de contrôles bougent mais qui n'ont rien à voir avec ce que l'on aurait attendu, à savoir un Dictionary textPrefs contenant deux items.
Dans mon programmes, les propriétés que je veux afficher ne sont pas bindées à un contrôle quelconque (il s'agit en fait de NSArray contenant les valeurs d'un bouton Pop-up) et leur valeur est uniquement déterminée "par défaut" dans le programme donc je pense que c'etait pour ça qu'on ne voyait rien.
Peut-être qu'il n'y a finalement pas d'autres solutions que celle de Qinoa pour faire ces préférences arborescentes...
Qu'en pensez-vous ?
En clair, les bindings sont faits pour éviter les problèmes simples. Pour les problèmes plus compliqués rien ne vaut quelques lignes de code, en l'occurrence l'utilisation d'un PreferenceController pour le nib des préférences.
Â
Créer des object controller intermédiaires et cocher "Handles Content As Compound Values"