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

时间:2012-10-17 16:48:19

标签: ios6 uipageviewcontroller

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

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

8 个答案:

答案 0 :(得分: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
                });
            }
        }];

答案 1 :(得分:56)

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

  1. 请勿使用UIPageViewControllerTransitionStyleScroll。

  2. 或者,如果您致电setViewControllers:direction:animated:completion:,请仅使用animated:NO

  3. 要清楚地看到错误,请拨打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

答案 2 :(得分:3)

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

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

答案 3 :(得分:1)

从iOS 12开始,原始问题中描述的问题似乎已基本解决。我之所以提出这个问题,是因为我在自己的特定设置中仍然遇到过这种情况,但这种情况仍然会发生,因此这里使用了“几乎”一词。

我遇到此问题的设置是: 1)该应用是通过深层链接打开的 2)基于链接,应用程序必须切换到特定选项卡并通过推入在那里打开给定项目 3)仅当用户先前未选择目标选项卡(因此UIPageViewController应该设置为该选项卡的动画)并且仅当setViewControllers:direction:animated:completion:具有animated = true时,才描述问题 4)推送返回到包含UIPageViewController的视图控制器后,发现后者是一团糟-即使调试显示在逻辑级别上一切正常,它也呈现了完全错误的视图控制器

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

通过延迟我在UI中的程序化导航,只需给UIPageViewController一些空闲时间

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

为我解决了这个问题。而且,它还使从视觉上对用户友好的链接项的程序化打开成为可能。

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

答案 4 :(得分: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 })
            }
        }
    }

答案 5 :(得分: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)
    }
}

答案 6 :(得分:0)

Swift中另一个简单的解决方法:只需重置UIPageViewController的数据源。这显然清除了其缓存并解决了该错误。这是一种直接进入页面而不中断后续滑动的方法。在下面,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
    }
}

答案 7 :(得分:-4)

<强> 声明:

似乎Apple已经发现开发人员正在使用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];

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

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

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

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

原始答案:

一遍又一遍地阅读所有答案后 - 包括了 接受了一个 - 还有一件事要说......

UIPageViewController的设计绝对是 FLAWLESS 以及所有 为了规避所谓的bug,你提交的黑客攻击只不过是 你自己错误的假设的补救办法,因为你在这个假设中蠢蠢欲动 第一名!!!

根本没有任何错误!你只是在与框架作斗争。我将解释原因!

有很多关于页码和索引的讨论!这些是概念 控制器知道 NOTHING !它唯一知道的是 - 它正在显示 一些内容(顺便提一下,由你提供的 dataViewController ),它可以 做一些像右/左动画一样的模仿翻页。 CURLSCROLL ... !!!

pageViewController 的世界中,只存在当前的SPACE(让我们来电 只是这样可以避免与页面和索引混淆。)

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

完成动画后(PVC的视图显示新SPACE) PVC认为这个SPACE是它的新宇宙中心,而它就是它的核心 询问DataSource关于它仍然不知道的那个。的情况下 向右转,它想知道新的AFTER空间和 完成向左转弯的情况下,它会要求新的BEFORE空格。

旧的BEFORE空格(在动画之前)是在完成转弯的情况下 权利完全过时,并尽快解除分配。旧center 现在是新BEFORE,而前AFTER是新的center。一切都好 向右移了一步。

所以 - 没有谈论&#39; 哪个页面&#39;或&#39; 无论索引&#39; - 只是简单 - 有一个BEFORE或 一个AFTER空间。如果你将NIL返回到DataSource回调中的一个回复 假设它位于 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) 对于动画 - 如果有一个意图 - 换句话说只是意​​味着 - &gt;即将 BEFOREAFTER ...

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

这只是一种以编程方式实现同​​样手势的方法! 通过向后移动时再次显示旧内容,PVC正在做得很好 在第一个位置向右移动后向左移动。记得 - 它只是以结构化的方式显示内容(您提供的内容) - 这是设计中的 'single page turn'

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

仅仅因为你在第1页之后提交PAGE 8并不是指PVC 关心你对一本书如何运作的扭曲观点。和你的用户 应用程序既不向右和向后翻转肯定会导致达到 原始页面 - 如果用动画完成。由你来纠正这个问题取决于你 找到灾难的解决方案。不要责怪 UIPageViewController 。它正在做 它的工作完美!

问问自己 - 你会用PAGE-CURL动画做同样的事吗?不是吗? 那么,你不应该用SCROLL动画!动画页面翻页是翻页而且只翻页! 无论哪种模式! 如果你决定撕掉你的书的第2页到第7页,这完全没问题! 但是,当你回到最近的页面时,不要期望 UIPageViewController 发明一个不存在的第7页,除非你告诉它事情已经发生了变化......

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

PVC甚至可以很好地发挥作用!跳转到没有动画的新SPACE时 它会向你询问两个方面 - BEFOREAFTER控制器。因此,您的应用程序逻辑可以跟上PVC ......

但是通过动画,你总是在传递 - 移动到上一个/下一个空间(BEFORE - AFTER)。因此从逻辑上讲,PVC根本不需要再次询问它已经存在的空间 知道动画页面何时转动!!!

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

如果您正在寻找一个比完成块更好的解决方案&#39;来自 接受的答案(因为有了它,你事先就可能会做的事情 可能甚至没有得到进一步使用)使用手势识别器代表:

- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer

如果你真的打算回去,请在这里设置你的PVC的DataViewController(没有动画) 离开第7页,DataSource将被要求BEFOREAFTER,您可以提交 你喜欢什么页面!在你不受控制的情况下,你应该藏着旗帜或者伊娃 从第1页跳到第8页这应该没问题......

当人们继续抱怨PVC中的一个错误时 - 做了2页翻转 应该只做1转 - 将它们指向本文。

相同问题 - 在转换手势中触发未动画的 setViewControllers:方法 将造成完全相同的破坏。你认为你设置了新的中心 - 询问DataSource 对于新的BEFORE - AFTER dataController - 你重置索引计数...... - 好吧,这似乎没问题......

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

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

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

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

这样的功能
- (DataViewController *)viewControllerAtIndex:(NSUInteger)index storyboard:(UIStoryboard *)storyboard position:(GSPositionOfDataViewController)position;
- (NSUInteger)indexOfViewController:(DataViewController *)viewController;

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

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

快乐的编码!!!