它工作得很好,除了在我调用setViewControllers:direction:animated:completion:之后的那一刻。然后,用户手动滚动一页后,我们得到了错误的页面。这里有什么问题?
我解决这个 bug 的方法是,在完成时创建一个块,该块将设置相同的视图控制器但没有动画。
__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
});
}
}];
iOS 9.2
中。 - Sakiboy这实际上是UIPageViewController中的一个缺陷。它仅在滚动样式(UIPageViewControllerTransitionStyleScroll)下发生,并且仅在使用animated:YES调用setViewControllers:direction:animated:completion:
之后才会出现。因此有两种解决方法:
不要使用UIPageViewControllerTransitionStyleScroll。
或者,如果您调用setViewControllers:direction:animated:completion:
,则仅使用animated:NO
。
为了清楚地看到错误,请调用setViewControllers:direction:animated:completion:
,然后在界面(作为用户)手动向左(返回)导航到前一页。您将导航回错误的页面:不是前一页,而是在调用setViewControllers:direction:animated:completion:
时所在的页面。
该错误的原因似乎是,在使用滚动样式时,UIPageViewController会进行某种内部缓存。因此,在调用setViewControllers:direction:animated:completion:
之后,它未能清除其内部缓存。它认为自己知道前一页是什么。因此,当用户向左导航到前一页时,UIPageViewController 未调用数据源方法pageViewController:viewControllerBeforeViewController:
,或者使用错误的当前视图控制器进行调用。
我发布了一个视频,清楚地演示如何看到这个错误:
http://www.apeth.com/PageViewControllerBug.mov
编辑此错误可能会在iOS 8中被修复。
编辑关于此错误的另一个有趣解决方法,请参见此答案:https://dev59.com/B2Up5IYBdhLWcg3wXGsZ#21624169
这个类并不完整,但在我的情况下(即水平滚动)它是有效的。
setViewControllers:direction:animated:completion:
具有animated = true
时才会出现所述问题。
4)在推送返回到包含UIPageViewController的视图控制器后,后者被发现是一团糟 - 它呈现完全错误的视图控制器,尽管调试显示逻辑层面上一切都很好。setViewControllers:direction:animated:completion:
之后非常快地推送视图控制器,以至于UIPageViewController没有机会完成某些操作(可能是动画、缓存或其他操作)。 DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 1) { ... }
对我来说解决了这个问题。同时也使得链接项的编程式打开在视觉上更加友好。
希望这能帮助到处于类似情况的人。
因为pageviewVC在滑动时调用多个childVC。但我们只需要最后一个可见的页面。
在我的情况下,当改变pageView时,我需要改变分段控件的索引。
希望这能帮助到某些人 :)
extension ViewController: UIPageViewControllerDelegate {
func pageViewController(_ pageViewController: UIPageViewController, didFinishAnimating finished: Bool, previousViewControllers: [UIViewController], transitionCompleted completed: Bool) {
guard let pageView = pageViewController.viewControllers?.first as? ChildViewController else { return }
segmentedControl.set(pageView.index)
}
}
在 Swift 中另一个简单有效的解决方法:重置 UIPageViewController 的数据源。这样可以清除缓存并避免该 bug。下面是一个直接进入页面而不破坏后续滑动的方法。在下面的代码中,m_pages 是你的视图控制器数组,我将在下面展示如何找到 currPage(当前页面的索引)。
func goToPage(_ index: Int, animated: Bool)
{
if m_pages.count > 0 && index >= 0 && index < m_pages.count && index != currPage
{
var dir: UIPageViewController.NavigationDirection
if index < currPage
{
dir = UIPageViewController.NavigationDirection.reverse
}
else
{
dir = UIPageViewController.NavigationDirection.forward
}
m_pageViewController.setViewControllers([m_pages[index]], direction: dir, animated: animated, completion: nil)
delegate?.tabDisplayed(sender: self, index: index)
m_pageViewController.dataSource = self;
}
}
var currPage: Int
{
get
{
if let currController = m_pageViewController.viewControllers?[0]
{
return m_pages.index(of: currController as! AtomViewController) ?? 0
}
return 0
}
}
这个 bug 在 iOS9 中仍然存在。我正在使用与 George 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时,将其应用于远超苹果最初设计选择的应用范围之外。PVC通常被用于在结构化环境中以编程方式跳转到随机位置,而不是以手势驱动的线性方式。因此,他们增强了UIPageViewController的实现,该类现在调用了DataSource回调函数。
- (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];
翻译后的回答:
反复阅读了所有的回答 - 包括被接受的回答 - 只剩下一件事要说了...
UIPageViewController
的设计绝对是完美无缺的,你提交的所有解决方法都是为了规避假定的错误,因为你在一开始就把它搞砸了!
根本没有错误! 你只是在与框架作斗争。我会解释原因!
现在有很多关于页码和索引的讨论!控制器对这些概念一无所知!它唯一知道的是:它正在显示某些内容(顺便提一下,这些内容由您作为数据视图控制器提供),并且它可以执行类似于翻页的右/左动画。
在pageViewController的世界里,只存在一个当前SPACE
(我们称之为这个名称,以避免与页面和索引混淆)。
当你最初设置一个pageViewController时,它只关心这个特定的SPACE
。只有在你开始滑动它的视图时,它才开始询问它的DataSource
,在发生左/右翻转时它应该显示什么。当你向左滑动时,PVC首先询问BEFORE-SPACE
,然后再询问AFTER-SPACE
;如果你向右滑动,那么它的运作方式正好相反。
SPACE
),PVC将此SPACE
视为宇宙的新中心,并在此过程中向DataSource
询问其仍不知道的内容。如果向右完成一次转弯,它想了解新的AFTER
空间;如果向左完成一次转弯,它则要求一个新的BEFORE
空间。BEFORE
空间(来自动画之前)完全过时,并尽快被释放。旧的center
现在是新的BEFORE
,以前的AFTER
是新的center
。一切都向右移动了一步。BEFORE
或
AFTER
空格。如果您在DataSource
回调之一中返回NIL,则PVC只是
假设它处于您的SPACES范围内的一个极端位置
。如果您对两个都返回NIL
回调,它会认为它正在显示唯一的SPACE
,并且永远不会再次调用DataSource
回调!逻辑由您定义!您在代码中定义页面和索引!而不是PVC!
对于类的用户,有两种与PVC交互的方式。
一个平移手势,指示是否需要转到BEFORE / AFTER空间
一种方法 - 即setViewControllers:direction:animated:completion:
这个方法与平移手势完全相同。您正在指示方向(例如UIPageViewControllerNavigationDirectionBackward / Forward
)进行动画 - 如果有意图的话 - 换句话说就是 -> 去到BEFORE
或AFTER
...
再次强调 - 不提及索引、页码等....!!!
这只是以编程方式实现手势相同的方法!当在第一次向右移动后向左移动时,PVC会通过显示旧内容来正确执行。请记住 - 它只是以结构化的方式显示您提供的内容 - 这是设计上的'单页翻转'
!!!
这就是翻页的概念 - 或者如果您更喜欢这个术语,可以称之为BOOK!
仅仅因为您在提交第一页后提交第八页而搞砸了它,并不意味着PVC会关心您对书籍工作方式的扭曲看法。您的应用程序用户也是如此。向右翻页再向左翻页应该肯定会回到原始页面 - 如果使用动画完成。而且,由于您找到了解决方案,所以需要您纠正错误。不要把责任推给UIPageViewController
。它正在完美地执行其工作!
只需问自己 - 您是否会在PAGE-CURL
动画中做同样的事情? 不会吗?那么,在SCROLL
动画中也不应该这样做!动画翻页只是翻页!无论哪种模式!如果您决定将书的第2页到第7页撕下来,那没问题!但是,除非您告诉它事情已经改变,否则不要指望UIPageViewController
发明不存在的第7页。
如果你真的想要实现一个不协调的跳转到其他地方,那么最好不要使用动画!在大多数情况下,这样做可能不太优雅,但是它是可行的...
而且 PVC 甚至可以很好地配合使用!当没有动画跳转到一个新的 SPACE
时,它会在后面进一步询问 BEFORE
和 AFTER
控制器。因此,您的应用程序逻辑可以跟上 PVC 的步伐...
但是使用动画时,您总是在传达 - 移动到前/后一个空间 (BEFORE-AFTER
)。因此,在动画翻页时,PVC 根本不需要再次询问已知道的空间!
如果你想在从右侧动画翻页到第一页后向左翻回看到第七页 - 那么我会说 - 这绝对是你自己的问题!
如果您正在寻找比接受的答案中的“completion-block” hack更好的解决方案(因为使用它,您会在可能根本不会再用到的情况下提前做一些工作),请使用手势识别器委托。
- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer
DataSource
将会在BEFORE
和AFTER
时被请求,您可以提交任何您喜欢的页面!通过一个标志或者ivar,当您从第1页跳转到第8页时,您应该已经储存好了这个标志,这样就不会有问题了...
当人们不断抱怨PVC中的一个错误 - 当它应该只做1次翻页时却做了2次翻页 - 就指向这篇文章。
同样的问题 - 在转换手势中触发未动画化的setViewControllers:方法将导致完全相同的混乱。你认为你设置了新的中心 - 数据源被要求提供新的BEFORE - AFTER
数据控制器 - 你重置了索引计数... - 好吧,这似乎没问题...
但是 - 在所有这些操作之后,PVC结束了它的转换/动画,并想知道下一个(对它来说仍然未知的)dataViewController(BEFORE
或AFTER
),并且还会触发DataSource
。这是完全合理的!它需要知道它在它小小的BEFORE - CENTER - AFTER
世界中的位置,并为下一次翻页做好准备。
但是你的程序逻辑将另一个index++计数添加到它的逻辑中,突然间就有了2次翻页!!!而这与你认为的位置相差1个。
而且你必须考虑到这一点!而不是UIPageViewController
!!!
- (DataViewController *)viewControllerAtIndex:(NSUInteger)index storyboard:(UIStoryboard *)storyboard position:(GSPositionOfDataViewController)position;
- (NSUInteger)indexOfViewController:(DataViewController *)viewController;
在云中所有复制/粘贴的示例应用程序中,并不意味着您必须吃那些预先烹制好的食物!您可以按照自己的方式进行扩展!只需查看上面 - 在我的签名中,您将找到一个'position:'
参数!我将其扩展以后知道完成页面翻转是向左还是向右。因为委托不幸地只告诉您是否完成了翻转!它没有告诉您方向!但这有时对于索引计数很重要,具体取决于您的应用程序的需要...
尽情发挥吧 - 它们是您的...
愉快的编码!!!
jumpTo8
并将[self dismissViewControllerAnimated:YES completion:nil];
插入到jumpTo8
的第一行时,向后滑动时会出现先前可见的页面(而不是第7页)。无论如何,此情况可以通过使用animated:NO
调用setViewControllers
来解决,因为动画也不会可见。 - Sebastian Kirsche