UIPageViewController使用Scroll过渡样式导航到错误的页面

问题描述 投票:43回答:7

我的UIPageViewController在iOS 5中运行良好。但是当iOS 6出现时,我想使用新的滚动转换样式(UIPageViewControllerTransitionStyleScroll)而不是页面卷曲样式。这导致我的UIPageViewController破坏。

它工作正常,除非我打电话给setViewControllers:direction:animated:completion:。之后,下次用户手动滚动一页时,我们会得到错误的页面。这有什么不对?

ios6 uipageviewcontroller
7个回答
76
投票

我对此错误的解决方法是在完成时创建一个块,即设置相同的viewcontroller但没有动画

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

56
投票

这实际上是UIPageViewController中的一个错误。它仅在滚动样式(UIPageViewControllerTransitionStyleScroll)中出现,并且仅在使用动画:YES调用setViewControllers:direction:animated:completion:之后才出现。因此有两种解决方法:

  1. 不要使用UIPageViewControllerTransitionStyleScroll。
  2. 或者,如果你打电话给setViewControllers:direction:animated:completion:,只使用animated:NO

要清楚地看到错误,请调用setViewControllers:direction:animated:completion:,然后在界面(作为用户)中,手动向左(向后)导航到上一页。您将导航回错误的页面:根本不是前一页,而是调用setViewControllers:direction:animated:completion:时所在的页面。

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

我发布了一部电影,清楚地演示了如何查看错误:

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

编辑此错误可能会在iOS 8中修复。

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


3
投票

Here是我整理的“粗糙”主旨。它包含一个受阿尔茨海默病影响的UIPageViewController替代方案(即:它没有Apple实现的内部缓存)。

这个类不完整,但它适用于我的情况(即:水平滚动)。


1
投票

从iOS 12开始,原始问题中描述的问题似乎几乎是固定的。我来到这个问题是因为我在我的特定设置中经历过它,它仍然会发生,因此在这里“几乎”这个词。

我遇到此问题的设置是:1)应用程序是通过深层链接打开的2)基于应用程序必须切换到特定选项卡的链接并通过推送打开给定项目3)描述的问题仅在目标时发生tab之前没有被用户选中(因此UIPageViewController应该设置为该选项卡的动画)并且只有当setViewControllers:direction:animated:completion:animated = true 4)推送返回到包含UIPageViewController的视图控制器后,后者被发现是一个大混乱 - 它提出了完全错误的视图控制器,即使调试显示逻辑级别的一切都很好

我认为问题的根源是我在setViewControllers:direction:animated:completion:调用后非常快地推动视图控制器,因此UIPageViewController没有机会完成某些事情(可能是动画,或缓存,或其他)。

通过在UI中延迟我的程序化导航,简单地为UIPageViewController提供一些空余时间

 DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 1) { ... } 

为我解决了这个问题。它还使得链接项目的程序化打开在视觉上更加用户友好。

希望这有助于处于类似情况的人。


0
投票

此错误仍存在于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 })
            }
        }
    }

0
投票

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

-4
投票

声明:

似乎Apple已经发现开发人员在非常不同的应用程序中使用UIPageViewController,这些应用程序首先超出了Apple最初设计选择的原始预期应用程序。不是以手势驱动的线性方式使用PVC,而是经常使用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];

即使动画翻页而不是建议在例如页面层次结构,如书籍或连续页面的PDF。虽然 - 我怀疑苹果从HIG的角度非常喜欢看到PVC被这种方式使用,但是 - 它不会破坏向后兼容性,这是一个简单的解决方案,所以 - 他们最终做到了。实际上,只是再一次调用两个DataSource方法中的一个,这在线性环境中是绝对不必要的,其中页面(ViewControllers)已经被兑现以供以后使用。

但是,即使这种增强对于某些用例来说非常方便,该类的初始行为也不会被视为错误。很多开发人员都这样做 - 在SO上的其他帖子中也指责了UIPageViewController的不当行为 - 而是强调了对其设计,目的和功能的广泛误解。

在这个伟大的设施中,我没有试图冒犯任何我的同事开发人员,但我决定不删除我最初的“研究”,这清楚地向OP解释了PVC的机制以及为什么他的假设是错误的,他必须在这里处理一个错误。

对于在UIPageViewController实现中遇到一些复杂问题的任何其他开发人员来说,这也可能有用!


原始答案:

在一遍又一遍地阅读所有答案之后 - 包括已接受的答案 - 还有一件事要说......

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

什么都没有BUG!你只是在与框架作斗争。我会解释原因!


有很多关于页码和索引的讨论!这些是控制器知道的概念!它唯一知道的是 - 它显示了一些内容(顺便提一下,由你作为dataViewController提供),并且它可以执行类似右/左动画的操作以模仿翻页。 CURLSCROLL ...... !!!

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

当你最初设置一个pageViewController它只关心这个非常SPACE。只有当你开始平移它的视图时,才会开始询问它的DataSource最终应该显示的内容,以防左/右翻转发生。当你开始向左平移时,PVC首先要求BEFORE-SPACE,然后是AFTER-SPACE,如果你从右边开始,它会反过来做。

在完成动画之后(PVC的视图显示了一个新的SPACE),PVC认为这个SPACE是它的新宇宙中心,当它在它的时候,它向DataSource询问它仍然不知道的那个。如果完成右转,它想知道新的AFTER空间,如果完成左转,它会要求新的BEFORE空间。

旧的BEFORE空间(从动画之前)是在完全转向右边的情况下完全过时并尽快解除分配。老center现在是新的BEFORE和前AFTER是新的center。一切都向右移了一步。

所以 - 没有谈论'哪个页面'或'任何索引' - 只是简单 - 是有BEFOREAFTER空间。如果你将NIL返回给其中一个DataSource回调,那么PVC就会假设它位于你的range of SPACES的一个极端。如果你将NIL返回到两个回调,它会假定它显示的是one and only SPACE,并且永远不会再次调用DataSource回调!逻辑取决于你!您可以在代码中定义页面和索引!不是PVC !!!


对于该类的用户,有两种与PVC交互的方式。

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

此方法与平移手势完全相同。你指的是动画的方向(例如UIPageViewControllerNavigationDirectionBackward/Forward) - 如果有一个意图 - 换句话说只是意​​味着 - >去BEFOREAFTER ......

再次 - 没有提到索引,页码等.... !!!

它只是一种以编程方式实现同​​样手势的方式!并且当首先向右移动后向左移动时,再次显示旧内容时,PVC正在做正确的事情。请记住 - 它只是以结构化的方式显示内容(您提供) - 这是一个设计的'single page turn'

如果您更喜欢这个词,那就是翻页的概念 - 或者BOOK。

仅仅因为你在第1页之后提交第8页并不意味着PVC完全不关心你对一本书如何运作的扭曲观点。并且您的应用的用户也不是。向右翻转并向左翻转肯定会导致到达原始页面 - 如果使用动画完成。你可以通过寻找灾难的解决方案来纠正这个问题。不要责怪它在UIPageViewController上。它完美地完成了它的工作!

只要问问自己 - 你会用PAGE-CURL动画做同样的事吗?不是吗?好吧,你也不应该用SCROLL动画!动画页面翻页是翻页而且只翻页!无论哪种模式!如果您决定将您的BOOK的第2页至第7页撕下来,那就完美了!但是,当你回到最近的页面时,不要指望UIPageViewController发明一个不存在的第7页,除非你告诉它事情已经改变了......


如果你真的想要在其他地方实现不协调的跳跃,那就好吧 - 没有动画吧!在大多数情况下,这不会很优雅但是 - 它可能......

PVC甚至可以很好地发挥作用!当没有动画的情况下跳到新的SPACE时,它会向你询问更进一步的路线 - BEFOREAFTER控制器。因此,您的应用程序逻辑可以跟上PVC ......

但是通过动画,你总是在传递 - 移动到上一个/下一个空间(BEFORE - AFTER)。所以逻辑上没有必要让PVC再次询问它在动画页面转动时已经知道的空间!

如果你想从第1页到右边的动画后再向左翻看,请看第7页 - 好吧,我会说 - 这绝对是你自己的问题!


如果你正在寻找一个更好的解决方案而不是来自已接受答案的“完成块”黑客攻击(因为有了它,你事先正在做一些可能甚至无法在未来使用的东西)使用手势识别器代表:

- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer

如果你真的打算回到PAGE 7并且DataSource将被要求提供BEFOREAFTER,你可以提交你想要的任何页面,在这里设置你的PVC的DataViewController(没有动画)!当你从第1页到第8页进行不受控制的跳跃时,你应该把一个旗子或者ivar藏起来,这应该没问题......


当人们继续抱怨PVC中的一个错误 - 当它应该只转1圈时做2页翻转 - 指向他们这篇文章。

同样的问题 - 在转换手势中触发一个未动画的setViewControllers:方法将导致完全相同的破坏。你认为你设置了新的中心 - 数据源被要求新的BEFORE - AFTER dataController - 你重置你的索引计数...... - 好吧,这似乎没关系......

但是 - 在所有这些业务之后,PVC结束其过渡/动画,并希望了解下一个(仍然未知)dataViewController(BEFOREAFTER)并且还触发DataSource。这是完全合理的!它需要知道它的小型BEFORE - CENTER - AFTER世界在哪里,并为下一个回合做好准备。

但是你的程序逻辑为它的逻辑添加了另一个索引++计数,突然变成了2页!那是你认为自己所在的地方之一。

而你必须考虑到这一点!不是UIPageViewController !!!


这正是DataSourceProtocol只有两种方法的要点!它希望尽可能通用 - 让你有空间和自由来定义自己的逻辑,而不是被其他人的特殊想法和用例所困扰!逻辑完全取决于你。而且只是因为你找到了类似的功能

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

在云中的所有复制/粘贴样本应用程序中,并不一定意味着你必须吃那些预煮食物!以你喜欢的方式扩展它们!只需看看上面 - 在我的签名中你会发现一个'position:'论点!我将此扩展为稍后知道完成的页面转弯是右转还是左转弯。因为代表不幸只是告诉你轮到你完成了!它没有告诉你方向!但这有时对索引计数很重要,具体取决于您的应用程序的需要......

发疯了 - 他们是你的......

快乐的编码!!!

© www.soinside.com 2019 - 2024. All rights reserved.