[Résolu] Se servir d'un nib comme un "template"

berfisberfis Membre
mai 2013 modifié dans API AppKit #1

Bonjour,


 


Oui, je sais, un nib EST un template, mais j'ai envie d'ajouter à  la fenêtre de mon application des sous-vues prédéfinies (avec des contrôles dessus) à  partir d'un modèle stocké dans une nib. Ma question est:


 


Dois-je créer un contrôleur pour chacune de ces customViews? Le garder? Avoir un ArrayController de ViewControllers? Ou puis-je ajouter la vue à  la subviews de ma window et la gérer via cette array?


 


En fait, ce que j'imagine faire, ce serait "d'éclater" l'équivalent d'une tableView gérée par un ArrayController en une série de subviews dans une fenêtre, avec possibilité de gestion par un ArrayController.


 


Ou encore, de reproduire l'équivalent de ce que Core Data fait avec des entités via une tableView et un contrôleur dédié, mais avec des NSViews disposées arbitrairement à  la place. Un peu comme une CollectionView, mais pas aussi rigide.


 


Vais-je droit dans le mur ou au contraire réinventai-je la poudre?


 


 


Réponses

  • AliGatorAliGator Membre, Modérateur

    C'est tout à  fait possible et se fait sans soucis.


    Il suffit de charger le XIB par code (comme c'est proposé par exemple dans le Table View Programming Guide pour charger des cellules customisées via un XIB par exemple), pas forcément besoin d'un ViewController pour ça.


     


    Par exemple avec [[NSBundle mainBundle] loadNibNamed:owner:options:] soit en mettant un owner nil et en récupérant le NSArray des topLevelObjects pour pouvoir accéder à  la vue qui t'intéresse dans ton XIB, soit en mettant un owner à  self et en prévoyant un IBOutlet dans ton objet courant self (en général ton UIViewController courant) pour qu'au chargement du XIB cet IBOutlet pointe vers l'objet qui t'intéresse dans ton XIB, et du coup après tu le récupères pour en faire ce que tu veux et tu remets ton IBOutlet à  nil.


     


    Note que tu peux tout à  fait charger plusieurs XIB avec le même UIViewController comme File's Owner. Si dans un XIB 1 ton File's Owner a les IBOutlets a,b et c de connectés mais pas d et e, et quand dans le XIB 2 le File's Owner a l'inverse a les IBOutlets d et e de connectés mais pas a,b,c alors au chargement du premier XIB ça va créer tous les objets dans ce premier XIB et connecter a,b,c et au chargement du 2e XIB ça va créer tous les objets déclarés dans ce 2e XIB et connecter les outlets d et e, sans toucher aux outlets a,b,c qui pointeront toujours vers les éléments chargés au chargement du premier XIB... Donc y'a pas de soucis pour charger plusieurs XIB avec le même VC au final, ni pour charger plusieurs fois le même XIB avec un même VC en File's Owner du moment qu'entre chaque chargement tu penses à  mettre de côté l'objet qui a été créé et connecté à  l'IBOutlet pour pas le perdre lors du chargement suivant qui en créera un autre.


  • berfisberfis Membre
    avril 2013 modifié #3

    Merci pour cette réponse détaillée!


     

    mettre de côté l'objet


    J'aime bien l'expression  ^_^ mais concrètement je fais quoi avec? une copie? Et les contrôles qui sont dessus?


     


     


    Si je veux insérer 10 vues dans ma fenêtre, je fais ça?



    for (int i = 0; i<10; i++) {
    MyController *controller = [[MyController alloc] initWithNibName:@ExternalView bundle:nil];
    [window addSubview:[controller view]];
  • AliGatorAliGator Membre, Modérateur

    Concrètement tu la mets juste dans une variable, ou dans un NSArray, puisqu'à  l'appel suivant de "loadNibNamed:owner:" le contenu de cette variable / de cet IBOutlet sera remplacé par la nouvelle instance nouvellement créé par ce 2e appel, et que si tu n'as pas affecté la première instance (récupérée via ton IBOutlet) à  une autre variable tu ne pourras plus y faire référence.


     


    Je t'invite à  lire la doc Apple ici sur les différentes étapes lors du chargement d'un NIB. En plus tu as un paragraphe dédié qui explique comment charger des NIB programmatiquement.


  • AliGatorAliGator Membre, Modérateur

    Si je veux insérer 10 vues dans ma fenêtre, je fais ça?



    for (int i = 0; i<10; i++) {
    MyController *controller = [[MyController alloc] initWithNibName:@ExternalView bundle:nil];
    [window addSubview:[controller view]];

    Heu non, car là  tu vas instancier 10 MyController... enfin tu peux, mais ça veut dire que pour chaque ExternalView tu vas instancier un MyController pour chacune... tout ça pour récupérer sa propriété "view" pour l'ajouter en SubView, et après tu ne fais plus rien du MyController et le laisse disparaà®tre... Créer autant de MyController pour rien c'est un peu dommage.


     


    Non, utilise plutôt les méthodes que je t'ai indiquées dans mes divers messages plus haut (je t'ai donné le nom et tout, je peux pas faire bcp mieux) et ce qui est expliqué dans la doc Apple (cf leurs exemples également).

  • Il ya quelques temps déjà , j'avais recherché quel était le moyen le plus simple de dupliquer une vue, il semble que cela soit d'utiliser NSArchiver:


     



    NSData * viewData = [NSKeyedArchiver archivedDataWithRootObject:iViewPrototype];
    LSView * viewCopy = [NSKeyedUnarchiver unarchiveObjectWithData:viewData];

     


    iViewPrototype est donc la vue à  dupliquer, que j'ajoute à  la fenêtre avec addSubView sur le contentView.


    Pour retrouver les contrôles contenus dans cette vue, j'utilise des tags.


  • @mpergand : en fait, ça crée une sorte de "nib virtuel" (je ne sais pas si les nib sont archivés de cette manière mais ça ne doit pas être différent). Je me souviens d'avoir aussi utilisé la recherche d'une vue par son tag, mais j'avais oublié cette astuce.


     


    @AliGator: je vais (re)suivre ces liens et tenter d'y voir plus clair. Ca finira par venir, comme le reste... Et merci du rappel: un pointeur sur un objet incrémente son compteur de vie!


     


    Si on ajoute à  ça la création de la vue par codage (sans nib), ça nous fait trois possibilités. La vita e bella!


     


    Bonne soirée à  vous deux et merci!


  • colas_colas_ Membre
    avril 2013 modifié #8

    Bonsoir !


     


    J'ai été confronté à  la même question que toi il y a quelques jours et j'ai opté pour un NSViewController pour chaque sous-vue. Toutes mes vues étaient différentes et j'avais besoin de les contrôler.


     


    Ta méthode 


     


     



    for (int i = 0; i<10; i++) {
    MyController *controller = [[MyController alloc] initWithNibName:@ExternalView bundle:nil];
    [window addSubview:[controller view]];

    fonctionne très bien.


     


     


    @AliGator : très élégant cette méthode de plusieurs Outlets suivant les chargements de xib. L'as-tu testée ?


    Ce serait parfait si l'on disposait d'une méthode (mais ça doit se coder) :


     


    loadViewFromNIB:withOwner


     


    Bon code !


  • AliGatorAliGator Membre, Modérateur

    Utiliser des ViewControllers dédiés pour les sous-vues, ça a du sens si les sous-vues en question sont assez complexes pour nécessiter/justifier un ViewController a elles toutes seules.


    Dans ce cas on utilise plutôt même le pattern de "Container View Controller", décrit dans le "View Controller Programming Guide" de la doc Apple, avec un ViewController parent qui contient des ViewControllers fils.


     


    Mais en général si c'est juste pour dupliquer des vues et les ajouter en subview, sans avoir besoin de toute une mécanique d'un ViewController dédié et séparé pour chaque instance de vue, alors c'est pas la peine et c'est instancier des objets pour rien.


     


    @colas : Oui je l'ai utilisé à  plusieurs reprises le chargement de plusieurs XIB différents avec le même ViewController en File's Owner. Le plus souvent que je l'ai utilisé, c'est évidemment dans le cas où je fais des UITableViewCell customisées, où je faisais exactement comme décrit dans le Programming Guide Apple sur les UITableView, que j'ai cité dans mon post précédent.


    Sauf que maintenant j'utilise même carrément UINib plutôt, qui permet de précharger un NIB une seule fois et de l'instancier ensuite autant de fois qu'on veut, sans avoir à  recharger (et que Cocoa ait à  reparser) le NIB à  chaque fois -- la classe UINib est faite pour ça d'ailleurs -- mais le principe est le même : soit on utilise le NSArray de topLevelObjets pour récupérer les objets qu'on veut, soit on met le UIViewController courant (dans lequel on a besoin de charger nos vues/NIB plusieurs fois) en File's Owner, on prévois un IBOutlet pour charger la vue du NIB qui nous intéresse, et on charge le NIB -- ce qui va instancier la vue, puis la configurer en fonction de ce qu'il y a dans le XIB, puis la connecter à  l'IBOutlet, et après on en fait ce qu'on veut avant de recharger le NIB une 2e, 3e, 4e fois...


     


    // Utile si on veut charger une vue une seule fois (car on précharge le NIB à  chaque fois dans cette méthode
    -(UIView*)loadFirstViewFromNibNamed:(NSString*)nibName
    {
      UINib* nib = [UINib nibWithNibName:nibName bundle:nil];
      return [self loadFirstViewFromNib:nib];
    }
     
    /* Si on veut instancier une vue d'un NIB plusieurs fois, il est préférable de créer un UINib une fois pour toutes, puis d'appeler cette méthode autant de fois que nécessaire ensuite sur ce même UINib. Ca évite de charger en mémoire le NIB de zéro à  chaque fois, c'est plus efficace */
    -(UIView*)loadFirstViewFromNib:(UINib*)nib
    {
      // Ici je montre la méthode qui récupère les TopLevelObjets plutôt que d'utiliser un File's Owner et un IBOutlet
      // La méthode où on utilise self comme File's Owner avec un IBOutlet pour récupérer la vue qui nous intéresse marche aussi
      // Mais l'exemple est déjà  dans la doc et les Programming Guides donc suffit d'aller la lire
     
      NSArray* topLevelObjects = [nib instantiateWithOwner:nil options:0]
      NSAssert([topLevelObjects count]>0], @No top level object found in NIB);
      NSAssert([topLevelObjects[0] isKindOfClass:[UIView class]], @Expected UIView as first topLevelObject of your NIB);
      return (UIView*)(topLevelObjects[0]);
    }

  • Comme on parle d'osx ici, c'est NSNib qu'il faut utiliser, ex:



    -(void) addProcessViewWithProcess:(Process*) process
    {
    NSNib* viewNib=[[NSNib alloc] initWithNibNamed:@ProcessView bundle:nil];
    NSArray* objects;

    if([viewNib instantiateNibWithOwner:nil topLevelObjects:&objects])
    {
    // blabla
    }

    [viewNib release];
    }

  • @AliGator :


    Avec ta méthode sans NSViewController, comment gérerais-tu le cas suivant ?


     


    classe A (typiquement NSDocument) charge une sous-vue X.


    Mais, des éléments de la sous-vue X doivent être contrôlés par un contrôleur (un NSArrayController) qui appartient à  la classe A.


     


    La solution que je vois :


    -> donner une property à  ce NSArrayController (myController)


    -> dans la sous-view X, dans les bindings, choisir : File's Owner puis faire un chemin genre : myController.canSelectNext (par exemple)


     


    J'ai cru remarqué que quand les controllers étaient accédés via des property dans les bindings, et non directement (dans le menu, genre : ArrayController, etc.), des fois, des fonctions ne marchent pas. ça te dit quelque chose cette situation ?


     


    Merci


  • AliGatorAliGator Membre, Modérateur

    Je vois pas trop le rapport entre ta question du dernier post et la question d'origine...


    Le fait de charger plusieurs XIB avec un même ViewController en File's Owner, pour que le XIB 1 crée des vies et connecte certains IBOutlets et que le XIB 2 crée d'autres vues et connecte à  d'autres IBOutlets, j'ai du mal à  voir en quoi ça impacterait les bindings ?


  • Je me permets une réponse plus simple.


    On peut tout à  fait copier via le finder un .xib d'un dossier à  l'autre. "Ya + ka" l'ajouter dans le projet de destination et à  l'utiliser.


    Je l'ai fait des 10zaines de fois, c'est super!!


     


    J'ai comme cela des code sources qui passent d'un projet à  l'autre, et lorsque je fais des tests avec des petits projets, je les intègre dans le grand avec de vulgaires PommeC/PommeV...


     


    Bon, ça fait bricolage par rapport à  vos solutions (ça fait presque Windows...) mais...  ::)


  • AliGatorAliGator Membre, Modérateur

    Hello @Herve


     


    J'avoue que je ne vois pas trop le rapport entre ta réponse et la question d'origine ?


     


     - Tu parles de "réutiliser un XIB d'un projet dans un autre projet" (et ta solution est le copier/coller)


     - La question tourne plutôt sur l'aspect "sur un même projet, réinstancier plusieurs fois le même XIB" ou plus exactement "pouvoir mettre juste une UIView dans un XIB et l'instancier autant de fois qu'on veut pour faire plein d'instances de ces vues sur le même modèle"


     


    Ceci dit bien sûr pour réutiliser un XIB d'un projet à  un autre, bien sûr qu'un copier/coller fonctionne, pareil que pour un .h/.m ou tout le reste. Mais je conseillerai pour cette question là  (qui n'est pas la question d'origine mais bon) de faire un minimum de suivi de version (un simple GIT local suffit, Xcode peut même le générer pour toi), ça t'évitera si tu corriges un bug sur ton XIB dans un projet par exemple d'avoir à  reporter le correctif partout...


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