UIButton et UIImage

Bonjour



J'ai une petite question en rapport avec les images pour les UIButton :



Je souhaite qu'a chaque fois que j'appuie sur un bouton quelconque, l'image de "bouton1" change. J'utilise donc ce morceau de code :
<br />
UIImage *imageBouton1 = [UIImage imageNamed:@&quot;Image1&quot;];<br />
	[bouton1 setImage:imageBouton1 forState:UIControlStateNormal];<br />




Le problème est que je n'ai pas qu'un mais une dizaine de boutons.



J'aimerai savoir si il est possible de réduire le code de façon à  se que je ne marque pas dix fois ce bout de code. Est-ce possible ?



Merci ! image/wink.png' class='bbc_emoticon' alt=';)' />
Mots clés:
«1

Réponses

  • Il y a plusieurs moyens, cela dépend du contexte. Par exemple sous-classer UIButton, ou définir une catégorie.

    En tout cas c'est une excellente idée de ne pas vouloir multiplier le même bout de code ; Martin Fowler n'a pas travaillé pour rien image/implore.gif' class='bbc_emoticon' alt=' o:) ' />
  • 'jpimbert' a écrit:


    Il y a plusieurs moyens, cela dépend du contexte. Par exemple sous-classer UIButton, ou définir une catégorie.


    C'est à  dire ? Aurais-tu un exemple pour illustrer ?



    Merci ! image/smile.png' class='bbc_emoticon' alt=':)' />
  • Ben ... cela dépend du contexte.

    Combien de contrôleurs ? Est-ce que ces contrôleurs ont des comportements communs ?
  • En fait j'ai une classe nommée ViewController reliée à  ma UIViewController.



    Comme je ne suis pas sûr que je donne vraiment la bonne réponse à  ta question, je te donne mon projet test. Il y a seulement 2 boutons mais c'est juste pour te montrer comment je code pour changer des images.
  • jpimbertjpimbert Membre
    août 2012 modifié #6
    Je n'ai pas pu voir grand chose dans le Zip car il n'y a que le projet Xcode, sans les codes source.

    Tu réponds en partie à  ma question ; tous les boutons sont manipulés par le même contrôleur de vue ; ça facilite les choses.



    D'abord, pour seulement 2 boutons, il ne faut pas se casser la tête à  factoriser le code ; un bon copier-coller des familles sera tout à  fait satisfaisant. À partir de 4-5 je dirais (mais c'est affaire de sensibilité personnelle) il faut siouxer.



    On va donc supposer que tu voudras un jour mettre 4 ou 5 boutons voire beaucoup plus.

    J'ai alors besoin de savoir encore 2 trucs :

    - Je suppose aussi que le contrôleur de vue définit une méthode d'action pour chaque bouton, exact ?

    - Est-ce que les méthodes d'action ne font que changer l'image où font elle autre chose aussi ?

  • Oui, je ne défini qu'une seule méthode d'action pour chaque bouton.

    Non, mes méthodes d'actions ne font que changer les images à  partir d'une valeur bollean. image/smile.png' class='bbc_emoticon' alt=':)' />
  • C'est donc le cas le plus facile, pas besoin de pattern compliqué, de classe dérivée, ...



    Il suffit que tous les boutons active la même méthode du contrôleur. Il faut utiliser la version avec le paramètre sender du prototype IBAction.

    Ensuite dans la méthode, il faut trouver un système qui permette de récupérer la bonne image en fonction de la valeur du paramètre sender (qui est une référence au bouton qui a été touché).

    Il y a plusieurs moyens pour lier une image à  un bouton :
    1. avec un dictionnaire ; les références de boutons sont les clés et les images les objets attachés
    2. en glissant l'image sous Interface Builder dans une des images du bouton pour un état non utilisé (par exemple Selected)
    3. en mentionnant le numéro de l'image correspondante dans le tag de chaque bouton
  • En fait, j'utilise déjà  les tags pour définir des actions en fonction de celui-ci. Les deux autres solutions me paraissent bien mais je ne vois pas vraiment comment elles pourraient faire une réduction dans mes lignes de code... image/huh.gif' class='bbc_emoticon' alt='???' />
  • 'Benjo'' a écrit:


    Non, mes méthodes d'actions ne font que changer les images à  partir d'une valeur bollean. image/smile.png' class='bbc_emoticon' alt=':)' />



    'Benjo'' a écrit:


    En fait, j'utilise déjà  les tags pour définir des actions en fonction de celui-ci.


    ça y est, je suis largué.
  • 'jpimbert' a écrit:


    ça y est, je suis largué.




    Il y avait forcément besoin des tags ? Sinon au lieu d'utiliser les tags je peux aussi utiliser le titre du bouton pour gérer mes actions image/smile.png' class='bbc_emoticon' alt=':)' />
  • NasatyaNasatya Membre
    août 2012 modifié #12
    Si j'ai bien compris.



    Ce que tu peux faire c'est dans le .xib si tu utilise les xib définir une image pour ton bouton en mode normal et l'autre image en mode "selected"



    Ensuite tu fais ce code


    <br />
    - (IBAction)imageBtnChange:(id)sender<br />
    {<br />
    	  UIButton *btn = (UIButton *)sender<br />
    	  btn.selected = &#33;btn.selected;<br />
    }<br />
    




    Tu relis ton action à  tous tes button du xib et ça devrait le faire.



    Ce que ça va faire c'est faire une sorte de selection c'est a dire que ton bouton aura deux états, un normal avec ton image de départ et un séléctionné avec une autre image.



    Si il faut que ça soit seulement au moment où tu cliques que l'image change alors tu peux jouer avec l'highlighted toujours dans ton .xib.



    Ceci dis cela marche aussi en code mais c'est juste horrible de doubler tous les codes pour ajouter les images selectionnées.
  • BenjoBenjo Membre
    août 2012 modifié #13
    D'accord merci ! image/smile.png' class='bbc_emoticon' alt=':)' /> Mais comment faire pour attribuer une image à  "selected" ou "highlighted" ? Parce que chaque bouton à  sa propre image et les images de chaque bouton sont différentes.
  • J'ai pas regardé son code, mais dans le cas " classique ", tu peux changer l'image, et t'as un forState:UIControlStateSelected ou quelque chose de ce genre-là ...
  • Ha oui tu as raison ! J'avais oublié le "forState". Merci ! image/smile.png' class='bbc_emoticon' alt=':)' />
  • Pour mon code :

    Dans le fichier .h


    <br />
    @property (weak, nonatomic) IBOutlet UIButton *bouton1;<br />
    @property (weak, nonatomic) IBOutlet UIButton *bouton2;<br />
    




    Dans le fichier .m


    <br />
    @implementation ViewController<br />
    @synthesize bouton1;<br />
    @synthesize bouton2;<br />
    bool changement; //Si &quot;changement est false, 1 est en haut et 2 est en bas<br />
    - (IBAction)changerLesImages:(id)sender {<br />
    	//Je déclare les images<br />
    	UIImage *image1 = [UIImage imageNamed:@&quot;Bouton1&quot;];<br />
    	UIImage *image2 = [UIImage imageNamed:@&quot;Bouton2&quot;];<br />
      <br />
    	//Ici je dit que si changement est false, je place l&#39;image &quot;2&quot; en haut et &quot;1&quot; en bas<br />
    	if (changement == false) {<br />
    		[bouton1 setImage:image2 forState:UIControlStateNormal];<br />
    		[bouton2 setImage:image1 forState:UIControlStateNormal];<br />
    		changement = true; //J&#39;ai changé les images<br />
    	}<br />
    	else {<br />
    		[bouton1 setImage:image1 forState:UIControlStateNormal];<br />
    		[bouton2 setImage:image2 forState:UIControlStateNormal];<br />
    		changement = false; //Les images sont à  leur place initiale<br />
    	}<br />
    }<br />
    




    J'ajoute également un capture d'écran pour vous faire une petite idée de l'interface.
  • NasatyaNasatya Membre
    août 2012 modifié #17
    Aà¯e aà¯e aà¯e... trop de ligne de code trop de ligne de code....



    Dans la partie encadré en rouge de mon image tu peux changer "default" pour "selected" tu peux mettre des images diffèrente pour les deux cas.



    Du coup xcode comprend que le bouton peut changer d'image sur certaine condition. ça marche pour les titre les images les background image et les couleur des textes.



    Le bouton affichera l'image défini dans default tant que "button.selected == FALSE". Elle affichera l'image définie dans selected quand "button.selected == TRUE".



    Du coup plus besoin de définir des images dans le code les boutons les ont en mémoire dans les xml généré par interface builder.

    Ensuite plus besoin non plus de changer les images des bouton il suffit juste de changer leur valeur "selected" d'où lon code de tout a l'heure


    <br />
    - (IBAction *)selectedButton:(id)sender<br />
    {<br />
       //pour coller a ton code<br />
       //Il faut par contre en définir un a false dans le viewDidLoad pour qu&#39;il soit diffèrent.<br />
       button1.selected = &#33;button1.selected;<br />
       button2.selected = &#33;button2.selected;<br />
    <br />
    }<br />
    




    Par contre pour une dizaine de bouton ton système ne pourra pas marcher.



    Enfin j'ai l'impression que pour l'instant tu essais plus ou moins de faire l'équivalent de l' "input select" de l'html pour deux boutons ton système marche bien pour plus ça va vite devenir ingérable.



    Si jamais tu te lance la dedans moi ce que je fais en général c'est une boucle sur les "subview" je regarde si j'ai a faire a un bouton je lui donne une condition pour éviter de bouger mes autres bouton genre tag > 1000 et la j'initialise tous mes boutons a selected = TRUE; puis je change celui qui a été cliqué.



    ça prend un peu plus de ligne de code par contre.



    [Hors sujet]



    BOOL changement;

    changement = FALSE;

    if(changement == FALSE)



    C'est excessivement redondant

    if(changement) suffit amplement image/wink.png' class='bbc_emoticon' alt=';)' />



    [/Hors sujet]



    buttonState.png


  • [font=helvetica, arial, sans-serif]Aà¯e aà¯e aà¯e... trop de ligne de code trop de ligne de code....[/font]




    Oui je sais et c'est bien cela qui m'embête ! image/wink.png' class='bbc_emoticon' alt=';)' />




    [font=helvetica, arial, sans-serif]Si jamais tu te lance la dedans moi ce que je fais en général c'est une boucle sur les "subview" je regarde si j'ai a faire a un bouton je lui donne une condition pour éviter de bouger mes autres bouton genre tag > 1000 et la j'initialise tous mes boutons a selected = TRUE; puis je change celui qui a été cliqué.[/font]




    Bah justement ta réponse est très bien mais comme tu dis, seulement pour deux boutons. Moi c'est pour dix... image/sad.png' class='bbc_emoticon' alt=':(' />



    Par contre j'ai pas bien compris l'explication avec la boucle... Sinon l'explication générale est très bien merci ! image/smile.png' class='bbc_emoticon' alt=':)' />




    [font=helvetica, arial, sans-serif]BOOL changement;[/font]

    [font=helvetica, arial, sans-serif]changement = FALSE;[/font]

    [font=helvetica, arial, sans-serif]if(changement == FALSE)[/font]



    [font=helvetica, arial, sans-serif]C'est excessivement redondant [/font]



    [font=helvetica, arial, sans-serif]if(changement) suffit amplement [/font] image/wink.png' class='bbc_emoticon' alt=';)' />




    Merci beaucoup du conseil ! J'aime bien qu'on me corrige aussi mon code pour qu'il soit plus court ! image/smile.png' class='bbc_emoticon' alt=':)' />
  • AliGatorAliGator Membre, Modérateur
    Pourquoi ne pas utiliser un IBOutletCollection (que tu relies à  tous tes boutons), pour boucler dessus et les remettre à  selected=NO ?



    Je suis pas sûr d'avoir tout suivi et bien compris le problème, mais si le but est bien ce que j'ai capté, à  savoir avoir N bouton sur ta vue, chacun ayant deux "états" (une image différente pour chaque état), avec un seul bouton dans l'état "sélectionné" uniquement à  chaque fois, alors c'est très simple et ne nécessite que très peu de code :

    - Placer tous les boutons dans IB, et définir par IB leur image pour l'état "normal" et l'image pour l'état "selected"

    - Créer un IBOutletCollection NSArray* buttons et relier tous les boutons à  cet IBOutletCollection dans IB

    - Créer une IBAction "buttonAction:" et relier tous les boutons à  cette même action

    - Le seul code à  écrire pour que le tout fonctionne est alors le suivant :
    -(IBAction)buttonAction:(UIButton*)sender {<br />
      for(UIButton* btn in buttons) btn.selected = NO;<br />
      sender.selected = YES;<br />
    }
    
    Et voilà  !
  • En fait ton code marche très bien mais pas dans ma situation... Moi je veux qu'un bouton sans image change les images de dix autres boutons. Ici ce n'est pas ce que ton code fait image/wink.png' class='bbc_emoticon' alt=';)' />
  • AliGatorAliGator Membre, Modérateur
    Bah ça change pas grand chose, suffit d'adapter, tu utilises toujours un IBOutletCollection connecté à  tous tes boutons avec image, et dans l'action du bouton sans image, tu boucles sur les boutons avec image et tu changes leur état avec btn.selected = !btn.selected.

    Non ?
  • NasatyaNasatya Membre
    août 2012 modifié #22
    Jusqu'à  maintenant j'ai encore jamais utilisé les IBOutletCollection. J'ai l'impression que c'est un grand tort de ma part et que je vais me mettre a me documenter la dessus. ça a l'air méchamment efficace image/wink.png' class='bbc_emoticon' alt=';)' />.



    Sinon je plussoie AliGator comme presque tout le temps (faut toujours laisser une once de doute on sait jamais) car soit tu fais une collection d'objet comme il le précise soit tu es obliger de boucler sur tous les éléments de ta view. Cela revient a dire soit tu boucle sur dix boutons rangés dans un tableau propre, soit tu boucle sur un énorme tableau qui contient tes dix bouton parmi tous les éléments. Si tu suis la logique tu comprendras que la solution d'AliGator est bien plus optimisé pour ton cas.



    Après la fonction pour changer les images sera de toute façon pas compliquer a mettre en place. Si tu veux vraiment utiliser mon code je te le donne on sait jamais mais bon personnellement j'essaierais de ne plus l'utiliser


    <br />
    -(void)reinitialiseBouton<br />
    {<br />
    	for (int i = 0; i &lt; [self.view.subview count]; ++i)<br />
    	{<br />
    		if([[self.view.subview objectAtIndex:i] isKindOfClass:[UIButton class]])<br />
    		{<br />
    			 if((UIButton *)[self.view.subview objectAtIndex:i].tag &gt; 1000)<br />
    				  (UIButton *)[self.view.subview objectAtIndex:i].selected = FALSE;<br />
    		}<br />
    	}<br />
    }<br />
    -(IBAction *)changeImageBoutonClick:(UIButton *)sender<br />
    {<br />
    	//Passe tous les boutons en deselectionné<br />
    	[self reinitialiseBouton];<br />
    	//Selectionne le bouton cliqué.<br />
    	sender.selected = TRUE;<br />
    }<br />
    




    Cela fait la même chose que la fonction d'AliGator avec plus de ligne et en étant moins optimisé.

    La condition du tag me semble inutile dans le cas où tu n'as qu'une catégorie de bouton a laquelle tu changes la propriété "selected" car les boutons ont par défaut le "selected" égale à  FALSE.
  • BenjoBenjo Membre
    août 2012 modifié #23


    [font=helvetica, arial, sans-serif]Jusqu'à  maintenant j'ai encore jamais utilisé les IBOutletCollection. J'ai l'impression que c'est un grand tort de ma part et que je vais me mettre a me documenter la dessus. ça a l'air méchamment efficace [/font] image/wink.png' class='bbc_emoticon' alt=';)' />[font=helvetica, arial, sans-serif].[/font]




    Merci à  toi Nasatya pour ta réponse très claire mais je préfère celle AliGator parce qu'il utilise les IBOutletCollection et c'est comme tu dis [font=helvetica, arial, sans-serif]méchamment efficace[/font] image/wink.png' class='bbc_emoticon' alt=';)' />




    [font=helvetica, arial, sans-serif]Bah ça change pas grand chose, suffit d'adapter, tu utilises toujours un IBOutletCollection connecté à  tous tes boutons avec image, et dans l'action du bouton sans image, tu boucles sur les boutons avec image et tu changes leur état avec btn.selected = !btn.selected.[/font]

    [font=helvetica, arial, sans-serif]Non ?[/font]




    J'ai essayé d'adapter mais je ne suis pas vraiment sûr de mon code :


    <br />
    bool changement = false;<br />
    - (IBAction)changerLesImages:(UIButton *)sender {<br />
    	if (changement) {<br />
    		for(UIButton *btn in buttons) btn.selected = YES;<br />
    		changement = true;<br />
    	}<br />
    	else {<br />
    		for(UIButton *btn in buttons) btn.selected = NO;<br />
    		changement = false;<br />
    	}<br />
    }<br />
    
  • AliGatorAliGator Membre, Modérateur
    @Nasatya : Pourquoi ne pas utiliser les boucles sur objets directement plutôt que des boucles indexées et devoir faire objectAtIndex ?

    Si tu voulais continuer à  utiliser ta solution, ce type de code serait plus efficace (PS en Objective-C on utilise YES et NO et non TRUE et FALSE) :
    -(void)reinitialiseBouton<br />
    {<br />
            for (UIView* v in self.view.subview)<br />
            {<br />
                    if([v isKindOfClass:UIButton.class] &amp;&amp; v.tag &gt; 1000)<br />
                    {<br />
                           ((UIButton *)v).selected = NO;<br />
                    }<br />
            }<br />
    }
    






    @Benjo'; : Ton code me semble bon, bien qu'il soit fortement déconseillé d'utiliser des variables globales comme tu le fais avec ta variable "changement". Il est préférable d'utiliser soit une variable d'instance, soit une variable statique interne à  la fonction.
  • D'accord merci AliGator ! image/smile.png' class='bbc_emoticon' alt=':)' /> Mon problème est enfin résolu ! image/smile.png' class='bbc_emoticon' alt=':)' />
  • NasatyaNasatya Membre
    août 2012 modifié #26
    Alors pour la boucle code de pas réveillé du matin c'est sur que ta boucle est mieux.

    Pour le booléen déjà  il me semble que les deux écriture sont correcte puis je trouve ça plus claire le vrais/faux que le oui/non et en plus c'est imposé par une coding style de mon entreprise c'est donc devenu une habitude.

    Bon ça c'est pour l'honneur du développeur image/wink.png' class='bbc_emoticon' alt=';)' />.



    Ensuite pour ton code :



    La globale effectivement j'en vois pas souvent mais ce que je comprends pas par dessus tout c'est ce que fait ta fonction.

    Je m'explique tu commence en disant

    - je met une variable a false / no

    - je rentre dans ma fonction

    - si false/no (d'ailleurs il me semble que tu as une erreur la if (changement) est l'équivalent de if(changement == true/yes) il te faudrait je pense if(!changement) dans ton cas).

    - je fais une boucle pour passer tout mes bouton a select = true;

    - je passe "changement" a true/yes

    - puis je repasse dans ma boucle je fais l'inverse.



    Tout cela n'est il pas équivalent a si mon bouton a son "selected" a true/yes je le passe a false/no si il l'a a false/no je le passe a true/yes?



    en clair je pense que ta variable "changement" dans ton cas ne sert a rien.



    juste test de changer ta fonction pour ça pour voir


    <br />
    - (IBAction)changerLesImages:(UIButton *)sender {<br />
    	  for(UIButton *btn in buttons) btn.selected = &#33;btn.selected;<br />
    }<br />
    
  • AliGatorAliGator Membre, Modérateur
    août 2012 modifié #27
    'Nasatya' a écrit:


    Alors pour la boucle code de pas réveillé du matin c'est sur que ta boucle est mieux.

    Pour le booléen déjà  il me semble que les deux écriture sont correcte puis je trouve ça plus claire le vrais/faux que le oui/non et en plus c'est imposé par une coding style de mon entreprise c'est donc devenu une habitude.

    Bon ça c'est pour l'honneur du développeur image/wink.png' class='bbc_emoticon' alt=';)' />.
    Y'en a qui vont avoir des problèmes avec le Modern Objective-C du dernier compilateur et les "Object Literals", puisque maintenant YES et NO ne sont plus équivalents à  (BOOL)1 et (BOOL)0 mais __objc_yes et __objc_no et cela a son importance pour certaines optimisations du compilateur (en particulier justement quand tu les encapsules dans des NSNumbers par la syntaxe des Object Literals) !

    Donc ce n'est pas tout à  fait équivalent pour le compilateur qui ne verra pas certaines optimisations ou ne détectera pas certains warnings si tu n'utilises pas les bons termes.


    'Nasatya' a écrit:
    Ensuite pour ton code
    Oui concernant le code de Benjo' je suis d'accord avec toi, la variable "changement" pourrait même totalement sauter et le code peut se résumer à  ceci puisque dans son cas l'état selected des boutons est normalement toujours consistant et n'est changé que par ce biais :
    - (IBAction)changerLesImages:(UIButton *)sender {<br />
        for(UIButton *btn in buttons) btn.selected = &#33;btn.selected;<br />
    }
    


  • [font=helvetica, arial, sans-serif]- si false/no (d'ailleurs il me semble que tu as une erreur la if (changement) est l'équivalent de if(changement == true/yes) il te faudrait je pense if(!changement) dans ton cas).[/font]




    Il me semble que dans les commentaires précédents, on me disait que "(changement)" était l'équivalent à  "false". image/huh.gif' class='bbc_emoticon' alt='???' />




    [font=helvetica, arial, sans-serif]Tout cela n'est il pas équivalent a si mon bouton a son "selected" a true/yes je le passe a false/no si il l'a a false/no je le passe a true/yes?[/font]



    [font=helvetica, arial, sans-serif]en clair je pense que ta variable "changement" dans ton cas ne sert a rien.[/font]




    Oui tu as raison j'aurais pu enlever ma variable merci du conseil ! image/smile.png' class='bbc_emoticon' alt=':)' />
  • Je pense que c'est moi qui t'ai induit en erreur j'ai relu les com et c'est de ma faute.



    Pour être clair

    if (changement)

    {

    //action si changement est true/yes

    }

    else

    {

    //action si changement est false/no

    }



    si tu veux l'inverse (ce qui peut être pratique des fois



    if (!changement)

    {

    //action si changement est false/no

    }

    else

    {

    //action si changement est true/yes

    }



    excuse moi pour la confusion dans mon précedent post.


  • [font=helvetica, arial, sans-serif]excuse moi pour la confusion dans mon précedent post.[/font]




    Pas de problème ! image/smile.png' class='bbc_emoticon' alt=':)' />
  • Merci à  tous ! Je vais pouvoir réduire un peu plus de 40 lignes de code ! image/smile.png' class='bbc_emoticon' alt=':)' />
Connectez-vous ou Inscrivez-vous pour répondre.