[Résolu] UIImageView/UIButton dans Custom TableViewCell (saccade)

Am_MeAm_Me Membre
septembre 2014 modifié dans API UIKit #1

Bonjour,


 


Voilà  j'ai un problème sur mon application.


Je charge des informations d'une page web (en .xml) et je rempli une UITableView (contenant une UITableViewCell custom)


 


Voilà  les information format texte se charge bien. Le problème survient au chargement des images. Quand j'ai 4 UITableViewCell à  créer ça marche bien mais au delà  genre 18 :


 


1) Ca saccade


2) Seul mes premières cell charge leur image correspondante et quand je scroll les autres "copie" mes précédentes images déjà  chargées. 


 


Je ne comprend pas pourquoi ...


Si qqun pouvait m'éclaircir.


 


Pour le moment je raisonne comme ça grossomodo (et je fait [tableView reloadData] dans la thread avec dispatch_get_main_queue).



- (UITableViewCell*)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath

Et dans cette méthode je fais le chargement de l'image de la cell.


S'il y a besoin de code il n'y a qu'a demander. Merci d'avance


«1

Réponses

  • AliGatorAliGator Membre, Modérateur
    Classique. C'est dû au mécanisme de recyclage des cellules, si tu n'en tiens pas compte dans ta logique de chargement des images et que tu fais le chargement de tes images dans cellForRowAtIndexPath, qui n'est pas le bon endroit, c'est logique que l'image mette du temps à  s'afficher ou saccade, ou que tu n'aies pas la bonne image dans la bonne cellule (si elle a été recyclée entre temps)

    On en a déjà  parlé maintes et maintes fois sur ce forum, en expliquant le pourquoi du comment, et les différentes méthodes pour traiter ce genre de cas correctement. La plus simple étant de passer par AFNetworking pour toutes tes requêtes réseau car ce framework de toute façon simplifie la vie pour pas mal de trucs de ce côté là , et d'en profiter pour utiliser la catégorie UIImageView+AFNetworking et sa méthode setImageWithURL: qui permet de prendre en compte tout ce mécanisme et de fonctionner même dans des cas de recyclage de cellule de tableview.
  • Am_MeAm_Me Membre
    septembre 2014 modifié #3

    Je m'en doute que ce problème fut mainte fois résolu mais je n'avais pas les mots-clés pour chercher le problème :/ d'ou mon post.


     


    Quand tu parles de toutes type de requête : même pour le chargement d'un XML (petit & grand) permettant la récupération de texte ?


     


    Du coup si je comprend bien ça change un peu le code de ce côté la. Car je résonne avec NSUrlSession. Du coup je dois remplacer mes NSURLSessionDataTask par des AFURLSessionManager


     


     


     


    Exemple provenant de http://code.tutsplus.com/tutorials/working-with-nsurlsession-afnetworking-20--mobile-22651



    NSString *key = @xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx;
    NSURL *URL = [NSURL URLWithString:[NSString stringWithFormat:@<span class=skimlinks-unlinked">https://api.forecast.io/forecast/%@/37.8267,-122.423</span>", key]];

    // Initialize Session Configuration
    NSURLSessionConfiguration *sessionConfiguration = [NSURLSessionConfiguration defaultSessionConfiguration];

    // Initialize Session Manager
    AFURLSessionManager *manager = [[AFURLSessionManager alloc] initWithSessionConfiguration:sessionConfiguration];

    // Configure Manager
    [manager setResponseSerializer:[AFJSONResponseSerializer serializer]];

    // Send Request
    NSURLRequest *request = [NSURLRequest requestWithURL:URL];
    [[manager dataTaskWithRequest:request completionHandler:^(NSURLResponse *response, id responseObject, NSError *error) {
    // Process Response Object
    }] resume];

  • AliGatorAliGator Membre, Modérateur
    Oui après tu peux n'utiliser que [UIImage imageWithURL:] de AFNetworking si tu ne veux pas utiliser le reste hein, c'est comme tu le sens.

    C'est vrai qu'AFNetworking était très utile à  l'époque où NSURLSession n'existait pas, facilitant bien des choses... mais qu'avec tout ce qu'apporte NSURLSession, AFN est moins "absolument indispensable" maintenant. Sauf pour 2-3 trucs sympas (multipart, cache, ...) mais bon.

    Sinon si tu ne veux pas utiliser imageWithURL: de AFNetworking tu peux juste reprendre le principe. Le mieux étant de ne pas faire le chargement des images dans cellForRowAtIndexPath:, en tout cas pas de façon bloquante et synchrone d'une part (surtout pas), et en vérifiant lorsque l'image est enfin téléchargée que l'URL à  afficher n'a pas changée entre temps surtout, du fait du mécanisme de recyclage des cellules.

    Tu cherches "image tableview" dans les forums ou sur Google tu verras tu as pléthore de messages sur le sujet (dont le fameux sample Apple dédié à  cette problématique, mais pour lequel leur approche avec des NSOperationQueue certes marche, mais commence à  dater un peu et est un peu complexe par rapport aux outils qu'on a maintenant)
  • Am_MeAm_Me Membre
    septembre 2014 modifié #5

    Oups je viens d'installer AFNetworking avec Pods donc je suis perdu la :D.


     


    DU coup tu me conseil quoi ? 


    Et je ne sais pas quoi faire dans le bloc du dessus car j'avais l'habitude de récupérer une NSDAta avec le bloc NSUrlSession la j'ai une NSURLResponse.


     


    Donc NSUrlSession peut gérer ça aussi ? 


     


    Bon je me lance dans google tout d'abord


  • Am_MeAm_Me Membre
    septembre 2014 modifié #6

    J'ai même essayé cela (dans mon bloc NSUrlSession) : 



    UIImage *placeholderImage = [UIImage imageNamed:@placeholder];
    NSURL *url = [[NSURL alloc] initWithString:ImageURL];
    [cell.imageView setImageWithURLRequest:[[NSURLRequest alloc] initWithURL:url] placeholderImage:placeholderImage success:^(NSURLRequest *request, NSHTTPURLResponse *response, UIImage *image)
    {
    cell.imageView.image = image;
    [cell setNeedsLayout];
    } failure:nil]; 

    Mais ça ne donne rien de nouveau : toujours le même souci. Je creuse


     


    MAJ : Le problème étant que je fait ma requête dans cellForRowAtIndexPath. (comme tu m'a dis Aligator)


    Bon la nuit porte conseil comme on dit je vais revoir ma méthodologie


  • CéroceCéroce Membre, Modérateur

    SDWebImage est également répandu pour fixer les images des UIImageViews de façon asynchrone (cherche son pod).


    ça évite d'utiliser le char d'assaut AFNetworking rien que pour ça.


  • Am_MeAm_Me Membre
    septembre 2014 modifié #8

    OK Céroce, merci je vais me pencher dessus.


     


    Du coup on peut mettre du code dans cellForRowAtIndexPath avec SDWebImage ah ce que je comprends d'après leur github



    - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
    {
    static NSString *MyIdentifier = @MyIdentifier;

    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:MyIdentifier];

    if (cell == nil)
    {
    cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault
    reuseIdentifier:MyIdentifier] autorelease];
    }

    // Here we use the new provided setImageWithURL: method to load the web image
    [cell.imageView setImageWithURL:[NSURL URLWithString:@http://www.domain.com/path/to/image.jpg]
    placeholderImage:[UIImage imageNamed:@placeholder.png]];

    cell.textLabel.text = @My Text;
    return cell;
    }

    1) Mais dans AFNetworking et SDWebImage ils utilisent le placeHolder (en même temps faudrait que je lise la doc). Et je ne comprend pas à  quoi il correspond. En théorie si je ne dis pas de bêtise le placeholder en HTML c'est pour mettre un texte exemple dans une "textfield" d'un formulaire (genre e-mail : toto@gmail.com). 


     


    2) Et mes autres requêtes qui récupère du texte : NSUrlSession suffit à  ce que je comprend du coup AFNetworking j'en ai pas trop besoin pour ca ?


     


    3) Du coup vous me conseillez quoi : me lancez dans AFNetworking : ou laisser tomber car NSUrlSession est à  la rescousse. Ou de me lancer dans SDWebImage ?


  • AliGatorAliGator Membre, Modérateur

    Bah oui pour une image c'est le même principe que pour l'exemple que tu prends (texte exemple dans un textfield = texte à  afficher en attendant que l'utilisateur tape du vrai texte).


     


    Le placeholder dans le cas de SDImage (ou de AFNetworking) c'est l'image à  afficher en attendant que la vraie image soit disponible. Puisque naturellement, vu que tu demandes une image venant d'une URL donc du web, le temps qu'elle se télécharge du réseau, tu n'auras pas tout de suite la vraie image de dispo, donc il est plus sympa si tu le souhaites d'afficher à  l'utilisateur une image placeholder en attendant (plutôt que de laisser une imageView vide).


  • Ok. Car j'avais essayé le placeHolder avec AFNetworking mais bon ça ne faisait pas des lueurs au final. J'ai du mal spécifier un truc.


     


    Ensuite moi j'utilise NSURLSessionDataTask. Est-ce que le fait d'utiliser NSUrlSessionDownloadTask peut être plus utile pour charger soit mes images soit mon texte ?


     


    D'après https://speakerdeck.com/chrisfsampaio/afnetworking-2-dot-0-plus-nsurlsession


  • AliGatorAliGator Membre, Modérateur

    NSURLSessionDownloadTask est fait pour télécharger des fichiers sur le disque. Du coup c'est un téléchargement comme quand tu télécharges une image sur ton disque dur depuis ton mac ou quoi (et du coup ça peut faire du téléchargement même quand l'appli n'est pas lancée, en laissant iOS télécharger le fichier et l'écrire sur le disque).


     


    Je ne pense pas que ce soit ce que tu veux ? Toi tu veux récupérer des NSData à  la fin, directement les données reçues par ta requête (pour les transformer en UIImage avec "imageWithData:"), non ? Et non pas télécharger les fichiers image sur ton disque tout ça pour relire le fichier après dans une UIImage ?


     


    Donc NSURLSessionDataTask est à  priori + adapté à  ton besoin, permettant d'envoyer une requête et de récupérer la réponse directement dans un NSData dans le block.


  • CéroceCéroce Membre, Modérateur


     


    3) Du coup vous me conseillez quoi : me lancez dans AFNetworking : ou laisser tomber car NSUrlSession est à  la rescousse. Ou de me lancer dans SDWebImage ?




    Dans un autre message, je vois que tu fais du POST multipart à  la mano, en composant toi-même le body.


    Si tu n'utilises ni le chargement asynchrone des images, ni le POST d'AFNetworking, il perd beaucoup d'intérêt, comme le disais Ali.


    Personnellement, je trouve qu'AFNetworking fonctionne très bien, mais est trop complexe pour des choses simples.

  • Alors au niveau POST/GET je ne suis pas top top. De quel message parles-tu (le body à  la mano)?


     


    Bah pour les images il y a ce que m'a dit Ali : le imageWithURL: ? si ce n'est que ça c'est pas si difficile au final non ? Ou je n'ai rien compris au message d'Ali


  • Bon je reviens avec un compte-rendu du AFNetworking et sa méthode imageWithURL:


    Ca marche beaucoup mieux ! merci à  tous d'ailleurs. Je n'ai plus trop le problème de recyclage : je ne l'ai même plus : du moins pour le moment :D


     


    Du coup SDWebImage je n'ai l'ai pas utilisé pour ce coup-ci. mais je retient le nom


     


    Maintenant que je me gratte la tête : Je n'y suis pas encore arrivé à  la mais dans mon application je devrai charger des XML très très grand : est-ce que NSURLSession fera son travail ou AFNetworking est à  la rescousse ce coup-ci : comme j'ai lu sur pas mal de site aussi que NSUrlSession+AFNetworking c'est le mariage parfait :).

  • AliGatorAliGator Membre, Modérateur


    Alors au niveau POST/GET je ne suis pas top top. De quel message parles-tu (le body à  la mano)?


     


    Bah pour les images il y a ce que m'a dit Ali : le imageWithURL: ? si ce n'est que ça c'est pas si difficile au final non ? Ou je n'ai rien compris au message d'Ali




    Céroce a confondu avec Kirax et sa question sur le POST multipart ici : http://forum.cocoacafe.fr/topic/12852-envoie-de-fichier-en-file-avec-variable-post/



  • Céroce a confondu avec Kirax et sa question sur le POST multipart ici : http://forum.cocoacafe.fr/topic/12852-envoie-de-fichier-en-file-avec-variable-post/




     


    Ah ok. Je me disais bien que je n'étais pas fou :D.


     


    Du coup si quelqu'un a des conseil à  me donner je suis preneur :p : on commence tous par être débutant dans AFNetworking ou NSUrlSession

  • Am_MeAm_Me Membre
    septembre 2014 modifié #17

    Ali : du coup en utilisant AFNetworking dans cellForRowAtIndexPath ca marche à  merveille même trop bien.


     


    Et quand je scroll aucun problème. 


    Mais en même temps je voudrais faire quelques chose de propre et dire 



    Une fois que la cellule a deja charge son image
    je n'y touche plus
    Si son image n'est pas encore chargée
    vas y cherche la

    Mais bon j'ai pas l'impression qu'on puisse faire ça : j'ai même rajouté un attribut à  ma class CustomCell : BOOL Loaded


    Mais bon vu le problème du recyclage : ça fait n'importe quoi.


     


    Du coup sans "optimisation" ca marche très bien ca me charge l'image dans la bonne cellule : mais quand je scroll ca les charge encore je crois bien (mais c'est transparent à  l'oeil nu je ne voyait pas de "rechargement").


     


    Voilà  est-ce possible de faire ce que je voulais ? ou pas.


    J'avais pensé à  changer le tag de la UIImageView dans la méthode setImageWithURL mais ai-je le droit de toucher à  leur api ... ?


  • Am_MeAm_Me Membre
    septembre 2014 modifié #18

    Bon j'ai trouvé un moyen plus simple. Sauvegarder les images en local dans un repertoire Cache/ et basta.


    Après je donnerait la possibilité à  l'utilisateur d'effacer le cache


     


    Je vais regarder s'il existe déjà  un fichier de cache pour une application : même si je suppose que oui.


     


    Algo : (rien de complexe) au final



    Si Image présente dans le cache
    ne fait rien
    Sinon
    Cherche la
  • AliGatorAliGator Membre, Modérateur
    Ce mécanisme est déjà  intégré dans AFNetworking et sa classe UIImageView+AFNetworking. Si tu regardes rapidement son code, il utilise un cache interne pour garder en mémoire les images déjà  chargée pour une URL donnée, pour éviter de les recharger.

    C'est aussi pour ça qu'on a tendance à  conseiller AFNetworking : c'est parce qu'il intègre tous ces petits détails qui rendent la vie plus facile concernant tout ce qui tourne autour du réseau.

    Après, c'est une lib conséquente, et si tu fais toutes tes autres requêtes avec NSURLSession plutôt qu'avec AFNetworking, du coup tu n'utiliseras qu'une partie d'AFN et c'est un peu too much d'importer toute la lib pour juste une fonctionnalité. Si tu ne veux que cette catégorie, mais que pour le reste de ton appli tu préfères utiliser NSURLSession (car après tout maintenant il répond à  la plupart des besoins), tu peux en effet préférer SDWebImage, et du coup il faut vérifier qu'il gère lui aussi un cache interne comme le fait AFNetworking et sa catégorie sur UIImageView.

    De toute façon si c'est pas le cas c'est pas bien compliqué à  implémenter comme algo. Le mieux étant d'utiliser les classes NSCache ou même NSURLCache de Cocoa, qui sont justement faites pour ça.
  • Am_MeAm_Me Membre
    septembre 2014 modifié #20

    Ok d'accord : ouai NSUrlSession pour le reste je pense.


     


    J'ai un problème qui persiste dans le recyclage des TableViewCell. J'ai associé un bouton à  chaque cell et à  chaque bouton j'associe une fonction



    [cell.monBouton addTarget:self action:@selector(maFonction:) forControlEvents:UIControlEventTouchDown]; 


    - (UITableViewCell*)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
    {

    //Chargement des images
    .......
    ....

    cell.monBouton.tag = indexPath.row;
    [cell.monBouton.addTarget:self action:@selector(maFonction:) forControlEvents:UIControlEventTouchDown];

    return cell;



    - (void)maFonction:(id)sender
    {
    UIButton *tmp = (UIButton*)sender;
    NSLog(@sender.tag : %li,(long)tmp.tag);

    [tmp setEnabled:false];
    [tmp setImage:[UIImage imageNamed:@grisee] forState:UIControlStateNormal];
    [favorites addObject:[mySeries objectAtIndex:tmp.tag]];
    }

    Mais quand je clique par exemple sur le bouton de la derniere cellule tout en bas : il se trouve que d'autre bouton aussi ce sont vu actionner l'action : mais ils ont le même tag alors qu'il ne devrait pas.


     


    PS : Dois-je ouvrir un nouveau Post ou le laisser ici vu que c'est un peu le même principe ?


  • AliGatorAliGator Membre, Modérateur

    Mais quand je clique par exemple sur le bouton de la derniere cellule tout en bas : il se trouve que d'autre bouton aussi ce sont vu actionner l'action : mais ils ont le même tag alors qu'il ne devrait pas.

    Bah si c'est tout à  fait logique vu le recyclage des cell. Tu ajoutes une action à  chaque fois que ta cellule est recyclée alors forcément les addTarget:action:forControlEvents: s'additionnent à  chaque recyclage, et tu te retrouves à  avoir ajouté 2x, 3x, n fois la même action à  un bouton qui l'avait déjà .

    Il ne faut ajouter ton action que quand la cellule est créée (dans l'init de sa sous-classe par exemple, ou via le Storyboard, etc) et pas à  chaque recyclage.

    J'ai l'impression que tu ne maà®trises pas bien le concept / fonctionnement de ce mécanisme de recyclage de cellules d'une tableView. Je t'invite fortement à  aller (re-)lire le Table View Programming Guide qui explique tous ces concepts clés en détail.
  • Am_MeAm_Me Membre
    septembre 2014 modifié #22

    Ouai c'est surtout que le système de recyclage je n'ai pas trop regardé moi étudié.


     


    C'est surtout le concept : Savoir quand ma cellule est crée que je maitrise plus la chose. Pourtant c'est important


     


    Et le fait que j'ai une Custom Cell : je peux faire à  partir du .xib la même chose ?


    Et init de ma sous-classe ? correspond au inithWithNbName ?


    Ou tu parlais du if(cell == nil) dans la fonction cellForRowAtIndexPath ?


     


    Bon je me plonge en parallèle dans la doc. 


  • Am_MeAm_Me Membre
    septembre 2014 modifié #23

    @Aligator : comme prévu j'ai lu les 97 pages (soit la totalité) de la doc sur les table view 


     


    Voilà  j'ai retenu pas mal de choses mais rien qui ne résous mon problème : 1) Je suis aveugle 2) Je suis un mauvais lecteur :D


     


    Tout d'abord : page 46/98 de la doc


     





    The data source, in its implementation of the tableView:cellForRowAtIndexPath: method, returns a configured cell object that the table view can use to draw a row. For performance reasons, the data source tries to reuse cells as much as possible. It first asks the table view for a specific reusable cell object by sending it a dequeueReusableCellWithIdentifier: message. If no such object exists, the data source creates it, assigning it a reuse identifier. The data source sets the cell's content (in this example, its text) and returns it. “A Closer Look at Table View Cells” (page 55) discusses this data source method and UITableViewCell objects in more detail.


     


     


    If the dequeueReusableCellWithIdentifier: method asks for a cell that's defined in a storyboard, the method always returns a valid cell. If there is not a recycled cell waiting to be reused, the method creates a new one using the information in the storyboard itself. This eliminates the need to check the return value for nil and create a cell manually. 





     


    __________________________________________________________________________________________________________________


     


    Dans un second temps (p. 56) : ils expliquent le fonctionnement du dequeuReusableCellWithIdentifier et son avantage sur la mémoire (dernière phrase)


     





    If a cell object is reusable"the typical case"you assign it a reuse identifier (an arbitrary string) in the storyboard. At runtime, the table view stores cell objects in an internal queue. When the table view asks the data source to configure a cell object for display, the data source can access the queued object by sending a dequeueReusableCellWithIdentifier: message to the table view, passing in a reuse identifier. The data source sets the content of the cell and any special properties before returning it. This reuse of cell objects is a performance enhancement because it eliminates the overhead of cell creation. 





     


    Le bout de code associé (p. 58)



    if (cell == nil) {
    cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:@MyIdentifier];
    cell.selectionStyle = UITableViewCellSelectionStyleNone;
    }

    __________________________________________________________________________________________________________________


     


    Et pour finir dans le paragraphe des Custom Cell ils expliquent que (p. 74) : 


     





    The proper use of table view cells, whether off-the-shelf or custom cell objects, is a major factor in the performance of table views. Ensure that your application does the following three things:


    Reuse cells. Object allocation has a performance cost, especially if the allocation has to happen repeatedly over a short period"say, when the user scrolls a table view. If you reuse cells instead of allocating new ones, you greatly enhance table view performance.


    Avoid relayout of content. When reusing cells with custom subviews, refrain from laying out those subviews each time the table view requests a cell. Lay out the subviews once, when the cell is created. 





     


    __________________________________________________________________________________________________________________


     


    J'ai lu et je pense avoir compris le principe globalement: 


     


    Grossomodo : Les cellules sont insérées au fur-et-à -mesure dans une sorte de "buffer" pour être recyclées (quand elle sont hors de vue : en gros non visible sur l'écran car l'utilisateur a scrollé par exemple). Quand l'utilisateur scroll : la tableView va aller chercher la cellule associée (associée avec un indentifiant reuseIdentifier) dans ce "buffer de recyclage". Il ne faut pas trop le surchargé de même. Et allouer une cellule à  chaque fois c'est MAL : d'ou le système de recyclage. J'ai oublié les cellules attendent gentiment dans le buffer pour être appelées (un peu comme une salle d'accueil chez le docteur)


     


    Le problème étant que moi j'utilise cela pour charger ma cellule : j'ai l'impression que ça n'a rien à  voir avec ce qui a dans la doc et que je ne peut faire ce qu'il font avec le initWithStyle:



    if(cell==nil)
    {
    NSArray *nib = [[NSBundle mainBundle] loadNibNamed:@CellSearch owner:self options:nil];
    cell = [nib objectAtIndex:0];
    //des cellules blanches sont crées car j'utilise le style Default. Mais ca ne charge pas la maquette des cellules du .xib
    //cell = [[CellSearch alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@searchCell];
    }


    Et aussi ma méthode est vide en même temps. Donc à  partir de là  je n'ai pas trop compris ce que je dois faire dans cette méthode ? redessiner la cellule, colorié mes machins truc ?



    - (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier
    {
    self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
    if (self) {
    // Initialization code

    }
    return self;
    }


    Mais je pense que mon problème de recyclage vient de là . Ai-je raison ?


  • CéroceCéroce Membre, Modérateur
    septembre 2014 modifié #24

    if(cell==nil)
    {
    NSArray *nib = [[NSBundle mainBundle] loadNibNamed:@CellSearch owner:self options:nil];
    cell = [nib objectAtIndex:0];
    }

    Ce code est bon. La lecture du nib va instancier une UITableViewCell.
     

    Mais je pense que mon problème de recyclage vient de là . Ai-je raison ?

    Non, tu as tort. Le problème de recyclage vient du fait que même si tu charges la cellule depuis un nib, il faut quand même que la tableview connaisse son Reuse Identifier.

    Il faut définir le Reuse Identifier dans le fichier xib (dans les propriétés de la UITableViewCell). Autrement, la tableview ne sait pas à  quelle famille appartient la cellule et ne peut pas utiliser son mécanisme de recyclage.

    Petit intermède: cette méthode pour utiliser des cellules personnalisées, décrite dans la doc d'Apple, est peu pratique. En effet, on n'a pas accès aux sous-vues de la cellule. Ce que conseille Apple est de mettre des entiers dans le champ .tag des vues et d'utiliser la méthode -[UIView subviewWithTag:] pour accéder à  chaque sous-vue. Mais c'est une méthode très longue.

    En fait, depuis iOS 5, le plus rapide pour avoir des ses propres cellules est de créer une sous-classe de UITableViewCell. À l'intérieur du xib, on pourra alors tirer des outlets entre la cellule et ses sous-vues (plus besoin de tag). Il faut ensuite déclarer la cellule en utilisant la méthode -[UITableView registerNib:forCellReuseIdentifier:]. Enfin, il faut utiliser la méthode -[UITableView dequeueReusableCellWithIdentifier:forIndexPath:] pour obtenir une cellule, soit tout neuve, soit recyclée (la méthode renvoie toujours une cellule, pas la peine de comparer à  nil).

     

    Et aussi ma méthode est vide en même temps. Donc à  partir de là  je n'ai pas trop compris ce que je dois faire dans cette méthode ? redessiner la cellule, colorié mes machins truc ?

    - (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier
    {
    self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
    if (self) {
    // Initialization code

    }
    return self;
    }

    C'est une méthode d'init, donc ça sert à  mettre les variables d'instance dans un état par défaut...
  • Am_MeAm_Me Membre
    septembre 2014 modifié #25


    Ce code est bon. La lecture du nib va instancier une UITableViewCell.

     

    Non, tu as tort. Le problème de recyclage vient du fait que même si tu charges la cellule depuis un nib, il faut quand même que la tableview connaisse son Reuse Identifier.


    Il faut définir le Reuse Identifier dans le fichier xib (dans les propriétés de la UITableViewCell). Autrement, la tableview ne sait pas à  quelle famille appartient la cellule et ne peut pas utiliser son mécanisme de recyclage.


    En fait, depuis iOS 5, le plus rapide pour avoir des ses propres cellules est de créer une sous-classe de UITableViewCell. À l'intérieur du xib, on pourra alors tirer des outlets entre la cellule et ses sous-vues (plus besoin de tag). Il faut ensuite déclarer la cellule en utilisant la méthode -[UITableView registerNib:forCellReuseIdentifier:]. Enfin, il faut utiliser la méthode -[UITableView dequeueReusableCellWithIdentifier:forIndexPath:] pour obtenir une cellule, soit tout neuve, soit recyclée (la méthode renvoie toujours une cellule, pas la peine de comparer à  nil).


     

    C'est une méthode d'init, donc ça sert à  mettre les variables d'instance dans un état par défaut...




     


    J'ai déjà  défini un reuse Identifier dans le .xib. J'ai aussi ma propre sous classe UITableViewCell ce qui évite les tag.


     


    Ensuite il faut la déclarer ou la cellule (avec cette ligne de code) : 



    [self.tableView registerNib:[UINib nibWithNibName:@CellSearch bundle:nil] forCellReuseIdentifier:@searchCell];

    et la méthode dequeu : pareillement il faut l'appeler ou du coup ? par logique cette méthode permet de recycler donc je suppose cellForRow ?


     


    Merci d'ailleurs pour ton explication


     


     


     


    Voilà  mon code (avec le problème qui persiste)



    - (UITableViewCell*)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
    {
    static NSString *cellIdentifier = @searchCell;

    //j'ai rajouté cette ligne dont tu m'a parlé
    [self.tableView registerNib:[UINib nibWithNibName:@CellSearch bundle:nil] forCellReuseIdentifier:cellIdentifier];


    CellSearch *cell = (CellSearch*)[tableView dequeueReusableCellWithIdentifier:cellIdentifier];



    if(cell==nil)
    {
    NSArray *nib = [[NSBundle mainBundle] loadNibNamed:@CellSearch owner:self options:nil];
    cell = [nib objectAtIndex:0];

    }

    // SUITE DU CODE INITIALISATION DES LABELS ET AUTRES .....


    //LA JE FAIS LE addTarget pour mon UIButton. ==> la fonction est pour le target est
    //décrite page 1 : grossomodo je fais le addTarget
    //et en même temps je fait monBoutton.tag = indexPath.row
    //et c'est grace à  cela que je reconnais le bouton dans ma méthode target.
    return cell;
    }


  • hello,


     


    Rajoute la ligne dans le viewDidLoad par exemple et non dans cellforRow..


  • CéroceCéroce Membre, Modérateur
    septembre 2014 modifié #27

    Samir veut dire la ligne -registerNib:forReuseIdentifier:. Cette méthode ne doit être appelée qu'une fois.

    Et j'ai bien écrit -dequeueReusableCellWithIdentifier:forIndexPath:.


    Et cell ne sera jamais == nil.


  • Am_MeAm_Me Membre
    septembre 2014 modifié #28

    Bah la pour le coup ça m'a un peut tout chamboulé. :/


     


    1) j'ai des cellules non initialisé pourtant j'ai reçu leur donnée (vérification via NSLog)


    2) j'ai parfois 1 ou 2 doublons


     


     


    MAJ : @Ceroce le -dequeueReusableCellWithIdentifier:forIndexPath:. est le problème quand je n'utilise pas le forIndexPath tout se passe bien.


     


    Quel différence y a t-il entre avec et sans le paramètre ?


     


     


    MAJ 2 : En fait ça marche : mais mes boutons sont de-selectionné quand je scroll (hors de la cellule) et que je reviens sur la cellule (sans le forIndexPath)


  • Am_MeAm_Me Membre
    septembre 2014 modifié #29

    Voilà  le code en question 


     


    viewDidLoad 



    - (void)viewDidLoad
    {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    self.tableView.delegate = self;
    self.tableView.dataSource = self;

    [self.tableView registerNib:[UINib nibWithNibName:@CellSearch bundle:nil] forCellReuseIdentifier:@searchCell];

    //etc ...
    }

    et cellForRowAtIndexPath



    - (UITableViewCell*)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
    {


    static NSString *cellIdentifier = @searchCell;
    CellSearch *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier forIndexPath:indexPath];

    //initialisation de mes labels et UIImage ...

    cell.monBouton.tag = indexPath.row;
    [cell.monBouton addTarget:self action:@selector(maFonction:) forControlEvents:UIControlEventTouchDown];

    return cell;

    }


    ____


     


     


    Mais pour le coup je n'utilise plus (cellForRowAthIndexPath:) : 



    NSArray *nib = [[NSBundle mainBundle] loadNibNamed:@CellSearch owner:self options:nil];
    cell = [nib objectAtIndex:0];


    MAJ : et je ne sais pas si c'est une coincidence mais si je sélectionne ma dernière cellule : à  partir d'elle toutes les 4 cellules un bouton est sélectionné aussi : il y a une sorte de propagation toute les 4 cellules ... bref problème de recyclage persistant j'ai l'impression. Ai-je oublié quelques chose ?


  • MAJ : J'ai rajouté du code pour ceux qui se sentent prêt à  comprendre mon problème. Enfin pour ceux qui ont le temps ;)


  • Am_MeAm_Me Membre
    septembre 2014 modifié #31

    J'ai enregistré une vidéo : pour vous montrer mon problème. (vidéo sur dropbox : cela dépend de votre connexion mais ça peut galérer à  lire la vidéo mais il est préférable de la télécharger puis l'ouvrir sur votre lecteur vidéo)


     


    Comme pas mal de développeur je n'aime pas trop rester bloqué sur un problème comme celui-ci.


    Je pense avoir fait le nécessaire : lecture totale de la doc + affichage du code au dessus et adaptation aux remarque de (@Ceroce et @samir) + vidéo :P


     


    Je prépare une corde pour ce soir  >:)


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