Iphone UIPageViewController,如何在不破坏数据源指定顺序的情况下正确跳转到特定页面?

Iphone UIPageViewController,如何在不破坏数据源指定顺序的情况下正确跳转到特定页面?,iphone,objective-c,ios,uipageviewcontroller,Iphone,Objective C,Ios,Uipageviewcontroller,我发现了一些关于如何使UIPageViewController跳转到特定页面的问题,但我注意到跳转的另一个问题,这些问题的答案似乎都不承认 不必详细介绍我的iOS应用程序(类似于分页日历),下面是我的体验。我声明一个UIPageViewController,设置当前视图控制器,并实现一个数据源 // end of the init method pageViewController = [[UIPageViewController alloc] initWith

我发现了一些关于如何使
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
如何保留对附近页面的引用有关,但我找不到任何对方法的引用,这些引用会迫使它刷新缓存

有什么想法吗?

我使用这个功能(我总是处于横向、两页模式)

-(void)flipToPage:(NSString*)索引{
intx=[索引intValue];
传单PageContentViewController*CurrentViewController=[self.pageViewController.ViewController对象索引:0];
NSUInteger RetrievedIndex=[self indexOfViewController:CurrentViewController];
传单PageContentViewController*firstViewController=[self-viewControllerAtIndex:x];
传单PageContentViewController*secondViewController=[self-viewControllerAtIndex:x+1];
NSArray*视图控制器=零;
viewControllers=[NSArray arrayWithObjects:firstViewController,secondViewController,nil];
如果(检索索引x){
[self.pageViewController设置ViewController:ViewController方向:UIPageViewControllerNavigationDirection反向动画:是完成:NULL];
} 
}
} 

所以我遇到了与您相同的问题,我需要能够“跳转”到一个页面,然后在我回指页面时发现“顺序混乱”。据我所知,页面视图控制器肯定在缓存视图控制器,当您“跳转”到页面时,必须指定方向:前进或后退。然后,它假设新的视图控制器是前一个视图控制器的“邻居”,因此在您回击时自动显示前一个视图控制器。我发现只有在使用
UIPageViewControllerTransitionStyleScroll
而不是
UIPageViewControllerTransitionStylePageCurl
时才会发生这种情况。页面卷曲样式显然不执行相同的缓存,因为如果您“跳转”到页面,然后用手势返回,它会将
pageViewController:viewController(Before/After)viewController:
消息发送到数据源,使您能够提供正确的邻居视图控制器

    - (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];
}
解决方案: 执行“跳转”到页面时,您可以首先跳转到相邻页面,跳转到您要跳转到的页面(
动画:否),然后在跳转的完成块中,跳转到所需页面。这将更新缓存,以便当您返回时,将显示正确的邻居页面。缺点是需要创建两个视图控制器;您要跳到的那个,以及在回指后应该显示的那个

下面是我为
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

要测试这一点,您可以创建一个新的“基于页面的应用程序”,并添加一个“转到”按钮,该按钮将“跳转”到某个日历月,然后返回。请确保将转换样式设置为滚动。

我可以确认此问题,并且它仅在使用
UIPageViewControllerTransitionStyleScroll
而不是
UIPageViewControllerTransitionStylePageCurl
时发生

解决方法:进行循环,并在每次翻页时调用
UIPageViewController setViewControllers
,直到到达所需页面


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

我有一种不同的方法,如果您的页面设计为在init之后更新:
选择手动页面时,我会更新一个标志

- (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;
    }
}
,作者Matt Neuburg记录了这个确切的问题,我发现他的解决方案感觉比目前公认的答案好一点。该解决方案效果很好,它有一个负面的副作用,即在图像之前/之后设置动画,然后用所需的页面替换该页面。我觉得这是一种奇怪的用户体验,Matt的解决方案解决了这个问题

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

如果您不需要为新页面设置动画(我没有),那么下面的代码对我很有用,在故事板中调用“值更改”。我没有在视图控制器之间更改,而是更改与当前视图控制器关联的数据

    - (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在那里,因此我可以在页面之间滑动时更新页面控件的currentPage


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

我自己也为这个问题挣扎了很长时间。对我来说,我有一个从故事板加载的UIPageViewController(我称之为PageController),并在其上添加了一个UIViewController“ContentVC”

我让ContentVC负责加载到内容区域的数据,让PageController负责滑动/转到/页面指示器更新。ContentVC有一个ivar CurrentPageIndex
- (void)setPageForward
{  
  ContentVC *FirstVC = [self viewControllerAtIndex:[CurrentPageIndex integerValue]];

  NSArray *viewControllers = @[FirstVC];
  [PageController setViewControllers:viewControllers direction:UIPageViewControllerNavigationDirectionForward animated:NO completion:nil];
}
- (NSInteger)presentationIndexForPageViewController:(UIPageViewController *)pageViewController 
{
  return [CurrentPageIndex integerValue];
}
  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?()
          }
          })
      }
    }
  }
-(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;
}
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)
            })
        }
    }
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)
    }
}
    let orderedViewControllers = [UIViewController(),UIViewController(), UIViewController()]
    let pageViewController = UIPageViewController()
    let pageControl = UIPageControl()

    func jump(to: Int, completion: @escaping (_ vc: UIViewController?) -> Void){

        guard orderedViewControllers.count > to else{
            //index of bounds
            return
        }

        let toVC = orderedViewControllers[to]

        var direction: UIPageViewController.NavigationDirection = .forward

        if pageControl.currentPage < to {
            direction = .forward;
        } else {
            direction = .reverse;
        }

        pageViewController.setViewControllers([toVC], direction: direction, animated: true) { _ in
            DispatchQueue.main.async {
                self.pageViewController.setViewControllers([toVC], direction: direction, animated: false){ _ in
                    self.pageControl.currentPage = to
                        completion(toVC)

                }
            }
        }
    }
self.jump(to: 5) { (vc) in
    // you can do anything for new vc.
}