[Résolu] Un bon tuto sur les UITableView ?

septembre 2014 modifié dans API UIKit #1

Bonjour,


 


Quelqu'un aurait il un lien (en fr si possible, vidéo ou pas) sur comment mettre en place une UITableView ?


 


Merci.


«13

Réponses

  • AliGatorAliGator Membre, Modérateur

    Le "Table View Programming Guide" dans la doc Apple, tout simplement ? Qui te décrit tous les concepts et toutes les étapes et extraits de code nécessaires ?


  • ObjectiveSwift : Je pense qu'Ali a tout à  fait raison. Il m'a obligé à  lire 98 pages en anglais mais là  j'ai compris des choses intéressantes. Je ne peut te conseiller que de la lire.


     


    Ensuite ... Google/Youtube est ton ami.


  • septembre 2014 modifié #4

    Et si il faut tout faire en "mode" programmatique ? Je viens de le commencer, le truc c'est que je n'ai pas beaucoup de temps, alors 98 pages 'in english', je sais pas trop mais bon je verrais bien.


     


    (quel bourreau ce Ali)


  • AliGatorAliGator Membre, Modérateur
    Bah les Table Views une fois que t'as compris le principe c'est pas méchant, mais il faut quand même assimiler plusieurs concepts
    - Les indexPath, les sections, les rows
    - Comment sont composées les cellules (leur contentView, etc)
    - Le principe de recyclage des cellules
    - Le principe de recyclage des cellules (ouais faut vraiment le comprendre celui-là , faut le relire 2 fois ^^)
    - Les delegates et dataSources
    - Le fait qu'il ne faut pas faire des calculs compliqués dans les méthodes du UITableViewDataSource pour des questions de performances lors du scroll & co
    - Les problématiques qui peuvent arriver du coup si on ne tient pas compte des trucs précédents (cas typiques : chargement d'images dans les cellules, du coup soit crash soit les images vont apparaà®tre n'importe comment sans correspondre aux cellules, etc)

    Et si en plus tu veux rajouter des choses comme :
    - Le swipe to delete
    - Le drag & drop de cellules
    - L'animation d'ajout/suppression de cellules
    C'est d'autres concepts à  comprendre

    Et si en plus tu veux coupler cela avec des données CoreData, il y a des optimisations à  faire avec un FRC pour qu'il ne fetch que les données nécessaires et gère la pagination



    Bref c'est pas en disant "j'ai pas beaucoup de temps" que ça va te faire apprendre la même chose + vite pour autant, y'a quand même pas mal de concepts clés à  comprendre si tu veux pas ensuite te retrouver avec des comportements qui vont pas aller voire crasher ou un truc qui va ramer.
  • Oui, je suis tout à  fais d'accord avec ta dernière phrase. C'est juste que ça passe ou ça casse, si je dois avoir un problème de perf c'est mort.


     


    Bon une fois qu'on récupère un fichier JSON avec toutes les données (qui contient des liens pour télécharger des images), comment vous gérez le truc ? Je pose la question parce que je ne connais pas du tout CoreData et pour le moment je ne sais pas quoi faire de ces données (en JS j'aurais aucun problème mais avec iOS je découvre).


     


    Pour faire le point, je récupère le JSON, et sur un autre projet "prototype", j'affiche une UITableView avec du texte (généré en dur). L'idée c'est d'assembler tout ça et donc de traiter le JSON pour l'afficher dans les Cellules, j'ai pas encore vu mais apparemment il existe un comportement prévu pour afficher des images. Donc dans les cellules, texte + image.


     


    J'ai vu des vidéos qui expliquent effectivement le principe de recyclage, je verrais en passant sur la pratique si j'ai bien compris.


  • CéroceCéroce Membre, Modérateur

    Pourquoi utiliser Core Data ? Tu parses ton JSON et tu stockes ça dans des sous-classes de NSObject.


    Tu peux même accéder directement au JSON parsé si c'est juste pour de l'affichage.


    Core Data c'est quand même souvent la grosse artillerie, alors si tu n'as jamais vu comment on fait de façon plus classique (avec des NSObject), c'est sûr que c'est difficile.


  • La question est comment passer de JSON en objet. J'ai bien vu NSJSONSerialization mais pour le moment je ne vois pas trop comment.


  • AliGatorAliGator Membre, Modérateur
    Si tu débutes les UITableView et que dès ta première expérience avec, tu ajoutes la difficulté d'y mettre dedans des images qui viennent du net (donc qu'il fait télécharger mais que le temps que l'image soit téléchargée ta cela eu le temps d'être recyclée etc) tu es loin de commencer par le plus facile !! Tu vas te casser les dents rapidement.


    C'est justement un des cas où il fait avoir déjà  bien compris les concepts de base de la tableView comme le recyclage et surtout ses conséquences (mais aussi les principes plus génériques et transverses de Cocoa, comme avoir bien respecté le pattern MVC, j'espère que tu les connais et applique déjà , sinon tu pars déjà  sur d'encore plus mauvaises bases) et c'est un des problèmes où les débutants se cassent systématiquement les dents.


    Mélanger recyclage, synchronisme / chargement différé d'images et dans parler du cache pour ne pas recharger les images à  chaque fois que tu scrolles dans un sens puis dans l'autre, tu mets la barre haute pour débuter ! Ou alors faut utiliser des libs toutes faites qui te facilitent la vie, encore faut-il déjà  connaà®tre les concepts qu'elles utilises (blocks, etc) et savoir comment intégrer proprement une lib externe à  ton projet (CocoaPods etc), encore des concepts que, si tu débutes, tu ne connais/maà®trise pas forcément...
  • septembre 2014 modifié #10

    Crois moi, Ali, je n'ai pas vraiment le choix et ce n'est pas le goût du risque ou de la difficulté qui me motive.


     


    Sinon, je pense avoir bien compris les blocs (grâce a toi d'ailleurs après avoir vu les vidéos de Rennes + lecture de doc et pratique notamment sur mon App précédente). CocoaPods, check, je m'en sert justement sur ce projet pour AFNetworking.


     


    Je suis sur la doc des UITableView et je viens d'en mettre une en place. Par contre il me manque pour le moment l'essentiel, c'est à  dire les données. Je peux récupérer mon JSON mais comme dis plus haut maintenant je dois le traiter pour afficher ça dans ma TableView (d'abord le texte, je verrais les images par la suite).


  • AliGatorAliGator Membre, Modérateur
    Bah NSJSONSerialization comme tu as dit (la doc à  tout ce qu'il faut " en même temps y'a que 2 méthodes, une pour lire du JSON et une pour en générer, ça doit être une des classes les plus simples, et au pire GIYF.
  • C'est à  dire que je ne vois pas de méthode qui retourne de dictionnaire (alors qu'ils en parlent mais j'ai peut-être rien compris). Sinon générer des classes mais alors là , j'en ai aucune idée. Je test et je reviens si j'ai des questions.


  • LarmeLarme Membre
    septembre 2014 modifié #13


    C'est à  dire que je ne vois pas de méthode qui retourne de dictionnaire (alors qu'ils en parlent mais j'ai peut-être rien compris). Sinon générer des classes mais alors là , j'en ai aucune idée. Je test et je reviens si j'ai des questions.




    Y'a une méthode de classe qui retournera soit un NSDictionary soit un NSArray (en fonction du "top level" de ton JSON) : +[NSJSONSerialization quelquechose]; (du coup, son retour dans la doc doit être id).


  • Le livre "Programmation iOS6" chez Eyrolles propose un tuto complet sur le sujet sur lequel je me suis appuyé. Ensuite, Interface Builder propose un TableView "par défaut". J'ai copié ses méthodes dans un UIViewController pour que la table n'occupe pas tout l'écran mais une partie seulement. C'est assez simple en fait...


  • septembre 2014 modifié #15

    Je pense avoir le bouquin dont tu parles mais pour la v5. Alors j'ai cette ligne :



    [NSJSONSerialization JSONObjectWithData:data options:0 error:&error];

    qui pose problème car l'application plante au lancement (après avoir chargé les données). Concernant la var data, j'ai cette ligne avant celle du dessus :



    NSData *data = [[NSData alloc] init];
    data = responseObject;

    Le responseObject est de type id retourné par AFNetworking. Si j'affiche data je me retrouve bien avec mon JSON complet.


     


    Pour finir, voici l'erreur :



    [__NSCFDictionary bytes]: unrecognized selector sent to instance 0x10961dd30
    2014-09-18 13:36:14.911 App[3314:60b] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[__NSCFDictionary bytes]: unrecognized selector sent to instance 0x10961dd30'

    Avez-vous une idée ?


     


    Merci.


  • CéroceCéroce Membre, Modérateur


    NSData *data = [[NSData alloc] init];
    data = responseObject;

    À quoi bon instancier une NSData si tu ne l'utilises pas après ?
     


    [__NSCFDictionary bytes]: unrecognized selector sent to instance 0x10961dd30
    2014-09-18 13:36:14.911 App[3314:60b] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[__NSCFDictionary bytes]: unrecognized selector sent to instance 0x10961dd30'

    ça dit que NSDictionary n'a pas de méthode -bytes. (Par contre NSData a bien une telle méthode).
    AFNetworking a déjà  parsé le JSON. responseObject est un NSDictionary, pas une NSData.
  • AliGatorAliGator Membre, Modérateur
    septembre 2014 modifié #17

    1) Ca sert à  rien de faire un alloc+init d'un tout nouveau NSData si finalement c'est pour le mettre à  la poubelle à  la ligne d'après en mettant autre chose dans la variable ! C'est comme si tu faisais "int i = 5" pour juste la ligne d'après mettre "i = 12", remplaçant ta valeur 5 immédiatement. Du coup là  tu alloues de la mémoire pour rien.


     


    2) L'erreur devrait quand même facilement te mettre sur la piste : il essaye d'appeler une méthode de NSData (la méthode "bytes") sur un objet qui semble en réalité être un NSDictionary.


    Ca veut dire que ta variable responseObject contient un NSDictionary et non pas des NSData.


     


    Si tu as utilisé AFNetworking et un AFJSONResponseSerializer, cette classe fait déjà  pour toi le travail d'interpréter les NSData retournées dans la réponse de la requête pour les interpréter en JSON et te retourner un objet parsé (un NSDictionary si ton JSON représente un dictionnaire comme objet racine).


     


    Donc en fait tu avais déjà  un NSDictionary depuis le début, puisque AFNetworking fait déjà  le boulot pour toi (en appelant lui-même NSJSONSerialization sous le capot dans l'implémentation de AFJSONResponseSerializer). C'est d'ailleurs un des intérêts de ce AFResponseSerializer, d'où son nom.


     


    [EDIT] Grillé par Céroce (de peu !) ^^




  • Et si il faut tout faire en "mode" programmatique ? Je viens de le commencer, le truc c'est que je n'ai pas beaucoup de temps, alors 98 pages 'in english', je sais pas trop mais bon je verrais bien.


     


    (quel bourreau ce Ali)




     


    J'ai encore ma tête tu sais :p


     


    Franchement tu prends 1h dans ta vie ça va pas te tuer je pense. enfin je ne l'espère pas xD

  • Ali, je passe par une simple requête GET (mais de AFNetworking), il semble effectivement faire tout le travail. La classe est bien NSCFDictionary.


     


    Oui Am_me, j'ai commencé et c'est pas si "complexe" que je le craignais.


     


    Merci pour vos réponses.


  • septembre 2014 modifié #20

    Alors je reviens ici car je récupère le JSON, le traite et génère un tableau d'objets contenant toutes les données parser.


     


    Ma question est au niveau des images dans le UITableView :



    - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
    {
    UITableViewCell *cell = [self.tableView dequeueReusableCellWithIdentifier:@MyIdentifier];

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

    cell.textLabel.text = [[self.array objectAtIndex:indexPath.row] one];
    cell.detailTextLabel.text = [[self.array objectAtIndex:indexPath.row] two];

    [manager GET:[[self.array objectAtIndex:indexPath.row] pict] parameters:nil success:^(AFHTTPRequestOperation *operation, id responseObject)
    {
    cell.imageView.image = image téléchargé.

    } failure:^(AFHTTPRequestOperation *operation, NSError *error)
    {
    NSLog(@%@", error);
    }];

    return cell;
    }

    J'ai cette erreur :


     



     


    "Request failed: unacceptable content-type: image/jpeg"



     


    Alors j'ai plusieurs questions :


     


    1/ Sur le principe, est-ce correct (dans la méthode cellForRowAtIndexPath, lancer un download asynchrone avec AFNetworking) ?


     


    2/ J'ai vu sur un site qu'ils parlent de AFImageRequestOperation, mais je ne peux pas regarder en détail car la page est bloqué chez nous (et sur le site de AF je ne trouve rien).


     


    Merci pour votre aide.


  • AliGatorAliGator Membre, Modérateur

    1/ Sur le principe, est-ce correct (dans la méthode cellForRowAtIndexPath[/size], lancer un download asynchrone avec AFNetworking) ?

    Non, non et non !!! Surtout pas.

    C'est ce dont je te parle depuis le début du thread justement. Les TableView c'est pas trop compliqué quand tu as toutes les données, faut comprendre le recyclage et tout mais ça va.

    Mais si tu fais de la récupération de données asynchrone (genre typiquement du téléchargement d'image), il faut prendre en compte les conséquences du recyclage des cellules. Car sinon tu lances ta requête quand une cellule est affichée, et quand la réponse arrive la cellule a eu le temps d'être recyclée donc tu vas afficher l'image pour un contenu qui aura changé entre temps. Sans parler du cache des images qu'il faut gérer, pour éviter que si tu scrolles dans tous les sens faut pas qu'une requête parte à  nouveau pour retélécharger la même image à  chaque fois que ta cellule s'affiche à  l'écran. Etc, etc.

    Bref, c'est ce que j'ai déjà  essayé de t'expliquer plus haut, c'est toutes ces petites conséquences du recyclage dont il faut avoir conscience et qui lèvent des concepts qu'il faut bien maà®triser.

    2/ J'ai vu sur un site qu'ils parlent de AFImageRequestOperation, mais je ne peux pas regarder en détail car la page est bloqué chez nous (et sur le site de AF je ne trouve rien).

    Si tu utilises déjà  AFNetworking dans ton projet, du coup je te conseille vivement de profiter de leur catégorie sur UIImageView qui rajoute une méthode "setImageWithURL:". Cette méthode est justement faite pour traiter ce cas assez tricky, elle se charge de télécharger l'image, de la mettre en cache, de gérer les cas de recyclage, de vérifier quand l'image est arrivée que tu n'as pas changé d'URL entre temps, etc. Bref ce qui fait toute la difficulté de mélanger TableView et son recyclage avec récupération asynchrone de données.
  • septembre 2014 modifié #22

    Ok, je vois, donc si je met en place (et à  cet endroit) setImageWithURL, j'ai gagné ? :)


     


    EDIT : Oui en effet la doc sur setImageWithURL est claire sur le sujet (cache, recyclage, etc.). Mais c'est quoi l'astuce pour l'utiliser ? Parce que mon cell.imageView, il ne connait pas de setImageWithURL...


  • AliGatorAliGator Membre, Modérateur
    Bah tu as #importé le header UIImage+AFNetworking dans laquelle elle est définie, au moins, pour pouvoir l'appeler ? ;)
  • septembre 2014 modifié #24

    Ah ouais ok, j'avais pas vu les deux ensembles... ... ... ... ...  :p


     


    Bon, j'ai toujours l'erreur par contre sur le fait qu'il n'accepte pas ce type de data (unacceptable content-type: image/jpeg). (setResponseSerializer ?)


     


    Bon si j'ai bien compris, AFNetworking prends par défaut du JSON et si c'est autre chose, ça ne fonctionne pas, il faut donc lui donner ce que l'on attends pour qu'il construise la requête, c'est bien ça ?


     


    Sinon, est-ce correct pour vous (en tout cas ça marche, j'ai pas encore essayé sur un vrai device) :



    - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
    {
    UITableViewCell *cell = [self.tableView dequeueReusableCellWithIdentifier:@MyIdentifier];

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

    cell.textLabel.text = [[array objectAtIndex:indexPath.row] titre];
    cell.detailTextLabel.text = [[array objectAtIndex:indexPath.row] texte];

    [manager setResponseSerializer:[AFImageResponseSerializer serializer]];
    [manager GET:[[array objectAtIndex:indexPath.row] img] parameters:nil success:^(AFHTTPRequestOperation *operation, id responseObject)
    {
    [cell.imageView setImageWithURL:[NSURL URLWithString:[[array objectAtIndex:indexPath.row] img]]];
    } failure:^(AFHTTPRequestOperation *operation, NSError *error)
    {
    NSLog(@%@", error);
    }];

    return cell;
    }

  • samirsamir Membre
    septembre 2014 modifié #25

    Hello,


     


    Pose toi la question pourquoi tu fais : [manager GET;....] ?


    Tu peux supprimer l'appel de ton manger, il fais rien de spécial à  part te créer des bugs...



    - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
    {
        UITableViewCell *cell = [self.tableView dequeueReusableCellWithIdentifier:@MyIdentifier];
        
        if (cell == nil)
        {
            cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:@MyIdentifier];
        }
        
        cell.textLabel.text = [[array objectAtIndex:indexPath.row] titre];
        cell.detailTextLabel.text = [[array objectAtIndex:indexPath.row] texte];
        [cell.imageView setImageWithURL:[NSURL URLWithString:[[array objectAtIndex:indexPath.row] img]]];
          return cell;
    }

    C'est la méthode setImageWithURL qui va s'occuper de tous les cassements de tête à  ta place. ( Téléchargement en backhround, cache,...).


  • septembre 2014 modifié #26

    Tu as était plus rapide, j'allais le supprimer :)


     


    Je fais le ménage et je reviens (trop concentré sur l'erreur et le setImageWithURL)


     


    Bon voici donc le final :



    - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
    {
    UITableViewCell *cell = [self.tableView dequeueReusableCellWithIdentifier:@MyIdentifier];

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

    cell.textLabel.text = [[array objectAtIndex:indexPath.row] titre];
    cell.detailTextLabel.text = [[array objectAtIndex:indexPath.row] texte];

    [cell.imageView setImageWithURL:[NSURL URLWithString:[[array objectAtIndex:indexPath.row] img]]];

    return cell;
    }
  • septembre 2014 modifié #27

    J'ai une question concernant la navigation entre un UITableViewController et la vue détail. J'ai créé une UIViewController pour afficher les détails que j'appelle dans cette méthode :



    - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
    {
    ViewControllerDetail *d = [[ViewControllerDetail alloc] init];

    [[self navigationController] pushViewController:d animated:YES];
    }


    Ca ne fonctionne pas mais je rentre bien dans cette méthode au click d'une UICellView. Par contre le NSLog suivant :



    NSLog(@%@", [self navigationController]);

    M'affiche null.


     


    Je suis en train d'essayer plein de trucs et j'ai regardé la doc mais je n'y arrive pas. Une petite idée ?


  • A oui, je précise que je n'utilise pas IB ou Storyboard.


  • AliGatorAliGator Membre, Modérateur
    Et pourquoi voudrais-tu qu'il soit autre chose que null ? Tu en as créé un de NavigationController pour y mettre ton ViewController dedans ?


    Si ton ViewController n'est pas dans un NavigationController c'est un peu normal que la propriété navigationController soit null non ? what did you expect? ^^
  • Bon alors mon .m sur lequel je travaille est un UITableViewController. Je ne m'attends pas a ce qu'il soit null mais je pense que cela montre bien qu'il manque un maillon (du moins, j'imagine).


     


    Dans la méthode viewDidLoad, je configure le UITableView (alloc, initWithFrame, etc.). J'ai aussi une UIView (qui me sert de "base" pour y mettre tout mon bazars dedans). Donc tout marche plutôt bien, a priori mais quand je click il ne ce passe rien.


     


    J'essaye de setter mon UINavigationController, ce que tu me dis dans ta dernière phrase, j'imagine que le problème est bien là .


     


    La question est : comment ?


     


    Est-ce clair et avec toutes les infos ?


  • AliGatorAliGator Membre, Modérateur
    Alors y'a un truc que je ne comprend pas, si tu utilises un UITableViewController pourquoi tu t'embêtes à  recréer ta TableView dans son viewDidLoad etc, alors que l'intérêt d'un UITableViewController par rapport à  un UIViewController classique est justement qu'il s'occupe de ça pour toi ?


    Et sinon, pour revenir au problème, j'ai bien envie de te renvoyer sur la documentation de UINavigationController pour apprendre et comprendre comment il marche.

    Ainsi, évidemment, que la documentation View Controllers Programming Guide qui détaille carrément tout ce qu'il faut savoir sur les ViewControllers dans leur ensemble et comment ils interagissent entre eux. D'ailleurs j'espéré que tu avais déjà  lu ce genre de documentation qui explique les bases dans le domaine et qui sont donc contournables.
Connectez-vous ou Inscrivez-vous pour répondre.