48

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

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

4

8 回答 8

81

我对这个错误的解决方法是在完成时创建一个设置相同视图控制器但没有动画的块

__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
                });
            }
        }];
于 2012-11-06T15:11:30.257 回答
62

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

  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.aeth.com/PageViewControllerBug.mov

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

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

于 2012-10-17T16:54:15.887 回答
3

是我汇总的“粗略”要点。它包含一个患有阿尔茨海默病的 UIPageViewController 替代方案(即:它没有 Apple 实现的内部缓存)。

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

于 2013-05-15T08:16:21.230 回答
1

从 iOS 12 开始,原始问题中描述的问题似乎几乎已解决。我提出这个问题是因为我在我的特定设置中经历过它,它仍然会发生,因此这里使用了“几乎”这个词。

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

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

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

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

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

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

于 2019-02-06T13:50:29.593 回答
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 })
            }
        }
    }
于 2016-05-12T21:28:33.647 回答
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)
    }
}
于 2019-04-02T08:48:54.437 回答
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
    }
}
于 2019-08-20T09:54:47.920 回答
-5

陈述:

似乎 Apple 已经发现开发人员在非常不同的应用程序中使用 UIPageViewController,这些应用程序超出了 Apple 最初基于其设计选择的最初预期的应用程序。而不是以手势驱动的线性方式使用它,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 的角度来看 Apple 是否非常喜欢看到 PVC 以这种方式使用,但是 - 它不会破坏向后兼容性,这是一个简单的修复,所以 - 他们最终做到了。实际上,这只是对两个 DataSource 方法之一的再调用一次,这在页面(ViewController)已经被兑现以供以后使用的线性环境中是绝对不必要的。

然而,即使这种增强对于某些用例可能非常方便,类的初始行为也不能被视为错误。许多开发人员这样做的事实——也在 SO 上的其他帖子中指责 UIPageViewController 行为不端——而是强调了对其设计、目的和功能的广泛传播的误解。

在没有试图冒犯这个伟大设施中的任何开发人员的情况下,我仍然决定不删除我最初的“调查”,该“调查”清楚地向 OP 解释了 PVC 的机制以及为什么他的假设是错误的,即他必须在这里处理一个错误.

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


原始答案:

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

的设计UIPageViewController绝对是完美无瑕的,你提交的所有黑客为了规避所谓的错误只不过是对你自己错误假设的补救措施,因为你一开始就搞砸了!!!

根本没有错误!你只是在与框架作斗争。我会解释为什么!


关于页码和索引的讨论太多了!这些是控制器一无所知的概念!它唯一知道的是 - 它正在显示一些内容(顺便说一句。由您作为dataViewController提供)并且它可以执行诸如右/左动画之类的操作以模仿翻页。 CURL或者SCROLL……!!!

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

当您最初设置pageViewController时,它只关心这一点SPACE。只有当您开始平移它的视图时,它才会开始询问DataSource它最终应该显示什么,以防发生左/右翻转。当您开始向左平移时,PVC 会先询问BEFORE-SPACE,然后 询问AFTER-SPACE,如果您从右侧开始,它会反其道而行之。

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

BEFORE空间(从动画之前开始)在完成向右转的情况下完全过时,并尽快被释放。旧center 的现在是新BEFORE的,以前AFTER的就是新的center。一切都只是向右移动了一步。

所以 - 没有谈论“哪个页面”或“任何索引” - 只是简单地 - 有一个BEFORE或一个AFTER空格。如果您将 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动画!动画翻页是翻页,只是翻页!无论哪种模式!如果您决定撕掉书的第 2 页到第 7 页,那就太好了!但是不要指望UIPageViewController在返回最近的页面时发明一个不存在的第 7 页,除非你告诉它事情已经改变了......


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

而且 PVC 甚至可以很好地配合!当跳转到SPACE没有动画的新内容时,它会询问您是否需要进一步了解 -BEFOREAFTER控制器。所以你的应用程序逻辑可以跟上 PVC ......

但是对于您始终在传达的动画 - 移动到上一个/下一个空间(BEFORE - AFTER)。所以从逻辑上讲,PVC 根本不需要在动画翻页时再次询问它已经知道的空间!!!

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


并且以防万一您正在寻找比已接受答案中的“完成块”破解更好的解决方案(因为您正在事先为一些可能甚至可能不会在以后使用的东西提前工作)使用手势识别器代表:

- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer

如果您真的打算向左返回第 7 页,则在此处设置您的 PVC 的 DataViewController(没有动画),并且DataSource将被要求BEFORE并且AFTER您可以提交您喜欢的任何页面!当你从第 1 页到第 8 页进行不受控制的跳跃时,你应该隐藏一个标志或 ivar,这应该没问题......


当人们继续抱怨 PVC 中的一个错误时——在应该只翻 1 翻的情况下翻了 2 页——将他们指向这篇文章。

同样的问题 - 在转换手势中触发未动画的setViewControllers:方法将导致完全相同的破坏。您认为您设置了新中心 - 要求 DataSource 提供新的BEFORE - AFTERdataController - 您重置了索引计数...... - 好吧,这似乎没问题......

但是 - 在所有这些业务之后,PVC 结束了它的转换/动画并想知道下一个(仍然未知)dataViewController(BEFOREAFTER)并且还触发DataSource. 这是完全合理的!它需要知道它在它的小BEFORE - CENTER - AFTER 世界中的位置,并为下一回合做好准备。

但是您的程序逻辑在其逻辑中添加了另一个 index++ 计数,并且突然翻了 2 页!这与您认为自己所处的位置不同。

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


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

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

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

发疯——它们是你的……

快乐编码!

于 2016-12-02T23:18:39.920 回答