[Résolu] Reproduire le carrousel Apple Store

MarcoDahMarcoDah Membre
décembre 2014 modifié dans API UIKit #1

Bonjour,


 


Bon je dois vous avouer ma détresse ici. Je galère comme un beau diable pour reproduire une feature .


Je cherche à  refaire ce carrousel qui est utilisé dans l'Apple Store. J'imagine que si Apple la propose c'est qu'il est facile de la faire. Mais je n'y arrive pas. J'ai bien entendu fait quelques recherches avant de poster ici. Mais soit je n'ai pas les bon mots clés pour cette recherche soit personne n'a eu de problème.


 


Voici l'idée que j'essaye de reproduire:


 


Une UIScrollView horizontale avec 3 views à  l'intérieur.


Un système de pagination ( avec l'option coché ).


Lorsque j'arrive au bout de la 3°view et que je scroll une fois de plus cela affiche la première vue.


 


Le comportement recherché étant celui de la scroll horizontale au top de la vue "Sélection" de l'Apple Store


 


J'ai déjà  un affichage fonctionnel à  l'heure actuelle, c'est à  dire que les vues s'affiche et il faux scroll back pour revoir les autres vues. Mais on me demande plutôt le genre d'affichage énoncé plus haut.


 


Auriez vous quelques pistes à  me conseiller ?


 


Merci d'avance


 


 


Réponses

  • Pourquoi ne pas faire une collection view, avec un numberOfItem infini (très grand), et un cellAtIndex qui te renvoie la cellule de vrai index indePath.row % realNumberOfItems (opération reste de ma division, ou modulo -> c'est ce qu'il te faut)
  • AliGatorAliGator Membre, Modérateur
    Le mieux est de faire une UICollectionView horizontale effectivement... avec un numberOfItem très grand c'est une possibilité comme le propose colas2, mais comme même "très grand" ce n'est pas "infini", l'idée c'est que quand tu as fini de scroller la CollectionView (méthode de delegate "scrollViewDidScroll:" typiquement), tu modifies le contentOffset.x de ton UICollectionView pour le remettre dans un range central.

    En fait si tu as 10 items à  afficher, il te faut donc pour faire simple une CollectionView à  30 items, et positionner initialement ta CollectionView non pas à  contentOffset.x = 0 mais à  contentOffset.x = offsetForItem(10) (= 10*itemSize.x + les marges entre chaque item). Comme ça même dès le début ton utilisateur va aussi pouvoir scroller pour voir les éléments à  gauche, et pas que ceux à  droite.

    Ensuite, une fois que l'utilisateur a fini de scroller, si le contentOffset.x n'est pas entre offsetForItem(10) et offsetForItem(19), alors tu fais en sorte qu'il le soit (une sorte de "modulo"), en ajoutant ou supprimant des multiples de la largeur nécessaire pour tes 10 items (offsetForItem(10) en somme), pour toujours te retrouver dans l'intervalle où tu as 10 items en réserve à  gauche et 10 items en réserve à  droite, laissant l'utilisateur scroller dans le sens qu'il veut. Comme tu vas changer ce contentOffset par un multiple de offsetForItem(10) " sans animation, pour le coup ", visuellement l'utilisateur ne vera pas de changement, car tu vas remplacer le visuel de l'item 2 par celui de l'item 12 par exemple, mais comme ce sont les mêmes il n'y verra que du feu
  • AliGatorAliGator Membre, Modérateur

    http://www.thinkandbuild.it/introduction-to-3d-drawing-in-core-animation-part-2/

    Il parle certes de Carrousel, donc moi aussi au début j'ai pensé au Carrousel 3D du Finder avec les panneaux en 3D, mais en fait il ne cherche même pas à  faire de la 3D, juste une ScrollView infinie :

    Le comportement recherché étant celui de la scroll horizontale au top de la vue "Sélection" de l'Apple Store

    Et cette dernière n'est qu'en 2D.
  • Sinon tu peux créer une UIView, sur lequel tu disposes une ImageView que tu récupère à  chaque fois que tu swipes sur la vue. Tu peux imiter un carousel de cette manière en usant efficacement des Animations. J'ai crée une classe dans ce but là  qui dans mes souvenirs n'était pas compliqué.


  • Merci à  tous pour vos réponses !!


     




    Sinon tu peux créer une UIView, sur lequel tu disposes une ImageView que tu récupère à  chaque fois que tu swipes sur la vue. Tu peux imiter un carousel de cette manière en usant efficacement des Animations. J'ai crée une classe dans ce but là  qui dans mes souvenirs n'était pas compliqué.




    En réalité sur ces différentes vues apparaissent les informations du compte de l'utilisateur. Il y a donc des Labels et des ImageViews.


     


     




    Il parle certes de Carrousel, donc moi aussi au début j'ai pensé au Carrousel 3D du Finder avec les panneaux en 3D, mais en fait il ne cherche même pas à  faire de la 3D, juste une ScrollView infinie




    Oui désolé, c'est effectivement plus une scrollView infinie.


     




    Le mieux est de faire une UICollectionView horizontale effectivement... avec un numberOfItem très grand c'est une possibilité comme le propose colas2, mais comme même "très grand" ce n'est pas "infini", l'idée c'est que quand tu as fini de scroller la CollectionView (méthode de delegate "scrollViewDidScroll:" typiquement), tu modifies le contentOffset.x de ton UICollectionView pour le remettre dans un range central.




    Effectivement j'avais essayé de le faire via des UIViews classiques.  Mais comme je n'ai jamais utilisé les CollectionView, j'hésitais à  me plonger dedans.


     


    Mais bon il faut un début à  tout. Je vais fouiller de mon côté grâce à  vos nombreux conseils et je reviendrais vers vous si je rame encore ou si j'ai finalement réussi ! Encore merci !!


  • Bonjour à  tous.


     


    Alors j'ai suivi vos conseils et je me suis mis sur la doc d'Apple sur les collections views.


    J'ai finalement réussi à  faire ce que je voulais et donc je partage ma solution même si je ne l'ai pas encore mis en place dans mon application.


     


    J'ai donc 3 vue à  faire défiler de manière infini.


    Les 3 vues sont placés comme ceci


    circularProcess.png


    Le comportement que je recherche c'est que lorsque j'arrive sur la vue 3 et que je scroll une nouvelle fois, ce soit la vue 1 qui apparaisse. Du coup, comme me l'a conseillé AliGator, j'ai créé des vues superflues aux extrémités des vues 1 et 3 comme ci:


     


    newArray.png


     


    Pour faire ça, j'ai utilisé une bête NSMutableArray.


     


    Du coup lorsque l'on arrive sur la vue 3 et que l'on scroll , on arrive sur une copie de la vue 1 et on replace la scrollView au bon endroit ( méthode de UICollectionViewDelegate ) :



    -(void) scrollViewDidEndDecelerating:(UIScrollView *)scrollView
    {
    // Calculate where the collection view should be at the right-hand end item
    float contentOffsetWhenFullyScrolledRight = self.collectionView.frame.size.width * ([self.dataArray count] -1);

    if (scrollView.contentOffset.x == contentOffsetWhenFullyScrolledRight) {

    // user is scrolling to the right from the last item to the 'fake' item 1.
    // reposition offset to show the 'real' item 1 at the left-hand end of the collection view

    NSIndexPath *newIndexPath = [NSIndexPath indexPathForItem:1 inSection:0];

    [self.collectionView scrollToItemAtIndexPath:newIndexPath atScrollPosition:UICollectionViewScrollPositionLeft animated:NO];

    } else if (scrollView.contentOffset.x == 0) {

    // user is scrolling to the left from the first item to the fake 'item N'.
    // reposition offset to show the 'real' item N at the right end end of the collection view

    NSIndexPath *newIndexPath = [NSIndexPath indexPathForItem:([self.dataArray count] -2) inSection:0];

    [self.collectionView scrollToItemAtIndexPath:newIndexPath atScrollPosition:UICollectionViewScrollPositionLeft animated:NO];

    }

    }

    J'ai trouvé cette solution ici.


     


    Et je l'ai remanié à  ma sauce de mon coté.


     


    Merci pour vos conseils.


    En lisant la doc d'Apple ça m'a permit de me faire une idée de ce que je devais faire et il me manquait plus que de savoir utiliser la méthode



    scrollToItemAtIndexPath: atScrollPosition: animated:

    J'ai donc fait la recherche sur mon ami Google et c'est comme ça que j'ai trouvé l'article/tuto !


     


    Encore merci ! o:)   :-*


     


    PS: ça ne fonctionne pas encore EXACTEMENT comme je le voudrais mais c'est très bien pour l'instant !! ( Juste pas parfait )


  • AliGatorAliGator Membre, Modérateur
    décembre 2014 modifié #9
    Merci pour ce retour !

    PS : Juste pour clarifier (je sais pas si c'est expliqué correctement dans l'article que tu cites) : tu parles (et l'article aussi) d'une "copie de la vue 1", mais pour être précis, il n'est pas nécessaire de faire une copie de l'objet du modèle associé, il y a juste besoin de prévoir des UICollectionViewCell supplémentaires (+ qu'il n'y a d'objets modèle, donc), qui viendront se remplir d'après les données... du même objet modèle.

    Par exemple si tes objets modèles ce sont des "AppStoreApps" (représentant une application sur l'AppStore), tu auras peut-être un "NSArray* appsList" contenant 10 objets "AppStoreApp", représentant 10 applications différentes.
    Si tu veux ton scroll infini, pas besoin de modifier ce NSArray "appsList" pour rajouter des copies des objets AppStoreApps. Ce tableau (modèle de données) reste inchangé. Par contre c'est ton implémentation du UICollectionViewDataSource qui va demander de créer + de vues qu'il n'y a d'objets modèle (genre numberOfItemsInSection va retourner "appsList.count + 2"), et quand il va te demander la cellForRowAtIndexPath, tu vas créer / faire un "dequeue" d'une UICollectionViewCell, et la remplir avec l'objet modèle qui se trouve au bon index.
    L'astuce est juste de calculer le bon index du modèle en fonction de l'indexPath... en prenant en compte qu'il y a un décalage à  cause des vues supplémentaires avant et après.
    Ca devrait donner un truc comme :
    static NSUInteger const nbExtraViewsBefore = 1; // Nombre de vues supplémentaires à  gauche
    NSUInteger n = self.appsList.count; // Nombre d'objets modèle
    // Et là  on calcule l'index dans la liste des objets modèles, à  partir de l'indexPath
    NSUInteger index = (indexPath.item + n - nbExtraViewsBefore) % n;
    AppStoreApp* appModelForThisCell = self.appsList[index];
    Ainsi si n = 10 (tu as 10 applications dans ton modèle de données) :
    • Pour l'index / la cellule 0, ça donnera 0+10-1 = 9 = dernier élément du tableau (car les index vont de 0 à  9), pour ton élément bonus que tu as "juste avant le vrai début".
    • Pour la cellule d'index i=1 à  i=10, ça vaudra (i+10-1)%10 = (i+9)%10 = (i-1), donc ça vaudra de 0 à  9, pour les éléments principaux (les vrais)
    • Pour la cellule d'index 11, ça vaudra (11+10-1)%10 = 0 et tu reboucles sur ton premier élément pour l'élément bonus de la fin.
    Ainsi tu ne dupliques pas d'objets de ton modèle, ton modèle de données et les données qu'il contient restent les mêmes, pas besoin de rajouter d'objets modèle superflus. Par contre ce sont les vues / cell de ta UICollectionView qui ont quelques cellules en bonus, qui viendront se remplir avec les données du même objet modèle à  chaque fois que tu affiches le premier ou le dernier élément de ta liste.
  • Eh bien merci pour ce petit point technique.


     


    Je dois avouer que du coup c'est très clair. Je viens de le mettre dans ma TO DO.


    J'ai des timings plutôt serré en ce moment ( ah.... les patrons), mais c'est clairement une amélioration qui va au sommet de la pile de ma TO DO après la mise en prod.


     


    Merci  !


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