UIPageViewController, comment est-ce que je saute correctement à une page spécifique sans déranger l'ordre spécifié par la source de données?

J'ai trouvé quelques questions sur la façon de faire passer un UIPageViewController à une page spécifique, mais j'ai remarqué un problème supplémentaire avec le saut qu'aucune des réponses ne semble reconnaître.

Sans entrer dans les détails de mon application iOS (qui est similaire à un calendar paginé), voici ce que je vis. Je déclare un UIPageViewController , définit le controller de vue actuel et implémente une source de données.

 // end of the init method pageViewController = [[UIPageViewController alloc] initWithTransitionStyle:UIPageViewControllerTransitionStyleScroll navigationOrientation:UIPageViewControllerNavigationOrientationHorizontal options:nil]; pageViewController.dataSource = self; [self jumpToDay:0]; } //... - (void)jumpToDay:(NSInteger)day { UIViewController *controller = [self dequeuePreviousDayViewControllerWithDaysBack:day]; [pageViewController setViewControllers:@[controller] direction:UIPageViewControllerNavigationDirectionForward animated:YES completion:nil]; } - (UIViewController *)pageViewController:(UIPageViewController *)pageViewController viewControllerAfterViewController:(UIViewController *)viewController { NSInteger days = ((THDayViewController *)viewController).daysAgo; return [self dequeuePreviousDayViewControllerWithDaysBack:days + 1]; } - (UIViewController *)pageViewController:(UIPageViewController *)pageViewController viewControllerBeforeViewController:(UIViewController *)viewController { NSInteger days = ((THDayViewController *)viewController).daysAgo; return [self dequeuePreviousDayViewControllerWithDaysBack:days - 1]; } - (UIViewController *)dequeuePreviousDayViewControllerWithDaysBack:(NSInteger)days { return [[THPreviousDayViewController alloc] initWithDaysAgo:days]; } 

Modifier Note: J'ai ajouté du code simplifié pour la méthode dequeuing. Même avec cette implémentation blasphématoire, j'ai exactement le même problème avec l'ordre des pages.

L'initialisation fonctionne comme prévu. La pagination incrémentielle fonctionne aussi bien. Le problème est que si j'appelle à nouveau jumpToDay , la command est brouillée.

Si l'user est sur le jour -5 et saute au jour 1, un défilement vers la gauche révélera le jour -5 à nouveau au lieu du jour 0 approprié. Cela semble avoir quelque chose à voir avec comment UIPageViewController garde des references aux pages proches, mais je ne trouve aucune reference à une méthode qui l'obligerait à rafraîchir son cache.

Des idées?

Programmation iOS6 , par Matt Neuburg documente ce problème exact, et j'ai effectivement trouvé que sa solution se sent un peu mieux que la réponse actuellement acceptée. Cette solution, qui fonctionne très bien, a un effet secondaire négatif d'animation sur l'image avant / après, puis en remplaçant avec discernement cette page par la page désirée. J'avais l'printing que c'était une expérience user étrange, et la solution de Matt s'occupe de ça.

 __weak UIPageViewController* pvcw = pvc; [pvc setViewControllers:@[page] direction:UIPageViewControllerNavigationDirectionForward animated:YES completion:^(BOOL finished) { UIPageViewController* pvcs = pvcw; if (!pvcs) return; dispatch_async(dispatch_get_main_queue(), ^{ [pvcs setViewControllers:@[page] direction:UIPageViewControllerNavigationDirectionForward animated:NO completion:nil]; }); }]; 

J'ai donc rencontré le même problème que vous, où je devais être capable de «sauter» sur une page, puis de find l'ordre «foiré» lorsque j'ai fait un geste de return d'une page. Pour autant que j'ai pu le dire, le controller de vue de page met définitivement en cache les controllers de vue et lorsque vous «sautez» sur une page, vous devez spécifier la direction: vers l'avant ou vers l'arrière. Il suppose ensuite que le nouveau controller de vue est un «voisin» du controller de vue précédent et présente donc automagiquement le controller de vue précédent lorsque vous effectuez un geste de return. J'ai trouvé que cela se produit uniquement lorsque vous utilisez UIPageViewControllerTransitionStyleScroll et non UIPageViewControllerTransitionStylePageCurl . Le style curl de la page ne fait apparemment pas la même caching puisque si vous "sautez" sur une page, puis returnnez le pageViewController:viewController(Before/After)ViewController: message à la source de données vous permettant de fournir la vue de voisin correcte manette.

Solution: Lorsque vous effectuez un «saut» vers la page, vous pouvez d'abord sauter à la page voisine de la page ( animated:NO ) à laquelle vous sautez, puis dans le bloc d'achèvement de ce saut, passer à la page désirée. Cela mettra à jour le cache de sorte que lorsque vous effectuez un geste de return, la page de voisin correcte sera affichée. L'inconvénient est que vous devrez créer deux controllers de vue; celui auquel vous êtes en train de sauter et celui qui devrait être affiché après avoir fait un geste.

Voici le code d'une catégorie que j'ai écrite pour UIPageViewController :

 @implementation UIPageViewController (Additions) - (void)setViewControllers:(NSArray *)viewControllers direction:(UIPageViewControllerNavigationDirection)direction invalidateCache:(BOOL)invalidateCache animated:(BOOL)animated completion:(void (^)(BOOL finished))completion { NSArray *vcs = viewControllers; __weak UIPageViewController *mySelf = self; if (invalidateCache && self.transitionStyle == UIPageViewControllerTransitionStyleScroll) { UIViewController *neighborViewController = (direction == UIPageViewControllerNavigationDirectionForward ? [self.dataSource pageViewController:self viewControllerBeforeViewController:viewControllers[0]] : [self.dataSource pageViewController:self viewControllerAfterViewController:viewControllers[0]]); [self setViewControllers:@[neighborViewController] direction:direction animated:NO completion:^(BOOL finished) { [mySelf setViewControllers:vcs direction:direction animated:animated completion:completion]; }]; } else { [mySelf setViewControllers:vcs direction:direction animated:animated completion:completion]; } } @end 

Ce que vous pouvez faire pour tester ceci est de créer une nouvelle 'application basée sur une page' et d'append un button 'goto' qui va 'sauter' à un certain mois calendaire puis revenir en arrière. Veillez à définir le style de transition pour faire défiler.

J'utilise cette fonction (je suis toujours en mode paysage, 2 pages)

 -(void) flipToPage:(NSSsortingng * )index { int x = [index intValue]; LeafletPageContentViewController *theCurrentViewController = [self.pageViewController.viewControllers objectAtIndex:0]; NSUInteger retreivedIndex = [self indexOfViewController:theCurrentViewController]; LeafletPageContentViewController *firstViewController = [self viewControllerAtIndex:x]; LeafletPageContentViewController *secondViewController = [self viewControllerAtIndex:x+1 ]; NSArray *viewControllers = nil; viewControllers = [NSArray arrayWithObjects:firstViewController, secondViewController, nil]; if (retreivedIndex < x){ [self.pageViewController setViewControllers:viewControllers direction:UIPageViewControllerNavigationDirectionForward animated:YES completion:NULL]; } else { if (retreivedIndex > x ){ [self.pageViewController setViewControllers:viewControllers direction:UIPageViewControllerNavigationDirectionReverse animated:YES completion:NULL]; } } } 

Voici ma solution Swift à utiliser pour les sous-classs de UIPageViewController :

Supposons que vous stockiez un tableau de viewControllers dans viewControllerArray et l'index de page en updateCurrentPageIndex dans updateCurrentPageIndex .

  private func slideToPage(index: Int, completion: (() -> Void)?) { let tempIndex = currentPageIndex if currentPageIndex < index { for var i = tempIndex+1; i <= index; i++ { self.setViewControllers([viewControllerArray[i]], direction: UIPageViewControllerNavigationDirection.Forward, animated: true, completion: {[weak self] (complete: Bool) -> Void in if (complete) { self?.updateCurrentPageIndex(i-1) completion?() } }) } } else if currentPageIndex > index { for var i = tempIndex - 1; i >= index; i-- { self.setViewControllers([viewControllerArray[i]], direction: UIPageViewControllerNavigationDirection.Reverse, animated: true, completion: {[weak self] (complete: Bool) -> Void in if complete { self?.updateCurrentPageIndex(i+1) completion?() } }) } } } 

Je peux confirmer ce problème, et que cela se produit uniquement lors de l'utilisation de UIPageViewControllerTransitionStyleScroll et non UIPageViewControllerTransitionStylePageCurl .

Solution: UIPageViewController setViewControllers une boucle et appelez UIPageViewController setViewControllers pour chaque tour de page, jusqu'à ce que vous atteigniez la page souhaitée.

Cela permet de synchroniser l'index interne de la source de données dans UIPageViewController.

Version rapide de la réponse de djibouti33:

 weak var pvcw = pageViewController pageViewController!.setViewControllers([page], direction: UIPageViewControllerNavigationDirection.Forward, animated: true) { _ in if let pvcs = pvcw { dispatch_async(dispatch_get_main_queue(), { pvcs.setViewControllers([page], direction: UIPageViewControllerNavigationDirection.Forward, animated: false, completion: nil) }) } } 

C'est seulement la solution

 -(void)buyAction { isFromBuy = YES; APPChildViewController *initialViewController = [self viewControllerAtIndex:4]; viewControllers = [NSArray arrayWithObject:initialViewController]; [self.pageController setViewControllers:viewControllers direction:UIPageViewControllerNavigationDirectionForward animated:NO completion:nil]; } -(NSInteger)presentationIndexForPageViewController:(UIPageViewController *)pageViewController { if (isFromBuy) { isFromBuy = NO; return 5; } return 0; } 

Il est important de noter que ce n'est plus le cas dans iOS 10 et que vous n'avez plus besoin d'utiliser la solution de réponse acceptée. Continuez comme toujours.

J'ai eu une approche différente, devrait être possible si vos pages sont conçues pour être mises à jour après init:
Quand une page de manuel est sélectionnée, je mets à jour un drapeau

 - (void)scrollToPage:(NSInteger)page animated:(BOOL)animated { if (page != self.currentPage) { [self setViewControllers:@[[self viewControllerForPage:page]] direction:(page > self.currentPage ? UIPageViewControllerNavigationDirectionForward : UIPageViewControllerNavigationDirectionReverse) animated:animated completion:nil]; self.currentPage = page; self.forceReloadNextPage = YES; // to override view controller automatic page cache } } - (ScheduleViewController *)viewControllerForPage:(NSInteger)page { CustomViewController * scheduleViewController = [self.storyboard instantiateViewControllerWithIdentifier:@"CustomViewController"]; scheduleViewController.view.tag = page; // keep track of pages using view.tag property scheduleViewController.data = [self dataForPage:page]; if (self.currentViewController) scheduleViewController.calendarLayoutHourHeight = self.currentViewController.calendarLayoutHourHeight; return scheduleViewController; } 

puis forcez la page suivante pour recharger avec datatables correctes:

 - (void)pageViewController:(UIPageViewController *)pageViewController willTransitionToViewControllers:(NSArray *)pendingViewControllers { CustomViewController * nextViewController = [pendingViewControllers lastObject]; // When manual scrolling occurs, the next page is loaded from UIPageViewController cache // and must be refreshed if (self.forceReloadNextPage) { // calculate the direction of the scroll to know if to load next or previous page NSUInteger page = self.currentPage + 1; if (self.currentPage > nextViewController.view.tag) page = self.currentPage - 1; nextViewController.data = [self dataForPage:page]; self.forceReloadNextPage = NO; } } 

Si vous n'avez pas besoin d'animer la nouvelle page, comme je ne l'ai pas fait, le code suivant a fonctionné pour moi, appelé "Valeur modifiée" dans le storyboard. Au lieu de changer entre les controllers de vue, je change datatables associées au controller de vue actuel.

  - (IBAction)pageControlCurrentPageDidChange:(id)sender { self.currentIndex = self.pageControl.currentPage; MYViewController *currentPageViewController = (MYViewController *)self.pageViewController.viewControllers.firstObject; currentPageViewController.pageData = [self.pageDataSource dataForPage:self.currentIndex]; [currentPageViewController updateDisplay]; } 

currentIndex est là pour que je puisse mettre à jour le currentPage de pageControl quand je glisse entre les pages.

pageDataSource dataForPage: renvoie un tableau d'objects de données affichés par les pages.

Je luttais avec ce problème depuis longtime moi-même. Pour moi, j'avais un chargement de UIPageViewController (je l'ai appelé PageController) à partir de storyboard et sur ce que j'ajoute un UIViewController 'ContentVC'.

Je laisse le ContentVC s'occuper des données à charger dans la zone de contenu et laisser PageController s'occuper des mises à jour glissantes / goto / PageIndicator. Le ContentVC a un ivar CurrentPageIndex et envoie cette valeur à PageController pour que PageController sache sur quelle page il se trouve. Dans mon file .m qui a PageController j'ai ces deux methods.

Notez que j'ai utilisé set à 0 et donc chaque fois que PageVC recharge il va à la première page que je ne veux pas, [self viewControllerAtIndex: 0].

 - (void)setPageForward { ContentVC *FirstVC = [self viewControllerAtIndex:[CurrentPageIndex integerValue]]; NSArray *viewControllers = @[FirstVC]; [PageController setViewControllers:viewControllers direction:UIPageViewControllerNavigationDirectionForward animated:NO completion:nil]; } 

Cette deuxième méthode est la méthode DataSource de PageViewController. presentationIndexForPageViewController mettra le point en surbrillance à la bonne page (la page que vous voulez). Notez que si nous returnnons ici 0 l'indicateur de page mettra en évidence le premier point qui indique la première page et nous ne voulons pas cela.

 - (NSInteger)presentationIndexForPageViewController:(UIPageViewController *)pageViewController { return [CurrentPageIndex integerValue]; } 

Voici une version mise à jour de Swift 3+ de la réponse par @ djibouti33 avec une syntaxe nettoyée.

 weak var weakPageVc = pageVc pageVc.setViewControllers([page], direction: .forward, animated: true) { finished in guard let pageVc = weakPageVc else { return } DispatchQueue.main.async { pageVc.setViewControllers([page], direction: .forward, animated: false) } }