[Résolu] UIPickerView - modulation des data

acrobatacrobat Membre
novembre 2014 modifié dans API UIKit #1

Bonjour.


 


Je voudrais savoir si cela est possible de faire varier l'affichage du composant à  l'index 1 en fonction du choix fait sur le composant d'index 0.


 


Par exemple, sur l'index 0 j'ai une marque d'appareil photo et sur l'index 1 les différents modèles de cette marque.


 


L'idée que j'avais en tête était de créer le pickerData avec un NSMutableArray (index 0 un Array et en 1 un NSMutableArray).


Ce NSMutableArray est initialisé avec les modèles d'une marque.


 


Ensuite dans la méthode numberOfRowsInComponent je fait un removeAllObjects pour remettre le tableau à  vide et je recharge les données dans le NSMutableArray du component 1 en fonction du choix fait sur l'index 0.


 


-(NSInteger) pickerView:(UIPickerView *)thePickerView numberOfRowsInComponent:(NSInteger)component


{


    // retourne le nombre de lignes dans chaque composant


    if (thePickerView ==monPickerApn) {                     // Picker Marques & modèles


        


        switch ([[pickerDataApn objectAtIndex:0]row]) {


            case 0:


                [arrayModeleSelonMarqueChoisie removeAllObjects];


                arrayModeleSelonMarqueChoisie = [[NSMutableArray alloc] initWithArray:arrayListeCanon];


                return [arrayModeleSelonMarqueChoisie count];


                


            case 1:


                [arrayModeleSelonMarqueChoisie removeAllObjects];


                arrayModeleSelonMarqueChoisie = [[NSMutableArray alloc] initWithArray:arrayListeCasio];


                return [arrayModeleSelonMarqueChoisie count];


...


 


 


Mais j'obtiens ce message d'erreur :


-[__NSArrayI row]: unrecognized selector sent to instance 0x8c9d600


 


*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[__NSArrayI row]: unrecognized selector sent to instance 0x8c9d600'


 

 

Merci par avance pour vos réponses  ::)

 


 


 


 


«1

Réponses

  • jpimbertjpimbert Membre
    octobre 2014 modifié #2

    Oui ben sûr c'est possible. Ta démarche est un peu compliquée, mais l'erreur n'a rien à  voir avec cette démarche.

     

    Ce n'est pas 



    switch ([[pickerDataApn objectAtIndex:0]row])

    Mais



    switch ([pickerDataApn objectAtIndex:0][row])

  • Merci pour ta réponse  ::)


    Cependant en portant ta correction j'ai ce message d'erreur :


    Use of undeclared identifier 'row'.


     


    Sinon, tu sais où je pourrais trouver une démarche plus simple ?

  • AliGatorAliGator Membre, Modérateur
    La démarche ou plutôt l'implémentation de cette dernière est très bizarre.

    En tout cas, je pense qu'il y a une grosse incompréhension du pattern "DataSource" de ta part. Affecter les tableaux utilisés par ton DataSource dans une méthode même du DataSource lui-même, y'a comme un problème... Ce n'est clairement pas comme ça qu'il faut s'y prendre.
    Il faut plutôt affecter ta @property contenant les modèles de la marque courante quand l'utilisateur tourne la première roue du Picker. Ce que tu vas détecter non pas avec le DataSource, mais avec une méthode de Delegate du Picker, qu'il faut que tu implémentes. Ensuite, une fois que tu auras changé le tableau de marques à  utiliser dans cette méthode, il faudra faire un reloadData sur le picker pour qu'il recharges les données de son 2eme composant en utilisant ce nouveau tableau de marques (tant pour le "count" que pour le "titleForRow")

    Sinon, dans les notes à  part :
    - tu fais un removeAllObject sur ton NSMutableArray... pour finalement remplacer le contenu de la variable par un tout autre objet (tu ne remplis pas ton NSMutableArray après l'avoir vidé, tu le remplaces par une toute autre nouvelle instance qui n'a rien à  voir). Aucun intérêt d'avoir un NSMutableArray dans ces cas-là . Il y a clairement une incompréhension du principe d'allocations mémoire et d'instanciation des objets

    - Tu appelles la méthode "row" sur un objet qui est un NSArray. Or la classe NSArray n'a pas de méthode d'instance qui s'appelle "row". C'est normal que ça plante, c'est ce que te dis le message d'erreur lors de l'exception. Je ne sais pas vraiment ce que tu comptais faire, mais clairement vu ton code c'est normal que ça crash.



    Je t'invite sinon au passage à  aller te présenter dans la section "Présentation des membres". On en saura ainsi mieux sur ton niveau, si tu fais de l'ObjC depuis longtemps, si tu as de l'expérience en POO, quelle est ta formation, etc... pour adapter nos réponses à  ton niveau en conséquence.
  • jpimbertjpimbert Membre
    octobre 2014 modifié #5

    Merci pour ta réponse  ::)

    Cependant en portant ta correction j'ai ce message d'erreur :

    Use of undeclared identifier 'row'.

     

    Sinon, tu sais où je pourrais trouver une démarche plus simple ?



     

    Une démarche plus simple mais pas encore tout à  fait plus académique :



    -(NSInteger) pickerView:(UIPickerView *)thePickerView numberOfRowsInComponent:(NSInteger)component
    {
    // retourne le nombre de lignes dans chaque composant
    if (thePickerView ==monPickerApn) { // Picker Marques & modèles
    switch (component) {
    case 0:
    return nbMarques; // à  remplacer par un truc qui donne le nombre de marques
    case 1:
    switch ([pickerDataApn selectedRowInComponent:0]) {
    case 0:
    return [arrayListeCanon count];

    case 1:
    return [arrayListeCasio count];
    ...

    Pour être plus académique il te faudrait une partie "Modèle" mieux structurée. Par exemple avec un dictionnaire de tableaux, la clé de dictionnaire étant la marque :



    NSArray *marques = @[; @Canon, @Casio ];
    NSDictionary *myModel = @{
    @Canon : arrayListeCanon,
    @Casio : arrayListeCasio
    };

    Le code de ta méthode de dataSource deviendrait alors :



    -(NSInteger) pickerView:(UIPickerView *)thePickerView numberOfRowsInComponent:(NSInteger)component
    {
    // retourne le nombre de lignes dans chaque composant
    if (thePickerView ==monPickerApn) { // Picker Marques & modèles
    switch (component) {
    case 0:
    return [myModel count];
    case 1:
    return [myModel[marques[pickerDataApn selectedRowInComponent:0]] count];
    }
    ...

    Si quelque chose te parait mystérieux dans ces lignes de code, il est urgent que tu étudies les patterns et framework Cocoa Touch avant d'aller plus loin.


  • Merci à  vous deux pour vos réponses. Elles m'ont permis de mieux appréhender la problématique/


    Je vais en tenir compte et remanier mon code en fonction.

  • Les conseils reçus m'ont permis de mettre à  jour correctement le component index 1 en fonction du choix fait dans l'index 0.


    Encore merci.


     


    J'aurais 2 questions :


    - Dans mon code à  quel niveau dois-je insérer mes calculs. Est-ce aberrant si je les code dans la méthode "didSelectRow" ?


    - Une fois que le User à  choisi son appareil photo dans le PickerView, comment conserver ces données pour la prochaine session ?



  • - Une fois que le User à  choisi son appareil photo dans le PickerView, comment conserver ces données pour la prochaine session ?




     


    NSUserDefaults

  • oki 


  • Après 2 jours de lecture et de recherche sur ce sujet, je ne vois pas comment intégrer cela dans mon code.


    D'après ce que j'ai compris il faut que je l'insère dans la méthode didSelectRow.


     


    Dans mon pickerView component index 0 = Marque et index 1 = Modèle, j'ai inséré un switch pour chacun des 2 components, et je suis un peu perdu sur la zone ou je dois insérer le NSUserDefaults 


     


    NSInteger selectedRowMarque = [monPickerApn selectedRowInComponent:0];
    [[NSUserDefaults standardUserDefaults] setInteger:selectedRowMarque forKey:@pickerMarque];


     


    NSInteger selectedRowModele = [monPickerApn selectedRowInComponent:1];
    [[NSUserDefaults standardUserDefaults] setInteger:selectedRowModele forKey:@pickerModele];

  • Il faut faire un [[NSUserDefaults standardUserDefaults] synchronise] pour sauvegarder la valeur. (si c'était ca le soucis)


  • Merci pour ta réponse, j'ai ajouté la ligne que j'avais omise ;-)


        NSNumber *selectedRowMarque = [NSNumber numberWithDouble:[monPickerApn selectedRowInComponent:0]];


        [[NSUserDefaults standardUserDefaults] setObject:selectedRowMarque forKey:@pickerMarque];


        [[NSUserDefaults standardUserDefaults] synchronize];


        NSLog(@Affichage defaults %@ (Marque)",[[NSUserDefaults standardUserDefaults] objectForKey:@pickerMarque]);


     


    Mais mon appli plante


    Je la lance, je sélectionne une marque. Je quitte l'appui. Et la relance et là  boum, plus de son, plus d'image. et aucun NSLog sur la console.


    Dans le lldb aucun message.


    Sur mon iphone, l'appli est totalement figé.


     


    Je pige pas,  >:(


  • La réponse à  mon appli plante ne va pas être facile à  trouver. Essais de mettre un point d'arrêt pour les exceptions et sinon tu fais du debug pas à  pas tu met un point d'arrêt dans toutes tes méthodes appelé au démarrage et tu vois dans laquelle ça plante. Comme ça tu auras une idée plus précise du problème et tu pourras si tu as besoin revenir avec une question plus précise qui nous permettra de t'aider.


     


    Bon courage la partie debug est assez passionnante je trouve mais en même temps pas mal frustrante.




  • Bon courage la partie debug est assez passionnante je trouve mais en même temps pas mal frustrante.




     


    Personnellement je trouve ça très fastidieux et pas très productif. J'évite au maximum le debug au bénéfice des tests unitaires.


     




    Merci pour ta réponse, j'ai ajouté la ligne que j'avais omise ;-)


        NSNumber *selectedRowMarque = [NSNumber numberWithDouble:[monPickerApn selectedRowInComponent:0]];


        [[NSUserDefaults standardUserDefaults] setObject:selectedRowMarque forKey:@pickerMarque];


        [[NSUserDefaults standardUserDefaults] synchronize];


        NSLog(@Affichage defaults %@ (Marque)",[[NSUserDefaults standardUserDefaults] objectForKey:@pickerMarque]);


     


    Mais mon appli plante


    Je la lance, je sélectionne une marque. Je quitte l'appui. Et la relance et là  boum, plus de son, plus d'image. et aucun NSLog sur la console.


    Dans le lldb aucun message.


    Sur mon iphone, l'appli est totalement figé.


     


    Je pige pas,  >:(




     


    Es-tu sûr de bien initialiser ton application avec les défauts utilisateurs à  son lancement et à  son retour au premier plan ?

  • Joanna CarterJoanna Carter Membre, Modérateur
    octobre 2014 modifié #15


    Merci pour ta réponse, j'ai ajouté la ligne que j'avais omise ;-)


        NSNumber *selectedRowMarque = [NSNumber numberWithDouble:[monPickerApn selectedRowInComponent:0]];


        [[NSUserDefaults standardUserDefaults] setObject:selectedRowMarque forKey:@pickerMarque];


        [[NSUserDefaults standardUserDefaults] synchronize];


        NSLog(@Affichage defaults %@ (Marque)",[[NSUserDefaults standardUserDefaults] objectForKey:@pickerMarque]);


     


    Mais mon appli plante


    Je la lance, je sélectionne une marque. Je quitte l'appui. Et la relance et là  boum, plus de son, plus d'image. et aucun NSLog sur la console.


    Dans le lldb aucun message.


    Sur mon iphone, l'appli est totalement figé.


     


    Je pige pas,  >:(




     


    Si ton appli plante au redémarrage, tu devrais nous montrer le code qui récupère les valeurs du NSUserDefaults.


     


    En plus, tu as sauvegardé le row, qui est un NSinteger, dans un NSNumber comme double. Il vaut mieux de faire :



    NSNumber *selectedRowMarque = [NSNumber numberWithInteger:[monPickerApn selectedRowInComponent:0]];

    [[NSUserDefaults standardUserDefaults] setObject:selectedRowMarque forKey:@pickerMarque];

    Ou, en utilisant Objective-C 2.0 :



    NSNumber *selectedRowMarque = @([;monPickerApn selectedRowInComponent:0]);

    [[NSUserDefaults standardUserDefaults] setObject:selectedRowMarque forKey:@pickerMarque];

    Ou, encore mieux :



    [[NSUserDefaults standardUserDefaults] setInteger:[monPickerApn selectedRowInComponent:0] forKey:@pickerMarque];



  • Personnellement je trouve ça très fastidieux et pas très productif. J'évite au maximum le debug au bénéfice des tests unitaires.


     


     


    Es-tu sûr de bien initialiser ton application avec les défauts utilisateurs à  son lancement et à  son retour au premier plan ?




     


    L'initialisation me pose problème. Je ne sais pas ou incorporer le code.


    Selon moi, je devrais le mettre dans le viewForRow mais je ne parviens pas à  comprendre à  quelle endroit je dois l'insérer.


     


    Mon code initiale est le suivant :


    -(UIView *)pickerView:(UIPickerView *)thePickerView viewForRow:(NSInteger)row forComponent:(NSInteger)component reusingView:(UIView *)view {


        UILabel *pickerViewLabel = (id)view;


        


        // Gestion affichage du 2ème picker (diaphrame, focale, metre et centimetre)


        if (thePickerView == monPicker) {


            


            if (!pickerViewLabel) {


                pickerViewLabel = [[UILabel alloc] initWithFrame:CGRectMake(0.0f, 0.0f, [thePickerView rowSizeForComponent:component].width - 10.0f, [thePickerView rowSizeForComponent:component].height-5.0f)];


            }


            


            //pickerViewLabel.backgroundColor = [[UIColor alloc]initWithRed:2.0 green:0.5 blue:0.0 alpha:0.5];


            pickerViewLabel.text = [[pickerData objectAtIndex:component] objectAtIndex:row];


            return pickerViewLabel;


        }


        


        // Gestion affichage du 1er picker (Marque et modèle)


        if (thePickerView == monPickerApn) {


            


            if (!pickerViewLabel) {


                pickerViewLabel = [[UILabel alloc] initWithFrame:CGRectMake(0.0f, 0.0f, [thePickerView rowSizeForComponent:component].width - 10.0f, [thePickerView rowSizeForComponent:component].height-5.0f)];


            }        


            if (component==0) {


                pickerViewLabel.text = [[pickerDataApn objectAtIndex:component] objectAtIndex:row];


                [monPickerApn reloadComponent:1];


            }


            


            else   // Liste des modèles par marques - Le choix de cette liste dépend de la marque sélectionnée


            {          


                pickerViewLabel.textAlignment = NSTextAlignmentCenter;


                pickerViewLabel.text = [arrayModeleSelonMarqueChoisie objectAtIndex:row];


                }


        }


        return pickerViewLabel;




  •  


    Si ton appli plante au redémarrage, tu devrais nous montrer le code qui récupère les valeurs du NSUserDefaults.


     


    En plus, tu as sauvegardé le row, qui est un NSinteger, dans un NSNumber comme double. Il vaut mieux de faire :



    NSNumber *selectedRowMarque = [NSNumber numberWithInteger:[monPickerApn selectedRowInComponent:0]];

    [[NSUserDefaults standardUserDefaults] setObject:selectedRowMarque forKey:@pickerMarque];

    Ou, en utilisant Objective-C 2.0 :



    NSNumber *selectedRowMarque = @([;monPickerApn selectedRowInComponent:0]);

    [[NSUserDefaults standardUserDefaults] setObject:selectedRowMarque forKey:@pickerMarque];

    Ou, encore mieux :



    [[NSUserDefaults standardUserDefaults] setInteger:[monPickerApn selectedRowInComponent:0] forKey:@pickerMarque];



     


    Merci pour cette remarque, je me suis encore égarer dans les formats, screugneugneu !!

  • Petit up.

    Y aurait il une bonne ame qui pourrait m'expliquer à  quel endroit du code je dois insérer l'instruction qui permet de charger les donnes stockées dans le NSUserDefaults ?
  • Joanna CarterJoanna Carter Membre, Modérateur

    Les données sont disponibles depuis la méthode [NSUserDefaults standardUserDefaults] à  n'importe où dans ton code




  • Petit up.

    Y aurait il une bonne ame qui pourrait m'expliquer à  quel endroit du code je dois insérer l'instruction qui permet de charger les donnes stockées dans le NSUserDefaults ?




     


    Tu n'as pas de réponse car ce n'est pas une "bonne" question, ou du moins les questions de codage ne sont pas les premières à  se poser.


    La première question concerne le Design de ton application : quel est le comportement que tu souhaites ?


    Plus concrètement, veux tu lire ces User Defaults lors d'un changement d'état de l'application (retour au premier plan, lancement, ...) ou lorsqu'une vue particulière s'affiche ?


    Choisis le comportement de ton application, ensuite reformule ta question ; tu auras très vraisemblablement des réponses.

  • Mon appli est destinée au photographe, elle à  vocation à  calculer l'hyperfocale et la profondeur de champ en fonction de son modèle d'appareil photo et les réglages de celui-ci.


     


    Il n'y a qu'une seule vue pour laquelle j'ai crée une classe et un contrôleur qui charge cette vue au démarrage.


     


    Dans la vue, j'ai une partie qui affiche le résultat calculé.


    Dans l'autre partie j'ai 2 pickerView.


    Le premier permet de selectionner une marque et un modèle d'appareil photo.


    Le deuxième sert à  collecter des données concernant le réglages de la prise de vue.


     


    Je voudrais que le choix du 1er picker (Marque et modèle) soit conservée et reprise à  la réouverture de l'appli.


    En effet, la totalité des modèles stockée est d'environ 1500 appareils, et à  force c'est lourd pour l'utilisateur de devoir rechercher à  chaque utilisation son appareil dans la liste.


     


    A ce stade de mon développement, j'ai mémorisé dans le code de la méthode didSelectRow la selection du 1er picker.


     


    Mon problème à  présent est de connaitre à  quel moment je dois insérer la récupération du choix fait par l'utilisateur la dernière fois qu'il a utilisé l'appli ?

  • dans le cellforrow at indexpath


  • AliGatorAliGator Membre, Modérateur
    Bah non, dans le didSelect, pas dans le cellForRow !

    Le cellForRowAtIndexPath il est appelé par le dataSource savoir quoi afficher dans les cellules. Ca n'a rien à  voir avec le fait que l'utilisateur ait sélectionné ou non ladite cellule / ligne du picker.


  • Mon problème à  présent est de connaitre à  quel moment je dois insérer la récupération du choix fait par l'utilisateur la dernière fois qu'il a utilisé l'appli ?




     


    Tu as une seule vue et un seul contrôleur de vue.


     


    Le plus simple est donc de récupérer les User Defaults stockés lors d'une exécution préalable de ton application dans la méthode viewDidLoad du contrôleur de vue.



  • Bah non, dans le didSelect, pas dans le cellForRow !


    Le cellForRowAtIndexPath il est appelé par le dataSource savoir quoi afficher dans les cellules. Ca n'a rien à  voir avec le fait que l'utilisateur ait sélectionné ou non ladite cellule / ligne du picker.




     


    Il voulait pas savoir comment utiliser les users default lorsque l'utilisateur relance son app, pour avoir les infos affichées comme elles ont été sauvegardée ?

  • AliGatorAliGator Membre, Modérateur

    Il voulait pas savoir comment utiliser les users default lorsque l'utilisateur relance son app, pour avoir les infos affichées comme elles ont été sauvegardée ?

    Ah pardon tu répondais à  cette question, désolé je suis allé un peu vite. Mais ça n'empêche que ce n'est pas non plus la bonne solution ;)

    cellForRowAtIndexPath & consoeurs sont des méthodes de dataSource. Qui potentiellement sont appelés régulièrement voire plusieurs fois quand tu scrolles (ou fais tourner la roue du picker, dans ce cas).

    Il faut charger les données dans le modèle (charger le contenu des NSUserDefaults indiquant la marque et le modèle initialement sélectionnés) lorsque la vue est chargée (viewDidLoad) ou à  des endroits comme ça, dans du code exécuté quand la vue (et donc le picker) va s'afficher, et pas à  chaque fois qu'il appelle les méthodes de DataSource, sinon tu vas lire 36 fois la valeur pour rien, à  la fois quand le picker va charger ses premières rows, mais aussi quand l'utilisateur va faire tourner les molettes/roues du picker pour afficher les valeurs suivantes ! Alors qu'on ne veut que récupérer la valeur initiale (celle à  sélectionner initialement dans le picker lorsqu'on l'affiche) via les NSUserDefauts.
  • Oui tu as tout à  fait raison (disons que je donnais la solution la plus rapide, pas la plus propre :) )


  • Merci pour ces précisions. Je continue à  chercher.


    Sinon, en passant c'est pas il, c'est elle.


  • Sinon, en passant c'est pas il, c'est elle.




     


    Tout fout le camp !


     


     


     


     


     


     


    >:)  c'est bon, je sors ...

  • Ouais je sais c'est moche, lol
  • NasatyaNasatya Membre
    octobre 2014 modifié #31

    Alors je remet en question toute ton architecture mais des fois c'est quand même bon. Tu utilises un Picker pour 1500 lignes ça me parait un peu dur pour l'utilisateur ça fait des scroll long pour aller vers le fond et c'est pas forcement simple de récupérer la bonne ligne. 


     


    Pourquoi tu créer pas une recherche avec un UISearchBar qui apparaitrai ou pas suivant la demande ça permettrais à  l'utilisateur de faire une recherche sur l'élément voulu et à  ne pas avoir a scroller comme un fou.


     


    Enfin ça c'est ton choix c'est juste que ça pourrait éventuellement (c'est pas vraiment sur suivant ce que tu veux) améliorer un peu l'érgonomie. 


     


    Pour être honnête ça entraine beaucoup de modification et ça ne correspondra pas forcement ça ce que tu veux c'est plus une proposition.


     


    Pour le bug jpimber te conseil le viewDidLoad je suis d'accord avec lui as tu essayé?


     


    Cependant si tu veux que ça se fasse à  chaque "ouverture" de l'application le viewDidLoad n'est pas forcement la meilleur solution. En effet cette fonction ne sera pas appelé si l'application passe en background et revient en foreground sans être "killé" .


     


    Dans l'appDelegate tu as des fonctions pour notifier que ton application sors de son état de "sommeil" tu peux aussi plancher sur ça.


    Une fonction comme le "applicationDidBecomeActive" peut être une bonne solution je penses.


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