【问题标题】:Tell When a UIPageViewController is Scrolling (for Parallax Scrolling of an Image)告诉 UIPageViewController 何时滚动(用于图像的视差滚动)
【发布时间】:2025-12-27 18:20:13
【问题描述】:

我正在尝试制作类似于新雅虎天气应用程序中的效果。基本上,UIPageViewController 中的每个页面都有一个背景图片,当滚动浏览页面视图时,图片的位置只滚动大约一半的速度。我该怎么做?我想我可以在UIPageViewController 中使用某种委托方法来获取当前偏移量,然后像这样更新图像。唯一的问题是我无论如何都无法判断UIPageViewController 是否正在滚动!有没有办法呢?谢谢!

【问题讨论】:

    标签: objective-c uiscrollview uiimage uipageviewcontroller


    【解决方案1】:
    for (UIView *view in self.pageViewController.view.subviews) {
        if ([view isKindOfClass:[UIScrollView class]]) {
             [(UIScrollView *)view setDelegate:self];
        }
    } 
    

    这使您可以访问所有标准滚动视图 API 方法。 而且这没有使用私有的 Apple API。

    我添加了遍历子视图,以 100% 找到UIPageViewController 的内部滚动视图 警告: 小心 scrollview.contentOffset。当控制器滚动到新页面时它会重置

    如果您需要持久性scrollview 偏移跟踪和类似的东西,最好使用UICollectionViewController 与集合视图本身大小的单元格并启用分页。

    【讨论】:

    • 如果 Apple 改变他们的页面视图控制器的架构,你就完蛋了。
    • @VanDuTran 同意,但在那之前,一切都很好,而且似乎是最好的方法。我没有看到其他选择
    • 对我来说这不是第一个子视图,而是第二个子视图。这段代码做到了:if([self.pageVC.view.subviews[1] respondsToSelector:@selector(setDelegate:)]){ [self.pageVC.view.subviews[1] setDelegate:self]; }
    • 这个问题是它没有给出scrollview.contentOffset的标准结果。它会在每个新页面重置。
    • 这不仅仅是Apple的chnages,而是你分配了新的委托,所以如果UIPageViewController的内部实现使用它,你会破坏它。
    【解决方案2】:

    我会这样做:

    Objective-C

    for (UIView *v in self.pageViewController.view.subviews) {
        if ([v isKindOfClass:[UIScrollView class]]) {
            ((UIScrollView *)v).delegate = self;
        }
    }
    

    并实现这个协议

    - (void)scrollViewDidScroll:(UIScrollView *)scrollView
    

    斯威夫特

    for view in self.pageViewController.view.subviews {
      if let scrollView = view as? UIScrollView {
        scrollView.delegate = self
      }
    }
    

    并实现这个协议

    func scrollViewDidScroll(scrollView: UIScrollView)
    

    【讨论】:

    • 感谢您提供的 Swift 代码
    【解决方案3】:

    我的猜测是它不是 UIPageViewController,而是分页的UIScrollView。 UIScrollView 确实为您提供了一个不断重复的委托方法,用于跟踪滚动发生时发生的事情。

    或者,您可能能够访问 UIPageViewController 秘密使用的分页 UIScrollView,但您可能会破坏某些东西,我不确定 Apple 会怎么想。

    【讨论】:

    • 必须有一种方法可以使用 UIPageViewController 执行此操作。如果我使用它,它只会简化一切。 Yahoo App 看起来也使用了 UIPageViewController。有没有办法从 UIViewController 本身获取屏幕上的位置?
    • UIPageViewController 基本上不就是一个花哨的 UIScrollView 吗?
    • 这对你来说可能更简单,但我向你保证,在滚动式 UIPageViewController 存在之前,仅使用 UIScrollView 我们就已经在做滚动式 UIPageViewController 之类的事情了。有完整的 WWDC 视频专门用于解释如何做到这一点。
    • 花哨但不透明。正如我所说,您可以沿着视图层次结构向下工作并找到滚动视图。但您可能不应该这样做。
    • 有没有办法可以将滚动视图放在顶部并从中读取数据?
    【解决方案4】:

    使用@Paul 的 sn-p -

    for (UIView *v in self.pageViewController.view.subviews) {
    if ([v isKindOfClass:[UIScrollView class]]) {
        ((UIScrollView *)v).delegate = self;
    }
    }
    

    实现这个协议:-(void)scrollViewDidScroll:(UIScrollView *)scrollView

    -(void)scrollViewDidScroll:(UIScrollView *)scrollView
    {
    CGPoint point = scrollView.contentOffset;
    
    float percentComplete;
    percentComplete = fabs(point.x - self.view.frame.size.width)/self.view.frame.size.width;
    NSLog(@"percentComplete: %f", percentComplete);
    }
    

    这为您提供了卷轴的完成百分比。编码愉快!

    【讨论】:

      【解决方案5】:

      在 Swift 3 中你可以写得更短:

      if let scrollView = self.pageViewController.view.subviews.first(where: { $0 is UIScrollView }) as? UIScrollView {
          scrollView.delegate = self
      }
      

      【讨论】:

        【解决方案6】:
        extension UIPageViewController {
        
            var scrollView: UIScrollView? {
        
                return view.subviews.filter { $0 is UIScrollView }.first as? UIScrollView
            }
        }
        

        使用:

        pageController.scrollView?.delegate = self
        

        【讨论】:

          【解决方案7】:

          您正在寻找的称为视差滚动,您可以找到几个可以帮助您解决此问题的库。

          编辑:马特是对的,这不是一个答案,只是一个提示。无论如何,让我们完成它:

          要为位于 UIPageViewController 后面的背景图像设置动画,您应该使用它提供的委托方法:

          -[id<UIPageViewControllerDelegate> pageViewController:willTransitionToViewControllers:]
          -[id<UIPageViewControllerDelegate> pageViewController:didFinishAnimating:previousViewControllers:transitionCompleted:]
          

          通过这两种方法,您可以计算滚动百分比(您应该将控制器存储在数组中,以了解您滚动到哪个控制器并获取百分比)

          【讨论】:

          • 你如何从中得到百分比?
          • 您应该使用-[NSArray indexOfObject:] 获得搜索到的视图控制器的位置,然后您可以做一些基本的数学运算来获得该索引相对于-[NSArray count] 的百分比
          • 这不是我真正想要的。每个视图控制器内部都有自己的 UIImage。我需要逐像素控制图像的位置,使用这样的百分比根本行不通。
          • mm 你可以查看-[UIPageViewController gestureRecognizers] 寻找UIPanGestureRecognizer 并使用它的方法准确(合法地)知道当前的运动是什么。
          【解决方案8】:

          您不应该更改页面视图控制器的滚动视图的委托:它可能会破坏其正常行为和/或以后不受支持。

          相反,您可以:

          1. 向页面视图控制器的视图添加平移手势:

            let panGesture = UIPanGestureRecognizer(target: self, action: #selector(panRecognized(gesture:)))
            view.addGestureRecognizer(panGesture)
            panGesture.delegate = self
            
          2. 添加新函数以了解视图是如何滚动的。

            @objc func panRecognized(gesture: UIPanGestureRecognizer) {
                // Do whatever you need with the gesture.translation(in: view)
            }
            
          3. 将您的 ViewController 声明为 UIGestureRecognizerDelegate

          4. 实现这个功能:

            func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
                return true
            }
            

          【讨论】: