[Rà‰SOLU] UIScrollView dans UITableViewCell

BenjoBenjo Membre
mai 2014 modifié dans API UIKit #1

Bonjour à  tous,


 


Je suis confronté à  un petit soucis dans mon projet. J'ai un TableViewController avec des custom cell dedans. Dans les cellules, il y a des scrollView qui font la taille de la cellule. Seulement lorsque je souhaite sélectionner une cellule, je ne peux pas puisque je touche la scrollView et non la cellule.


J'ai donc essayé de créer une sous classe d'UIScrollView et de l'assigner à  ma scrollView qui est dans ma cellule. Et j'ai essayé ça :



#import "ScrollView.h"
#import "ListeTableViewController.h"

@implementation ScrollView

- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
// Initialization code
}
return self;
}

-(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
ListeTableViewController *liste = [[ListeTableViewController alloc] init];
[liste performSegueWithIdentifier:@détail sender:liste];
}

@end

Mais lorsque je touche la cellule, j'ai cette erreur :


Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'Receiver (<ListeTableViewController: 0x109308d30>) has no segue with identifier 'détail''


Pourtant depuis StoryBoard, j'ai bien créé un segue "push" avec un identifiant "détail".


 


Je ne comprends pas vraiment où est mon erreur. La technique d'assigner une sous classe d'UIScrollView à  ma scrollView je l'ai trouvé sur le web mais je ne trouve pas d'autre solution.


 


Quelqu'un a-t-il une idée sur la question ?


 


Merci d'avance :)


Réponses

  • LarmeLarme Membre
    avril 2014 modifié #2

    Si j'ai bien suivi :

    UITableViewController possède une ScrollView.

    UITableViewController a un segue @detail.

     


    J'ai l'impression que tu fais une nouvelle allocation du ViewController (ton UITableViewController) et du coup, lui n'a pas de segue à  proprement parlé qui lui est propre.


    Personnellement, je serais passé par un delegate, liant ton UITableViewController et ta ScrollView.


  • AliGatorAliGator Membre, Modérateur
    Essaye plutôt de garder une UIScrollView standard dans chacune de tes cell (plutôt qu'une sous-classe) de faire mumuse avec les méthodes "delaysContentTouches" ou "canCancelContentTouches" (je sais jamais trop laquelle de ces méthodes utiliser et dois refaire des tests un peu à  chaque fois, mais bon typiquement elles sont pensées pour ce genre de cas), ou alors de sous-classer sa méthode "touchesShouldCancelInContentView:"

    ---

    Sinon pour l'explication de ton crash, je pense pour l'idée expliquée par Larme aussi : tu fais un alloc/init sur ListeTableViewController ce qui crée une nouvelle instance, totalement indépendante de la configuration des instances que tu as prévues dans le Storyboard.
    Si tu veux une ListeTableViewController qui soit configurée telle qu'elle l'est dans le Storyboard, avec des segues liées à  cette instance et tout, il faut demander à  ton Storyboard de créer l'intance pour qu'il la crée et la configure avec les Segues & tout tel que tu l'as configuré dans ton fichier storyboard. Donc un petit appel à  -[UIStoryboard instanciateViewControllerWithIdentifier:] quoi plutôt qu'un simple alloc/init.
  • dans ce genre de situation j'utilise plutôt un gesture recognizer, ca évite du code trop spécifique et ca permet de garder une certaine forme d'interaction avec la scrollview.


  • Merci à  tous pour vos réponses.


     




    Si j'ai bien suivi :

    UITableViewController possède une ScrollView.

    UITableViewController a un segue @detail.




     


    Je ne sais pas si je me suis bien exprimé ou si c'est moi qui comprend mal. Je vais ré-expliquer plus clairement pour être sûr.


    En fait ce que j'essaye de reproduire, c'est l'effet de "parallaxe" lors du défilement d'un TableView comme dans l'application météo sur iOS 7. Du coup ma méthode c'est de placer une image dans une scrollView et cette scrollView est placée dans chaque cellule de mon tableView (voir pièces jointes).


    En fait ce que je me demande aussi c'est : est-ce que c'est la bonne méthode ?


     


    Parce qu'après j'ai un autre problème : je dois passer des données à  une autre ViewController. Je pensais utiliser la méthode "prepareForSegue" et "indexPathForSelectedRow" mais du coup je ne peux pas utiliser "indexPathForSelectedRow" et c'est embêtant.


     


    J'ai regardé tes méthodes dans la doc AliGator il faut que je regarde comment ça fonctionne et je vais essayer.


     



     


    dans ce genre de situation j'utilise plutôt un gesture recognizer, ca évite du code trop spécifique et ca permet de garder une certaine forme d'interaction avec la scrollview.



    Oui c'est une solution mais je suis alors confronté au problème du "indexPathForSelectedRow" puisque je ne peut pas déterminer (je pense) l'indexPath de ma scrollView dans mon tableView.


  • LarmeLarme Membre
    avril 2014 modifié #6

    De ce que j'ai compris et de ce que j'aurais fait à  chaud :


     


    CustomScrollView.h :



    @protocol CustomScrollViewDelegate < NSObject >
    -(void)pushSegueWithIdentifier:(NSString *)identifier withData:(NSDictionary)data;
    @end

    @property (nonatomic, assign) NSIndexPath indexPath;
    @property (nonatomic, week) id < CustomScrollViewDelegate > customDelegate;

    CustomScrollView.m



    if ([_customDelegate respondsToSelector:@selector(pushSegueWithIdentifier:withData:)])
        [_customDelegate pushSegueWithIdentifier:@détail withData:@{@indexPath:indexPath"}];

    Dans ListeTableViewController.h

    Je rajouterais 



    < CustomScrollViewDelegate >

    Dans ListeTableViewController.m

    Dans les settings de la cell, je mettrais :



    cell.scrollView.customDelegate = self;
    cell.scrollView.indexPath = indexPath;


    -(void)pushSegueWithIdentifier:(NSString *)identifier withData:(NSDictionary)data
    {
        [self performSegueWithIdentifier:identifier sender:[data objectForKey:@indexPath]];
    }


    -(void)prepareForSegueEtJeSaisPlusLaSuiteDeLaMethode
    {
        if ([[segue identifier] isEqualToString:@detail])
        {
           NSIndexPath pathQueTuVoulais = [sender objectForKey:@indexPath];
           // Reste des settings habituels pour passer de la data entre 2 VC via un Segue
        }
    }

    C'est sûrement pas le plus propre, et ça ne marchera peut-être pas, j'ai "codé" de tête, mais vu que j'aime beaucoup les delegates... Peut-être un peu trop.


     


     


    Maintenant, j'en reviens à  un truc que j'ai cru lire quelque part et qui semble marcher, mais un "sender" attend un NSDictionary si tu veux lui faire passer un object "bizarre". En tout cas, c'est mon impression, mais ça sera le sujet d'une autre conversation, j'ai une ou deux questions, suite à  une réponse que j'ai donné sur SO.


     


     


    Maintenant, Ali parlait d'instancier directement le ViewController dans le contexte du StoryBoard.

    Si j'ai bien tout compris, et je serais ravi que vous me corrigiez le cas contraire, mais :

    Le StoryBoard possède ses segues, etc.

    Dans une simple classe d'un ViewController n'en a pas en soit.

    Du coup, le alloc/init que tu faisais ne connaissait pas le segue.

    Maintenant, tu peux appeler un StoryBoard, et un VC en particulier (et donc avec le contexte des segues, etc.) par code :

    Genre, avec : UIStoryboard *storyBoard = [UIStoryboard storyboardWithName:@Main_iPhone bundle:nil]; //Dans ton cas, vu que je ne suis pas sûr que tu puisses récupérer le "storyboard actuel" avec [self storyboard]


      UIViewController *vc = [storyBoard instantiateInitialViewController]; // Pour le premier 

    ou [storyBoard instantiateViewControllerWithIdentifier:@StringName] //String Name est à  configuré dans StoryBoard, y'a une case pour chaque ViewController)


  • Bonjour,


    désolé j'ai des problèmes d'accès internet ces derniers temps (les joies des déplacements...). Si tu veux gérer de la parallax, je pense que tu devrais chercher un peu plus du côté d'UIMotionEffect, je suis pas sur qu'il soit nécessaire d'utiliser une scrollview pour cela (déplacer une simple vue en fond devrait suffire). Par ailleurs si tu désires vraiment utiliser une scroll view, dans ce cas tu n'as pas besoin d'interaction utilisateur, donc tu peux la désactiver (depuis l'inspecteur de composants de storyboard) et tu retrouveras le comportement normal de ta cellule.


    Concernant ta remarque sur l'index path, rien ne t'empêche de sous classer un gesture recognizer pour lui ajouter l'index path de la cellule et le retrouver dans le sender que tu récupère en paramètre dans le prepareForSegue: 

  • BenjoBenjo Membre


    Essaye plutôt de garder une UIScrollView standard dans chacune de tes cell (plutôt qu'une sous-classe) de faire mumuse avec les méthodes "delaysContentTouches" ou "canCancelContentTouches" (je sais jamais trop laquelle de ces méthodes utiliser et dois refaire des tests un peu à  chaque fois, mais bon typiquement elles sont pensées pour ce genre de cas), ou alors de sous-classer sa méthode "touchesShouldCancelInContentView:"




    J'ai essayé avec delaysContentTouches. ça fait directement appel à  la méthode "touchesShouldBegin..." et il est donc possible d'effectuer la transition vers l'autre viewController. Merci AliGator pour cette solution.


     


     




    De ce que j'ai compris et de ce que j'aurais fait à  chaud :


     


    CustomScrollView.h :



    @protocol CustomScrollViewDelegate < NSObject >
    -(void)pushSegueWithIdentifier:(NSString *)identifier withData:(NSDictionary)data;
    @end

    @property (nonatomic, assign) NSIndexPath indexPath;
    @property (nonatomic, week) id < CustomScrollViewDelegate > customDelegate;

    CustomScrollView.m



    if ([_customDelegate respondsToSelector:@selector(pushSegueWithIdentifier:withData:)])
        [_customDelegate pushSegueWithIdentifier:@détail withData:@{@indexPath:indexPath"}];

    Dans ListeTableViewController.h

    Je rajouterais 



    < CustomScrollViewDelegate >

    Dans ListeTableViewController.m

    Dans les settings de la cell, je mettrais :



    cell.scrollView.customDelegate = self;
    cell.scrollView.indexPath = indexPath;


    -(void)pushSegueWithIdentifier:(NSString *)identifier withData:(NSDictionary)data
    {
        [self performSegueWithIdentifier:identifier sender:[data objectForKey:@indexPath]];
    }


    -(void)prepareForSegueEtJeSaisPlusLaSuiteDeLaMethode
    {
        if ([[segue identifier] isEqualToString:@detail])
        {
           NSIndexPath pathQueTuVoulais = [sender objectForKey:@indexPath];
           // Reste des settings habituels pour passer de la data entre 2 VC via un Segue
        }
    }

    C'est sûrement pas le plus propre, et ça ne marchera peut-être pas, j'ai "codé" de tête, mais vu que j'aime beaucoup les delegates... Peut-être un peu trop.


     


     


    Maintenant, j'en reviens à  un truc que j'ai cru lire quelque part et qui semble marcher, mais un "sender" attend un NSDictionary si tu veux lui faire passer un object "bizarre". En tout cas, c'est mon impression, mais ça sera le sujet d'une autre conversation, j'ai une ou deux questions, suite à  une réponse que j'ai donné sur SO.


     


     


    Maintenant, Ali parlait d'instancier directement le ViewController dans le contexte du StoryBoard.

    Si j'ai bien tout compris, et je serais ravi que vous me corrigiez le cas contraire, mais :

    Le StoryBoard possède ses segues, etc.

    Dans une simple classe d'un ViewController n'en a pas en soit.

    Du coup, le alloc/init que tu faisais ne connaissait pas le segue.

    Maintenant, tu peux appeler un StoryBoard, et un VC en particulier (et donc avec le contexte des segues, etc.) par code :

    Genre, avec : UIStoryboard *storyBoard = [UIStoryboard storyboardWithName:@Main_iPhone bundle:nil]; //Dans ton cas, vu que je ne suis pas sûr que tu puisses récupérer le "storyboard actuel" avec [self storyboard]


      UIViewController *vc = [storyBoard instantiateInitialViewController]; // Pour le premier 

    ou [storyBoard instantiateViewControllerWithIdentifier:@StringName] //String Name est à  configuré dans StoryBoard, y'a une case pour chaque ViewController)




    J'ai essayé cette méthode et ça fonctionne bien en effet ça règle le problème du alloc init que je faisais et qui n'étais pas bon. Merci pour tes explications.


     


     




    Bonjour,


    désolé j'ai des problèmes d'accès internet ces derniers temps (les joies des déplacements...). Si tu veux gérer de la parallax, je pense que tu devrais chercher un peu plus du côté d'UIMotionEffect, je suis pas sur qu'il soit nécessaire d'utiliser une scrollview pour cela (déplacer une simple vue en fond devrait suffire).




    Je connais UIMotionEffect mais il s'utilise avec l'accéléromètre. Ici ce n'est pas ce que je cherche à  faire. Je créer l'effet "parallaxe" seulement lorsque l'utilisateur scroll le tableView et non quand il bouge son appareil.


     


     




    Par ailleurs si tu désires vraiment utiliser une scroll view, dans ce cas tu n'as pas besoin d'interaction utilisateur, donc tu peux la désactiver (depuis l'inspecteur de composants de storyboard) et tu retrouveras le comportement normal de ta cellule.


    Concernant ta remarque sur l'index path, rien ne t'empêche de sous classer un gesture recognizer pour lui ajouter l'index path de la cellule et le retrouver dans le sender que tu récupère en paramètre dans le prepareForSegue: 




    Effectivement je n'avais pas essayé cette solution et ça marche aussi je retrouve alors, comme tu l'avais dit, le comportement d'une cellule normale. Merci pour cette autre solution.


     


    Merci à  vous tous pour vos explications et vos réponses :)

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