UIPageViewController导航到滚动转换样式的错误页面

我的UIPageViewController在iOS 5中工作正常。但是当iOS 6出现时,我想使用新的滚动过渡样式(UIPageViewControllerTransitionStyleScroll)而不是页面curl样式。 这导致我的UIPageViewController中断。

它正常工作,除非我已经调用setViewControllers:direction:animated:completion: 。 之后,用户下一次手动滚动一页时,就会出现错误的页面。 这里有什么问题?

我的这个bug的解决方法是创build一个块时完成设置相同的viewcontroller,但没有animation

 __weak YourSelfClass *blocksafeSelf = self; [self.pageViewController setViewControllers:viewControllers direction:UIPageViewControllerNavigationDirectionForward animated:YES completion:^(BOOL finished){ if(finished) { dispatch_async(dispatch_get_main_queue(), ^{ [blocksafeSelf.pageViewController setViewControllers:viewControllers direction:UIPageViewControllerNavigationDirectionForward animated:NO completion:NULL];// bug fix for uipageview controller }); } }]; 

这实际上是UIPageViewController中的一个错误。 它只发生在滚动样式(UIPageViewControllerTransitionStyleScroll)中,并且只有在调用setViewControllers:direction:animated:completion:之后setViewControllers:direction:animated:completion: with animated:YES 。 因此有两个解决方法:

  1. 不要使用UIPageViewControllerTransitionStyleScroll。

  2. 或者,如果您调用setViewControllers:direction:animated:completion:使用animated:NO

为了清楚地看到bug,调用setViewControllers:direction:animated:completion:然后,在界面(以用户身份)中,手动向左(后退)导航到前一页。 您将导航回错误的页面:根本不是前面的页面,而是在setViewControllers:direction:animated:completion:被调用时setViewControllers:direction:animated:completion:的页面。

这个错误的原因似乎是,当使用滚动样式时,UIPageViewController会执行某种内部caching。 因此,在调用setViewControllers:direction:animated:completion: ,它无法清除内部caching。 它认为它知道前一页是什么。 因此,当用户向左导航到前一页时,UIPageViewController 无法调用dataSource方法pageViewController:viewControllerBeforeViewController:或者使用错误的当前视图控制器调用它。

我已经发布了一个清晰演示如何看到这个bug的电影:

http://www.apeth.com/PageViewControllerBug.mov

编辑这个bug可能会在iOS 8中修复

编辑有关此错误的另一个有趣的解决方法,请参阅此答案: https : //stackoverflow.com/a/21624169/341994

这是一个“粗略”的要点我放在一起。 它包含一个UIPageViewController替代品,患有阿尔茨海默病(即:它没有苹果实施的内部caching)。

这个课程并不完整,但它适用于我的情况(即:横向滚动)。

这个错误在iOS9中仍然存在。 我正在使用乔治Tsifrikas公布的上述相同的解决方法,但一个Swift版本:

  pageViewController.setViewControllers([page], direction: direction, animated: true) { done in if done { dispatch_async(dispatch_get_main_queue()) { self.pageViewController.setViewControllers([page], direction: direction, animated: false, completion: {done in }) } } } 

声明:

看来,苹果已经发现,开发人员正在使用UIPageViewController在非常不同的应用程序,远远超出了原来预期的苹果基于他们的deviseselect首先。 而不是以手势驱动的线性方式使用PVC,通常用于编程跳转到结构化环境中的随机位置。 所以他们增强了对UIPageViewController的实现,现在类正在调用两个DataSourcecallback函数

 - (UIViewController *)pageViewController:(UIPageViewController *)pageViewController viewControllerBeforeViewController:(UIViewController *)viewController - (UIViewController *)pageViewController:(UIPageViewController *)pageViewController viewControllerAfterViewController:(UIViewController *)viewController 

在UIPageViewController上设置新的contentViewController后

 [self.pageViewController setViewControllers:viewControllers direction:UIPageViewControllerNavigationDirectionForward animated:YES completion:nil]; 

即使页面的animation翻页反而暗示了页面层次结构中的线性进度,例如具有连续页面的书或PDF。 虽然 – 我怀疑从HIG的angular度来看,苹果非常喜欢用这种方式来使用PVC,但是 – 它不会打破向后兼容性,这是一个简单的解决scheme,所以 – 他们最终做到了。 实际上,只是在线性环境中,两个DataSource方法之一的调用是绝对不必要的,在这个线性环境中,页面(ViewController)已经被兑现以备后用。

但是,即使这种增强function在某些使用情况下可能非常方便,但该类的初始行为不被视为一个错误。 许多开发人员也这样做 – 在SO上的其他文章中,指责UIPageViewController的不当行为 – 反而强调了对其devise,目的和function的广泛的误解。

我不想在这个伟大的工厂冒犯我的任何开发伙伴,但我决定不删除我最初的“disquisition”,这个问题清楚地向OP解释了PVC的机制,以及为什么他的假设是错误的,他必须在这里处理一个bug 。

这也可以用于任何其他的开发人员谁也在UIPageViewController的实现中的一些复杂的斗争!


原文答案:

一遍又一遍地读完所有的答案 – 包括接受的答案 – 还剩下一件事要说…

UIPageViewController的devise绝对是FLAWLESS ,为了规避所谓的错误而提交的所有黑客只不过是你自己的错误假设的补救措施,因为你首先把它搞砸了!

根本没有任何BUG! 你只是在打架。 我会解释为什么!


有太多关于页码和索引的讨论! 这些是控制器不知道的概念! 它唯一知道的是 – 它显示了一些内容(btw。由你提供的dataViewController ),并且它可以做一些类似右/左的animation,以模仿翻页。 CURLSCROLL … !!!

pageViewController的世界里,只存在一个当前的SPACE (让我们这样称呼它,以避免与页面和索引混淆)。

当你最初设置一个pageViewController它只关心这个SPACE 。 只有当你开始平移它的视图时,它才开始询问它的DataSource最终应该显示什么,以防左/右翻盖发生。 当你开始向左平移时,PVC首先要求BEFORE-SPACE ,然后要求AFTER-SPACE ,如果你从右边开始,则相反。

在完成的animation(PVC的视图显示一个新的SPACE )之后,PVC认为这个SPACE是它的新的宇宙中心,并且在这个时候,它向DataSource询问它还不知道什么。 在完成向右转的情况下,它想要知道新的AFTER空间,并且在向左完成转向的情况下要求新的空间。

旧的BEFORE空间(从animation之前)如果完全转向右侧完全过时并尽快被释放。 旧的center现在是新的center ,前者是新的center 。 一切都向右移一步。

所以 – 没有谈论“ 哪个页面 ”或者“ 任何索引 ” – 只是简单地说 – 是在BEFORE还是在AFTER空间。 如果您将NIL返回到其中一个DataSourcecallback函数,那么PVC只会假定它处于您range of SPACES一个极端。 如果您将NIL返回给两个callback函数,则假定它正在显示one and only SPACE ,并永远不会再调用DataSourcecallback函数! 逻辑取决于你! 您可以在代码中定义页面和索引! 不是PVC!


对于class级的用户来说,有两种与PVC交互的方式。

  • A pan-gesture that indicates whether a turn to the BEFORE/AFTER space is desired
  • A method - namely setViewControllers:direction:animated:completion:

这个方法和平移手势完全一样。 您正在为animation指示方向(例如, UIPageViewControllerNavigationDirectionBackward/Forward ) – 如果有一个有意的 – 换句话说就是指 – >去BEFOREAFTER

再次 – 没有提及指数,页码等….

这只是一个程序化的方式来达到同样的姿态! 而且PVC在向右移动回到左边时再次显示旧内容是正确的。 请记住 – 它只是以结构化的方式显示内容(您提供) – 这是一个devise的'single page turn'

那就是翻页的概念 – 或者书本,如果你更喜欢这个词的话!

仅仅因为你在页面1之后提交第8页并不意味着PVC关心你关于一本书应该如何工作的扭曲的意见。 而你的应用程序的用户也不是。 向右和向左翻转肯定会导致到达原始页面 – 如果做一个animation。 这是由你find解决这个灾难的方法来纠正这个问题的。 不要责怪它在UIPageViewController 。 这是完美的工作!

问问自己 – 你会用PAGE-CURLanimation做同样的事吗? NO? 那么,你也不应该用SCROLLanimation! animation翻页是一个翻页,只有一页翻页! 无论哪种模式! 如果你决定把第2页撕到你的书的第7页,那就太好了! 但是,不要指望UIPageViewController一个不存在的页面7时,转回到最近的页面,除非你告诉它事情已经改变…


如果你真的想要实现一个不协调的跳转到其他地方,那么没有animation! 在大多数情况下,这不会是非常优雅的,但 – 可能… –

而PVC甚至可以一起玩! 在没有animation的情况下跳到一个新的SPACE ,它会问你进一步的道路 – BEFOREAFTER控制器。 所以你的应用程序逻辑可以跟上PVC …

但是用一个animation你总是在传达 – 移到上一个/下一个空间( BEFORE - AFTER )。 因此,从逻辑上看,PVC不需要再次询问animation页面旋转时已知的空间!

如果你想在页面1右移之后向左翻页时看到第7页 – 那么我会说 – 这绝对是你自己的问题!


而且,如果你正在寻找一个比被接受的答案中的“完成块”破解更好的解决scheme(因为有了它,你事先做了一些事情,甚至可能无法在路上继续使用),使用手势识别器代表:

 - (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer 

如果你真的打算回到第7页,那么在这里设置你的PVC的DataViewController(没有animation),并且DataSource将被请求BEFOREAFTER ,你可以提交你喜欢的任何页面! 当你从第1页到第8页进行不受控制的跳转时,你应该藏起一面旗帜或伊娃,这应该是没有问题的…


而当人们不停地抱怨PVC中的一个错误 – 当它应该只做1转时做2页转弯 – 指向这篇文章。

同样的问题 – 在转换手势中触发一个未animation的setViewControllers:方法将导致完全相同的破坏。 你认为你设置了新的中心 – DataSource被要求提供新的BEFORE - AFTER dataController – 你重置了你的索引计数… – 好吧,那好像好吧…

但是 – 在所有这些业务后,PVC结束了它的转换/animation,并且想知道下一个(还不知道它的)dataViewController( BEFOREAFTER ),并且还触发了DataSource 。 这是完全正确的! 它需要知道在它BEFORE - CENTER - AFTER的小型BEFORE - CENTER - AFTER ,为下一个回合做好准备。

但你的程序逻辑增加了另一个索引++计数到它的逻辑,突然得到了2页转! 这就是你认为自己的地方。

必须说明这一点! 不是UIPageViewController


这正是DataSourceProtocol只有两种方法的要点! 它希望尽可能通用 – 留给你空间和自由来定义你自己的逻辑,而不是被别人的特殊想法和用例卡住! 逻辑完全取决于你。 只是因为你find类似的function

 - (DataViewController *)viewControllerAtIndex:(NSUInteger)index storyboard:(UIStoryboard *)storyboard position:(GSPositionOfDataViewController)position; - (NSUInteger)indexOfViewController:(DataViewController *)viewController; 

在云中的所有复制/粘贴示例应用程序并不一定意味着你必须吃预先烹调的食物! 以任何你喜欢的方式扩展它们! 只要看看上面 – 在我的签名,你会发现一个'position:'论点! 如果完成的翻页是左转或右转,我将在后面介绍这一点。 因为不幸的是,代表只会告诉你你的轮到是否完成! 它没有告诉你方向! 但是,这有时对索引计数很重要,这取决于您的应用程序的需要…

发疯 – 他们是你的…

快乐的编码!