ios UIPageViewController,如何正确跳转到特定页面而不打乱数据源指定的顺序?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/13633059/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-30 21:22:40  来源:igfitidea点击:

UIPageViewController, how do I correctly jump to a specific page without messing up the order specified by the data source?

iphoneobjective-ciosuipageviewcontroller

提问by Kyle

I've found a few questions about how to make a UIPageViewControllerjump to a specific page, but I've noticed an added problem with jumping that none of the answers seem to acknowledge.

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

Without going into the details of my iOS app (which is similar to a paged calendar), here is what I'm experiencing. I declare a UIPageViewController, set the current view controller, and implement a data source.

无需深入我的 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];
}

Edit Note: I added simplified code for the dequeuing method. Even with this blasphemous implementation I have the exact same problem with page order.

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

The initialization all works as expected. The incremental paging all works fine as well. The issue is that if I ever call jumpToDayagain, the order gets jumbled.

初始化都按预期工作。增量分页也能正常工作。问题是,如果我jumpToDay再次打电话,订单会变得混乱。

If the user is on day -5 and jumps to day 1, a scroll to the left will reveal day -5 again instead of the appropriate day 0. This seems to have something to do with how UIPageViewControllerkeeps references to nearby pages, but I can't find any reference to a method that would force it to refresh it's cache.

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

Any ideas?

有任何想法吗?

回答by djibouti33

Programming iOS6, by Matt Neuburg documents this exact problem, and I actually found that his solution feels a little better than the currently accepted answer. That solution, which works great, has a negative side effect of animating to the image before/after, and then jarringly replacing that page with the desired page. I felt like that was a weird user experience, and Matt's solution takes care of that.

由 Matt Neuburg编写的 Programming iOS6记录了这个确切的问题,我实际上发现他的解决方案比目前接受的答案要好一些。该解决方案效果很好,但有一个负面影响,即在之​​前/之后对图像进行动画处理,然后用所需的页面替换该页面。我觉得这是一种奇怪的用户体验,而 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];
                   });
               }];

回答by Spencer Hall

So I ran into the same problem as you where I needed to be able to 'jump' to a page and then found the 'order messed up' when I gestured back a page. As far as I have been able to tell, the page view controller is definitely caching the view controllers and when you 'jump' to a page you have to specify the direction: forward or reverse. It then assumes that the new view controller is a 'neighbor' to the previous view controller and hence automagically presents the previous view controller when you gesture back. I found that this only happens when you are using the UIPageViewControllerTransitionStyleScrolland not UIPageViewControllerTransitionStylePageCurl. The page curl style apparently does not do the same caching since if you 'jump' to a page and then gesture back it delivers the pageViewController:viewController(Before/After)ViewController:message to the data source enabling you to provide the correct neighbor view controller.

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

Solution: When performing a 'jump' to page you can first jump to the neighbor page to the page (animated:NO) you are jumping to and then in the completion block of that jump, jump to the desired page. This will update the cache such that when you gesture back, the correct neighbor page will be displayed. The downside is that you will need to create two view controllers; the one you are jumping to and the one that should be displayed after gesturing back.

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

Here is the code to a category that I wrote for UIPageViewController:

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

What you can do to test this is create a new 'Page-Based Application' and add a 'goto' button that will 'jump' to a certain calendar month and then gesture back. Be sure to set the transition style to scroll.

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

回答by jcesarmobile

I use this function (I'm always in landscape, 2 page mode)

我使用此功能(我总是处于横向,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];
      } 
    }
} 

回答by confile

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

这是我用于子类的 Swift 解决方案UIPageViewController

Assume you store an array of viewControllers in viewControllerArrayand the current page index in updateCurrentPageIndex.

假设您将一个 viewControllers 数组存储viewControllerArrayupdateCurrentPageIndex.

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

回答by julian

Swift version of djibouti33's answer:

djibouti33 答案的 Swift 版本:

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

回答by user1416564

It's important to note that this is no longer the case in iOS 10 and you no longer have to use the accepted answer solution. Just continue as always.

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

回答by Jagveer Singh

This is only 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;
}

回答by Peter Boné

I can confirm this issue, and that it only happens when using UIPageViewControllerTransitionStyleScrolland not UIPageViewControllerTransitionStylePageCurl.

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

Workaround:Make a loop and call UIPageViewController setViewControllersfor each page turn, until you reach the desired page.

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

This keeps the internal datasource index in UIPageViewController in sync.

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

回答by Yariv Nissim

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

我有不同的方法,如果您的页面旨在在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;
}

and then force the the next page to reload with the correct data:

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

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

回答by Erich Wood

If you do not need to animate to the new page, as I didn't, the following code worked for me, called on "Value Changed" in the storyboard. Instead of changing between view controllers, I change the data associated with the current view controller.

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

    - (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 is there so I can update the pageControl's currentPage when I swipe between pages.

currentIndex 在那里,所以当我在页面之间滑动时,我可以更新 pageControl 的 currentPage。

pageDataSource dataForPage: returns an array of data objects that are displayed by the pages.

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