64

我发现了一些关于如何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保持对附近页面的引用有关,但我可以'找不到任何对强制它刷新缓存的方法的引用。

有任何想法吗?

4

13 回答 13

85

Programming iOS6,由 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];
                   });
               }];
于 2013-09-03T22:00:03.047 回答
37

所以我遇到了和你一样的问题,我需要能够“跳转”到一个页面,然后当我返回一个页面时发现“顺序混乱”。据我所知,页面视图控制器肯定会缓存视图控制器,当您“跳转”到页面时,您必须指定方向:正向或反向。然后它假定新的视图控制器是前一个视图控制器的“邻居”,因此当你回击时会自动呈现前一个视图控制器。我发现这只发生在您使用UIPageViewControllerTransitionStyleScrolland not时UIPageViewControllerTransitionStylePageCurl。页面卷曲样式显然不会执行相同的缓存,因为如果您“跳转”pageViewController:viewController(Before/After)ViewController:

解决方案:执行“跳转”到页面时,您可以先跳转到您要跳转到的页面的相邻页面(animated:NO),然后在该跳转的完成块中跳转到所需的页面。这将更新缓存,以便当您返回时,将显示正确的邻居页面。缺点是您需要创建两个视图控制器;您要跳转到的那个和回击后应该显示的那个。

这是我编写的类别的代码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

您可以做的测试是创建一个新的“基于页面的应用程序”并添加一个“跳转”按钮,该按钮将“跳转”到某个日历月,然后返回手势。请务必将过渡样式设置为滚动。

于 2013-02-13T21:12:38.880 回答
5

我使用这个功能(我总是在横向,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];
      } 
    }
} 
于 2012-11-30T16:19:25.353 回答
5

Here is my Swift solution to be used for subclasses of UIPageViewController:

Assume you store an array of viewControllers in viewControllerArray and the current page index in 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?()
          }
          })
      }
    }
  }
于 2015-05-07T22:00:11.007 回答
3

快速版本的 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)
            })
        }
    }
于 2015-10-19T08:39:44.943 回答
3

需要注意的是,iOS 10 不再是这种情况,您不再需要使用接受的答案解决方案。像往常一样继续。

于 2016-10-25T09:27:33.517 回答
2

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

解决方法:创建一个循环并UIPageViewController setViewControllers在每次翻页时调用,直到您到达所需的页面。

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

于 2013-02-21T11:19:00.913 回答
2

这只是解决方案

-(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;
}
于 2015-09-04T13:00:21.060 回答
1

I had a different approach, should be possible if your pages are designed to be updated after init:
When a manual page is selected I update a flag

- (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;
    }
}
于 2013-07-25T02:26:19.817 回答
1

如果您不需要为新页面设置动画,就像我没有那样,以下代码对我有用,在情节提要中调用“Value Changed”。我没有在视图控制器之间进行更改,而是更改了与当前视图控制器关联的数据。

    - (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 的 currentPage。

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

于 2014-02-09T16:29:54.687 回答
1

这是@djibouti33 的答案的最新Swift 3+ 版本,其语法经过了清理。

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)
    }
}
于 2017-11-20T14:00:29.237 回答
1
    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.
}
于 2019-07-12T14:10:32.877 回答
0

我自己在这个问题上挣扎了很长时间。对我来说,我从情节提要加载了一个 UIPageViewController(我称之为 PageController),并在其上添加了一个 UIViewController 'ContentVC'。

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

请注意,我使用设置为 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];
}
于 2014-10-24T18:59:32.157 回答