UIPageViewController,我怎么正确跳转到一个特定的页面,而不搞乱数据源指定的顺序?

我发现了一些关于如何使UIPageViewController跳转到特定页面的问题,但是我注意到跳转出现了一个额外的问题,没有一个答案似乎承认。

没有进入我的iOS应用程序的细节(这是类似于分页日历),这是我正在经历的。 我声明一个UIPageViewController ,设置当前的视图控制器,并实现一个数据源。

 // 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]; } 

编辑注:我添加了出列方法的简化代码。 即使有这个亵渎实现我有页面顺序完全相同的问题。

初始化全部按预期工作。 增量分页也可以正常工作。 问题是,如果我再次打电话jumpToDay ,秩序混乱。

如果用户在第-5天跳转到第1天,向左滚动将再次显示第-5天而不是恰当的第0天。这似乎与UIPageViewController如何保持对附近页面的引用有关,但是我找不到任何引用强制刷新caching的方法。

有任何想法吗?

11 Solutions collect form web for “UIPageViewController,我怎么正确跳转到一个特定的页面,而不搞乱数据源指定的顺序?”

编写iOS6 ,马特·纽伯格logging了这个确切的问题,实际上我发现他的解决scheme比现在接受的答案要好一些。 这个解决scheme效果很好,它具有在图像之前/之后对图像进行animation处理的负面效果,然后用所需的页面将该页面震撼地取代。 我觉得这是一个奇怪的用户体验,马特的解决scheme照顾。

 __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]; }); }]; 

所以我遇到了同样的问题,因为我需要能够“跳转”到一个页面,然后当我回到页面时发现“订单搞砸了”。 据我所知,页面视图控制器肯定是caching视图控制器,当你跳转到一个页面,你必须指定方向:正向或反向。 然后假定新的视图控制器是前一个视图控制器的“邻居”,因此当你回头时自动呈现前一个视图控制器。 我发现这只发生在使用UIPageViewControllerTransitionStyleScroll而不是UIPageViewControllerTransitionStylePageCurl 。 页面curl的风格显然不会做同样的caching,因为如果你跳转到一个页面,然后手势回来,它提供了pageViewController:viewController(Before/After)ViewController:消息到数据源,使您能够提供正确的邻居视图控制器。

解决scheme:当执行“跳转”页面时,您可以首先跳转到您跳转到的页面( animated:NO )的邻居页面,然后在该跳转的完成块中跳转到所需的页面。 这将更新caching,使得当您回头时,会显示正确的邻居页面。 缺点是你将需要创build两个视图控制器; 一个是你跳到的那个,一个是在回来之后应该显示的那个。

这里是我为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 

你可以做什么来testing这是创build一个新的“基于页面的应用程序”,并添加一个“跳转”button,将跳转到某个日历月,然后回头。 确保将过渡样式设置为滚动。

我使用这个function(我总是在风景,2页模式)

 -(void) flipToPage:(NSString * )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]; } } } 

这里是我的Swift解决scheme用于UIPageViewController子类:

假设你在viewControllerArray存储一个viewController数组,并在viewControllerArray存储当前页面索引。

  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?() } }) } } } 

我可以确认这个问题,并且只在使用UIPageViewControllerTransitionStyleScroll而不是UIPageViewControllerTransitionStylePageCurl时发生。

解决方法:为每个页面UIPageViewController setViewControllers一个循环并调用UIPageViewController setViewControllers ,直到到达所需的页面。

这使UIPageViewController中的内部数据源索引保持同步。

迅捷版的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) }) } } 

这只是解决scheme

 -(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; } 

需要注意的是,iOS 10中不再是这种情况,您不再需要使用已接受的答案解决scheme。 只要一如既往的继续。

我有一个不同的方法,如果您的网页devise为在init之后更新,应该是可能的:
当select手册页时,我更新一个标志

 - (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; } 

然后强制下一页重新加载正确的数据:

 - (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; } } 

如果你不需要animation到新的页面,我没有,下面的代码为我工作,在故事板中调用“值改变”。 而不是在视图控制器之间进行更改,而是更改与当前视图控制器关联的数据。

  - (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是在那里,所以我可以更新pageControl当我在页面之间滑动。

pageDataSource dataForPage:返回由页面显示的数据对象的数组。

我一直在为这个问题长期奋斗。 对我来说,我有一个UIPageViewController(我称之为PageController)从故事板加载,并添加一个UIViewController'ContentVC'。

我让ContentVC负责将数据加载到内容区域,让PageController负责滑动/转到/ PageIndicator更新。 ContentVC具有ivar CurrentPageIndex并将该值发送到PageController,因此PageController知道它在哪个页面上。 在我的.m文件中有PageController我有这两个方法。

请注意,我使用设置为0,所以每次PageVC重新加载它到我不想要的第一页,[self viewControllerAtIndex:0]。

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

第二种方法是PageViewController的DataSource方法。 presentationIndexForPageViewController将突出显示的点设置到右页(您想要的页面)。 请注意,如果我们在这里返回0,页面指示器将突出显示第一个点,它表示第一个页面,我们不希望这样做。

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