[Résolu] viewControllerAfterViewController appelle aussi viewControllerBeforeViewController

busterTheobusterTheo Membre
juillet 2015 modifié dans API UIKit #1

Bonjour,


quelqu'un a-t-il une idée pourquoi la méthode viewControllerAfterViewController appelle aussi la méthode  viewControllerBeforeViewController ?


 


J'ai fait des tests avec des println, j'ai essayé plein de trucs depuis deux jours, j'ai cherché sur le web.


 


Et je ne trouve rien qui explique comment contourner ce p... de bug.


 


Au passage, je ne sais pas quoi mettre dans le willTransitionToViewControllers


 


Je met une vidéo ou on voit le projet ainsi que le code et les println


 


Je viens de me rendre compte qu'on ne voyait pas bien le code, donc je le met ici.



// MARK: - UIPageViewControllerDataSource

func pageViewController(pageViewController: UIPageViewController, viewControllerBeforeViewController viewController: UIViewController) -> UIViewController? {
if let controller = viewController as? ContentEtapesViewController {

println("controller.itemIndex BEFORE = \(controller.itemIndex)")

if controller.itemIndex > 0 {
pageControl.currentPage = controller.itemIndex
levelLabel.text = levels[pageControl.currentPage]
return controllers[controller.itemIndex - 1]
} else {
pageControl.currentPage = 0
levelLabel.text = levels[0]

return nil
}
}
return nil
}

func pageViewController(pageViewController: UIPageViewController, viewControllerAfterViewController viewController: UIViewController) -> UIViewController? {
if let controller = viewController as? ContentEtapesViewController {

println("controller.itemIndex AFTER = \(controller.itemIndex)")

if controller.itemIndex < controllers.count - 1 {
pageControl.currentPage = controller.itemIndex
levelLabel.text = levels[pageControl.currentPage]
return controllers[controller.itemIndex + 1]
} else {
pageControl.currentPage = controllers.count
levelLabel.text = levels[pageControl.numberOfPages-1]
return nil
}
}
return nil
}


// MARK: - UIPageViewControllerDelegate

func pageViewController(pageViewController: UIPageViewController, willTransitionToViewControllers pendingViewControllers: [AnyObject]) {

}

func pageViewController(pageViewController: UIPageViewController, didFinishAnimating finished: Bool, previousViewControllers: [AnyObject], transitionCompleted completed: Bool) {
if !completed {
return
}
}

Voici le lien


 


On remarque que :


- La première fois que l'on avance, ça fait - after - before - after -> Pas normal


- La seconde fois,  ça fait - after -> Normal


- Puis quand je recule une première fois - ça ne fait pas le before - Pas normal


- Puis la seconde fois que je recule - ça fait deux fois before - Pas normal


 


Merci d'avance pour la solution miracle.


Réponses

  • Il est indiqué dans la documentation du protocole UIPageViewControllerDataSource que ces méthodes :


    1/ sont parfois appelées simultanément


    2/ ne sont pas systématiquement appelées à  chaque changement de page


     


    Le comportement que tu décris n'est pas contraire au comportement documenté. Il n'y a pas de bug.


  • AliGatorAliGator Membre, Modérateur
    À mon avis c'est pour une histoire de cache et de réactivité : ils prechargent toujours la page d'avant et la page d'après la page courante histoire que le changement de page vers la précédente ou la suivante se fasse sans à -coup
  • Merci pour vos réponses.


     


    Ah oui c'est vrai, j'avais vu ça dans la doc, y' a 2 mois.


     


    Le problème est comment détourner ce mécanisme.


     


    Parce que, j'ai besoin, sur le premier écran, de faire, lors d'un recul, un pushViewController vers un autre controller que le pageViewController.


    J'avais donc rajouté un gestureRecognizer sur le premier écran du pageViewController, pour faire le push.


     


    Et donc, au premier after, il prenait directement le push associé au before, alors qu'il ne devrait que aller au second écran du pageViewcontroller.


     


    J'ai donc rajouté plein de contrôles au sein des deux méthodes, et je n'y suis pas parvenu.


    C'est pourquoi, j'ai supprimé le gestureRecognizer, et suis reparti dans les méthodes before/after, avec des contrôles sur le numéro de l'écran.


     


    Et donc, je tourne en rond...


     


     


    Et pour ces histoire de cache, on peut le supprimer, non ?

  • Je pense que si le mécanisme proposé par UIPageViewController ne te convient il suffit d'écrire un autre contrôleur de vue qui fasse ce que tu veux.


  • AliGatorAliGator Membre, Modérateur


    Le problème est comment détourner ce mécanisme.

    Si tu commences à  chercher à  détourner un mécanisme c'est qu'il y a déjà  quelque chose qui cloche dans la démarche 😄

    Parce que, j'ai besoin, sur le premier écran, de faire, lors d'un recul, un pushViewController vers un autre controller que le pageViewController..

    Bah donc tu fais ce push dans le viewDidAppear de la page précédente dans ce cas, ça me semble la méthode la plus adaptée ça, non ?

  •  


     


    Je pense que si le mécanisme proposé par UIPageViewController ne te convient il suffit d'écrire un autre contrôleur de vue qui fasse ce que tu veux.

     


    L'exigence du client est de naviguer avec des swipes entre plusieurs pagegeViewControllers :


    - Du premier écran d'un pageView vers le dernier écran du pageview suivant (car j'ai plusieurs pageViews qui s'enchainent), ou vers un autre controller.


    - Et du dernier écran d'un pageView vers le premier écran du pageView suivant, ou vers un autre controller.


     


    Rien d'illogique dans tout ça, enfin, pour moi.


     


    Il est trop tard pour abandonner le pageView.


     


    Je ne veux pas détourner le mécanisme. Je me suis mal exprimé, et je n'ai ni l'ambition, ni les capacité de faire une chose pareille, mais je cherche une parade à  ce pb de cache.


     



     


     


    Bah donc tu fais ce push dans le viewDidAppear de la page précédente dans ce cas, ça me semble la méthode la plus adaptée ça, non ?

     


    De quelle page précédente parles-tu ?


    1- Du premier écran du pageView (Le point de départ) ?


    Là , je ne peux pas mettre une détection de gesture dans le didAppear


    2- Dans l'autre controller hors du pageView (la destination) ?


    Ben non, puisque sur cette page je n'ai pas de pb pour entrer dans le pageview, puisque je fais un push qui va dans le premier écran du pageview en allant tout simplement sur le pageview


     


    En gros la double question est ?


     


    Comment à  partir d'un écran d'un pageView, atteindre un autre controller que le pageView ?


    Et, comment, à  partir d'un autre controller que le pageView, atteindre n'importe quel écran d'un pageView ?


    Et le mix des deux : Comment à  partir d'un écran d'un pageView, atteindre n'importe quel écran d'un autre pageView ?


     


    C'est sportif tout ça  :o


     


    J'ai essayer de placer le gesture à  plusieurs endroits, et ça bloque toujours.


     


    Je suis certain qu'il y a une astuce, c'est obligé. Cela m'étonnerai que Apple interdise ce type de navigation.


     


    J'ai même essayé en mettant des index qui s'incrémentent dans les méthode datasource, puis de procéder à  des contrôle pour empêcher que le after appelle le before et inversement, et ça bloque toujours.


     


    Merci pour votre participation.


  • AliGatorAliGator Membre, Modérateur

    De quelle page précédente parles-tu ?
    1- Du premier écran du pageView (Le point de départ) ?
    Là , je ne peux pas mettre une détection de gesture dans le didAppear
    2- Dans l'autre controller hors du pageView (la destination) ?
    Ben non, puisque sur cette page je n'ai pas de pb pour entrer dans le pageview, puisque je fais un push qui va dans le premier écran du pageview en allant tout simplement sur le pageview

    En fait je ne suis pas bien sûr de comprendre ton besoin ou surtout ta démarche.

    Pourquoi tu parles de "mettre une détection de gesture dans le didAppear" ?!? Quelle drôle d'idée, je n'ai jamais parlé de ça moi...

    Parce que, j'ai besoin, sur le premier écran, de faire, lors d'un recul, un pushViewController vers un autre controller que le pageViewController..

    Moi ce que j'ai compris de cette phrase c'est que tu avais l'air de dire que tu as des pages P1, P2, P3 et que tu voulais que quand tu reviens en arrière de la page P2 à  la page P1 par exemple, tu veux que P1 fasses un pushViewController(autreVC), c'est ça ?

    Donc si tu veux que quand P1 apparaisse tu pousses un autre VC, pourquoi ne pas mettre le code qui appelle pushViewController(autreVC) dans le viewDidAppear de P1, de sorte que quand P1 apparaisse, ça fasse le push, puisque c'est exactement ce que tu avais l'air de demander ?

    Après, si tu ne fais que ça dans ton viewDidAppear de P1, évidemment ça va aussi l'afficher au premier affichage de P1, même si ce n'est pas parce que tu reviens en arrière sur P1 alors que tu étais sur P2 avant.

    Mais détecter ce cas particulier ne doit certainement pas se faire avec des Gestures pour moi, car ce n'est pas la bonne approche : déjà  c'est bête de créer ton propre UIGestureRec plutôt que d'utiliser celui déjà  existant de UIPageViewController, mais surtout, utiliser les Gestures ça répondrait au besoin : "je veux faire une action quand il y a telle Gesture" pas "quand il va à  la page précédente". Qui te dit que demain l'utilisateur ne pourrait pas changer de page et aller à  la page précédente par un autre moyen qu'avec la gesture du UIPageController ? Par exemple avec un tap sur le UIPageControl, typiquement...

    Pour moi il faut plutôt que ton UIPageController garde par exemple le numéro de la dernière page affichée, ou une référence vers le dernier UIViewController affiché, et dans le viewDidAppear de chaque page, tu t'en sers pour regarder si la page d'où on vient est une page qui est après (genre "if (previousPageNumber > currentPageNumber)") ce qui voudrait dire qu'on est revenu en arrière, et donc que tu dois faire ton "pushViewControlelr(autreVC)".
  • busterTheobusterTheo Membre
    juillet 2015 modifié #9

    Merci AliGator pour tes efforts.


     


    Je reconnais que c'est confus.


     



     


     


    Moi ce que j'ai compris de cette phrase c'est que tu avais l'air de dire que tu as des pages P1, P2, P3 et que tu voulais que quand tu reviens en arrière de la page P2 à  la page P1 par exemple, tu veux que P1 fasses un pushViewController(autreVC), c'est ça ?

    Non :


    Je veux, quand je suis en page P1 du pageView, tout simplement, allez dans un autre controller que le pageView en swipant comme si j'allais à  la page précédente (qui n'existe pas, bien sur, car on est en P1).


     



     


     


    déjà  c'est bête de créer ton propre UIGestureRec plutôt que d'utiliser celui déjà  existant de UIPageViewController

    Je suis carrément et absolument d'accord avec toi. C'est ce que je disais au début. Mais en essayant donc, comme ceci (bon, là  c'est un popToRoot, car c'est vers le premier controller du projet, mais ce sera ensuite que des pushViews) :


     


    Dans la méthode before



    if controller.itemIndex == 0 {
    if pagesNavigationController == nil {
    pagesNavigationController = UINavigationController(rootViewController: etape0ViewController)
    }
    pagesNavigationController.popToRootViewControllerAnimated(true)
    }

    Et ça marche impeccable, mais le problème est que la méthode after (comme elle appelle aussi la méthode before), eh bien ne fait pas le after mais fait le before du premier écran et fait le popToRoot ou le push - C'est trop con comme truc.


  • En gros, comment aller de P1 (d'un pageView) au P5 (le dernier) d'un autre pageView ?


    Et inversement, et aussi carrément, pourquoi pas d'un P1 vers vers un P3 d'un autre pageView ?


     


    En tout cas, je vais creuser ton idée de 



    if (previousPageNumber > currentPageNumber
  • Bon j'ai placé :



    println("pageControl.currentPage BEFORE = \(pageControl.currentPage)")

    dans la méthode "before"


     


    et :



    println("pageControl.currentPage AFTER = \(pageControl.currentPage)")

    dans la méthode "after"


     


    et le résultat est n'importe quoi.


     


    Il répond quand il veut, et surtout pas toujours la même valeur - pfff


     


    Donc impossible de faire un test là -dessus non plus, comme pour le controller.itemIndex.


  • Bon, je suis enfin presque près du but.


    Avec les méthodes delegate.


    Je peux aller dans les deux sens, mais après le P2 vers P1, il enchaine directement le popToRoot.


    Je dois faire juste un  p'tit réglages dans mes variables de contrôles.


     


    Voici mon code. Qu'en penses-tu ?


    Lien vidéo courte, plus loin.



    // MARK: - UIPageViewControllerDataSource

    func pageViewController(pageViewController: UIPageViewController, viewControllerBeforeViewController viewController: UIViewController) -> UIViewController? {

    println("BEFORE A = \(beforeVC)")

    if afterVC == 0 {

    if let controller = viewController as? ContentEtapesViewController {
    beforeVC = beforeVC + 1

    println("BEFORE B = \(beforeVC)")

    if controller.itemIndex > 0 {
    pageControl.currentPage = controller.itemIndex
    levelLabel.text = levels[pageControl.currentPage]
    return controllers[controller.itemIndex - 1]
    } else {
    pageControl.currentPage = 0
    levelLabel.text = levels[0]

    pagesNavigationController.popToRootViewControllerAnimated(true)

    return nil
    }
    }
    }
    return nil
    }

    func pageViewController(pageViewController: UIPageViewController, viewControllerAfterViewController viewController: UIViewController) -> UIViewController? {

    println("AFTER A = \(afterVC)")

    if beforeVC == 0 {

    if let controller = viewController as? ContentEtapesViewController {
    afterVC = afterVC + 1

    println("AFTER B = \(afterVC)")

    if controller.itemIndex < controllers.count - 1 {
    pageControl.currentPage = controller.itemIndex
    levelLabel.text = levels[pageControl.currentPage]
    return controllers[controller.itemIndex + 1]
    } else {
    pageControl.currentPage = controllers.count
    levelLabel.text = levels[pageControl.numberOfPages-1]
    return nil
    }
    }
    }
    return nil
    }


    // MARK: - UIPageViewControllerDelegate

    func pageViewController(pageViewController: UIPageViewController, didFinishAnimating finished: Bool, previousViewControllers: [UIViewController], transitionCompleted completed: Bool) {
    afterVC = 0
    beforeVC = 0
    println("BEFORE didFinishAnimating = \(beforeVC)")
    println("AFTER didFinishAnimating = \(afterVC)")

    }

    Et une petite vidéo ici


  • DrakenDraken Membre
    juillet 2015 modifié #13

    A mon avis, Before ne correspond pas forcément à  la page n-1, mais à  une page située avant la page courante (n-quelque chose). De même pour After (n+quelque chose). Le UIPageView doit construire et détruire les pages à  la volée, en fonction de la mémoire disponible et de la vitesse de lecture de l'utilisateur. Il peut très bien construire 2 ou 3 pages à  l'avance, selon sa prédiction sur les demandes de l'utilisateur.


     


    Enfin c'est juste une hypothése basée sur ce que j'ai vu du fonctionnement de UITableView, l'équivalent vertical de UIPageView.



  •  


     


    A mon avis, Before ne correspond pas forcément à  la page n-1, mais à  une page située avant la page courante (n-quelque chose

    Ouais, je pense un peu comme toi.


     


    Je pense être sur la bonne voie, avec la méthode delegate et mes variables cafter" et "before".


     


    Qu'en penses-tu ?


     


    C'est correct au niveau d'apple ?

  • AliGatorAliGator Membre, Modérateur
    Ton code ne me semble pas cohérent car les méthodes pageController:viewControllerBefore: et pageController:viewControllerAfter: ne sont sensées être que des "getters", il te demande une page et tu lui renvoies, mais *tu ne sais pas pourquoi il te la demande* (ça peut être par ce que tu vas effectivement à  cette page car l'utilisateur a swipé mais ça peut être le pageController qui te le demande juste en prévision. Ces méthodes ne doivent donc contenir que du code qui retourne une valeur, pas qui fait une action tel changer un état (et encore moins présenter un autre VC). Juste un code qui fait d'éventuels calculs, crée ou récupère le ViewController à  retourner, le configure et le retourne, mais ce code n'est rien sensé modifier (n'est pas sensé changer l'état de self en changeant ses propriétés par exemple ça n'aurait pas de sens) ni faire d'action.


    C'est un peu comme si tu implémentais une méthode "-(NSString*)currentUserName;" qui était sensée retourner le nom de l'utilisateur courant... dans le code de cette méthode tu ne vas pas présenter des VC ou chant des propriétés, ce n'est pas une méthode d'action mais une méthode qui est juste sensée retourner une valeur.


    Si tu as des actions à  prévoir (comme justement ton presentViewController ou autre), c'est dans la méthode de delegate adéquate qu'il faut le faire qui décrit quand il y a un changement de page


    Ou alors effectivement maintenant y que je comprend un peu mieux ton besoin si la vraie demande c'est de faire un popToRoot quand on swipe vers la gauche alors qu'on est sur la page P1, s'attacher aux Gestures existantes du PVC.
  • Ouah.



     


     


    Ces méthodes ne doivent donc contenir que du code qui retourne une valeur etc.

    Donc, ok, je suis ton conseil. Merci. Je ne savais pas tout ça.


     


    Ce code de base est bon, quand même ?



    // MARK: - UIPageViewControllerDataSource

    func pageViewController(pageViewController: UIPageViewController, viewControllerBeforeViewController viewController: UIViewController) -> UIViewController? {
    if let controller = viewController as? ContentEtapesViewController {
    if controller.itemIndex > 0 {
    pageControl.currentPage = controller.itemIndex
    levelLabel.text = levels[pageControl.currentPage]
    return controllers[controller.itemIndex - 1]
    } else {
    pageControl.currentPage = 0
    levelLabel.text = levels[0]
    }
    }
    return nil
    }

    func pageViewController(pageViewController: UIPageViewController, viewControllerAfterViewController viewController: UIViewController) -> UIViewController? {
    if let controller = viewController as? ContentEtapesViewController {
    if controller.itemIndex < controllers.count - 1 {
    pageControl.currentPage = controller.itemIndex
    levelLabel.text = levels[pageControl.currentPage]
    return controllers[controller.itemIndex + 1]
    } else {
    pageControl.currentPage = controllers.count
    levelLabel.text = levels[pageControl.numberOfPages-1]
    }
    }
    return nil
    }

    func presentationCountForPageViewController(pageViewController: UIPageViewController) -> Int {
    return controllers.count
    }

    func presentationIndexForPageViewController(pageViewController: UIPageViewController) -> Int {
    return 0
    }

    // MARK: - UIPageViewControllerDelegate

    func pageViewController(pageViewController: UIPageViewController!, didFinishAnimating finished: Bool, previousViewControllers: [AnyObject], transitionCompleted completed: Bool) {
    if !completed {
    return
    }
    }


     


     


    s'attacher aux Gestures existantes du PVC

    Heu, j'ai cherché" sur le web, j'ai rien trouvé à  part ça (sur la page d'apple), et je vois pas ce que je peux en faire.



     


    gestureRecognizers Property

    An array of UIGestureRecognizer objects that are configured to handle user interaction. (read-only)




    Declaration

    SWIFT


    var gestureRecognizers: [AnyObject] { get }






    Discussion

    These gesture recognizers are initially attached to a view in the page view controller's hierarchy. To change the region of the screen in which the user can navigate using gestures, they can be placed on another view.




     


    je sais utiliser les gesture comme ça



    var swipeRightToLeftPanRecognizer: UIPanGestureRecognizer = UIPanGestureRecognizer(target: self , action:"swipeNextEtape:")
    self.view.addGestureRecognizer(swipeRightToLeftPanRecognizer)

    et



    func swipeNextEtape(recognizer:UIPanGestureRecognizer) {
    .....
    }

    Mais comment jouer avec les gesture sur un pageView ?


  • busterTheobusterTheo Membre
    juillet 2015 modifié #17

    Bon, après avoir grillé un bon million de neurones, j'ai enfin réussi. Pfff.


    C'est énervant, parce que c'est tout con, j'en étais sûr.


    Voilà  le code.


     


    J'ajoute le gesture recognizer sur le premier écran du PVC :



    func populateControllersArray() {
    for i in 0...4 {
    let controller = storyboard!.instantiateViewControllerWithIdentifier("Etape1SbLevel\(i+1)") as ContentEtapesViewController
    controller.itemIndex = i
    controllers.append(controller)
    }
    swipeLeftToRightPanRecognizer = UIPanGestureRecognizer(target: self , action:"swipePrevEtape:")
    controllers[0].view.addGestureRecognizer(swipeLeftToRightPanRecognizer)
    }

    Puis la fonction associée :



    // Prev Etape
    func swipePrevEtape(recognizer:UIPanGestureRecognizer) {
    let gestureIsDraggingFromRLeftToRight = (recognizer.velocityInView(view).x > 0)
    if gestureIsDraggingFromRLeftToRight {
    if recognizer.state == .Ended {
    pagesNavigationController.popToRootViewControllerAnimated(true)
    }
    }
    let gestureIsDraggingFromRightToLeft = (recognizer.velocityInView(view).x < 0)
    if gestureIsDraggingFromRightToLeft {
    if recognizer.state == .Ended {
    if !controllers.isEmpty {
    pageViewController.setViewControllers([controllers[1]], direction: UIPageViewControllerNavigationDirection.Forward, animated: true, completion: nil)
    }
    }
    }
    }

    Et puis je remet les méthodes datasource :



    // MARK: - UIPageViewControllerDataSource

    func pageViewController(pageViewController: UIPageViewController, viewControllerBeforeViewController viewController: UIViewController) -> UIViewController? {
    if let controller = viewController as? ContentEtapesViewController {
    if controller.itemIndex > 0 {
    pageControl.currentPage = controller.itemIndex
    levelLabel.text = levels[pageControl.currentPage]
    return controllers[controller.itemIndex - 1]
    } else {
    pageControl.currentPage = 0
    levelLabel.text = levels[0]
    }
    }
    return nil
    }

    func pageViewController(pageViewController: UIPageViewController, viewControllerAfterViewController viewController: UIViewController) -> UIViewController? {
    if let controller = viewController as? ContentEtapesViewController {
    if controller.itemIndex < controllers.count - 1 {
    pageControl.currentPage = controller.itemIndex
    levelLabel.text = levels[pageControl.currentPage]
    return controllers[controller.itemIndex + 1]
    } else {
    pageControl.currentPage = controllers.count
    levelLabel.text = levels[pageControl.numberOfPages-1]
    }
    }
    return nil
    }

    Donc, encore merci pour le p'tit cours au passage.


    Je met résolu


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