获取 UIPageViewController 的滚动位置

Posted

技术标签:

【中文标题】获取 UIPageViewController 的滚动位置【英文标题】:Get scroll position of UIPageViewController 【发布时间】:2015-01-30 17:26:08 【问题描述】:

我正在使用UIPageViewController,我需要在用户滑动时获取 ViewController 的滚动位置,以便在视图转换到下一个 UIViewController 时部分淡化一些资产。

UIPageViewController 的委托和数据源方法似乎不提供对此的任何访问,并且在内部我假设UIPageViewController 必须在某处使用滚动视图,但它似乎并不直接子类化它,所以我无法调用

func scrollViewDidScroll(scrollView: UIScrollView) 


我看到其他一些帖子建议获取对pageViewController!.view.subviews 的引用,然后第一个索引是滚动视图,但这似乎非常 hacky。我想知道是否有更标准的方法来处理这个问题。

【问题讨论】:

我能问一下你为什么要使用页面浏览控制器吗?一个简单的滚动视图会起作用吗? 我通常发现 UIPageViewController 在功能和自定义方面都极其缺乏。我几乎总是在集合视图上使用滚动视图。 @Aggressor,我正在使用 pageViewController,因为它为我们正在构建的东西提供了近乎精确的内置功能,我可以在 UIScrollView 中编写所有代码,但我希望 pageViewController 会很健壮足以提供这个相对简单的功能,但看起来它可能没有。 它具体提供了一般滚动视图中缺少的什么?正如 AdamPro13 上面提到的,我也总是使用自己的滚动视图。 【参考方案1】:

您可以在UIPageViewController 中搜索 UIScrollView。为此,您必须实现UIScrollViewDelegate

之后你就可以得到你的scrollView了:

for v in pageViewController.view.subviews
    if v.isKindOfClass(UIScrollView)
        (v as UIScrollView).delegate = self
    

之后,您可以使用所有 UIScrollViewDelegate 方法,因此您可以覆盖 scrollViewDidScroll 方法,您可以在其中获取 scrollPosition:

func scrollViewDidScroll(scrollView: UIScrollView) 
   //your Code


或者如果您想要单线

let scrollView = view.subviews.filter  $0 is UIScrollView .first as! UIScrollView
scrollView.delegate = self

【讨论】:

感谢您实际回答给定的问题。我会试试这个,看看它是如何工作的。 +1 完美运行。谢谢【参考方案2】:

UIPageViewController 滚动不像普通的滚动视图那样工作,你不能像其他滚动视图一样获得 scrollView.contentOffset。

所以这里有一个技巧来了解用户滚动时发生的事情:

首先你必须找到滚动视图并将委托设置为当前视图控制器,就像其他答案所说的那样。

class YourViewController : UIPageViewController 

    var startOffset = CGFloat(0) //define this

    override func viewDidLoad() 
         super.viewDidLoad()

         //from other answers   
         for v in view.subviews
             if v is UIScrollView 
                 (v as! UIScrollView).delegate = self
             
         
     

    .
    .
    .


extension YourViewController : UIScrollViewDelegate

    func scrollViewWillBeginDragging(_ scrollView: UIScrollView) 

        startOffset = scrollView.contentOffset.x
    

    public func scrollViewDidScroll(_ scrollView: UIScrollView) 

        var direction = 0 //scroll stopped

        if startOffset < scrollView.contentOffset.x 
            direction = 1 //going right
        else if startOffset > scrollView.contentOffset.x 
            direction = -1 //going left
        

        let positionFromStartOfCurrentPage = abs(startOffset - scrollView.contentOffset.x)
        let percent = positionFromStartOfCurrentPage /  self.view.frame.width

        //you can decide what to do with scroll
    


【讨论】:

这真是太棒了! 好伙伴!拯救了我的一天。 这仅在您不使用 2 根手指以继续在页面之间滚动时才有效。通过第二页后,此 startOffset 应设置为 0,但事实并非如此 @nonolays 每次 PageViewController 请求视图控制器时将 startOffset 设置为零怎么样? 此代码是应用于 PageViewController 还是应用于 PageViewController 'set' 中的单个视图?【参考方案3】:

与 Christian 的回答类似,但更像 Swift(而不是不必要地继续遍历 view.subviews):

for view in self.view.subviews 
    if let view = view as? UIScrollView 
        view.delegate = self
        break
    

【讨论】:

【参考方案4】:
var pageViewController: PageViewController? 
    didSet 
        pageViewController?.dataSource = self
        pageViewController?.delegate = self
        scrollView?.delegate = self
    


lazy var scrollView: UIScrollView? = 
    for subview in pageViewController?.view?.subviews ?? [] 
        if let scrollView = subview as? UIScrollView 
            return scrollView
        
    
    return nil
()

extension BaseFeedViewController: UIScrollViewDelegate 

func scrollViewDidScroll(_ scrollView: UIScrollView) 

    let offset = scrollView.contentOffset.x
    let bounds = scrollView.bounds.width
    let page = CGFloat(self.currentPage)
    let count = CGFloat(viewControllers.count)
    let percentage = (offset - bounds + page * bounds) / (count * bounds - bounds)

    print(abs(percentage))


【讨论】:

【参考方案5】:

ios 13 开始,UIPageViewController 在转换到另一个视图控制器后似乎会重置滚动视图的 contentOffset。这是一个可行的解决方案:

    按照其他答案的建议,找到子 scrollView 并将其委托设置为 self 跟踪pageViewController的当前页面索引:
var currentPageIndex = 0
// The pageViewController's viewControllers
let orderredViewControllers: [UIViewController] = [controller1, controller2, ...]

pageViewController.delegate = self

func pageViewController(_ pageViewController: UIPageViewController, didFinishAnimating finished: Bool, previousViewControllers: [UIViewController], transitionCompleted completed: Bool) 
    guard completed, let currentViewController = pageViewController.viewControllers?.first else  return 
    currentPageIndex = orderredViewControllers.firstIndex(of: currentViewController)!

    获取0到1的进度
func scrollViewDidScroll(_ scrollView: UIScrollView) 
    let contentOffsetX = scrollView.contentOffset.x
    let width = scrollView.frame.size.width
    let offset = CGFloat(currentPageIndex) / CGFloat(orderredViewControllers.count - 1)
    let progress = (contentOffsetX - width) / width + offset

【讨论】:

【参考方案6】:

为了使代码尽可能可读和分离,我会在UIPageViewController上定义一个扩展:

extension UIPageViewController 
  var scrollView: UIScrollView? 
    view.subviews.first(where:  $0 is UIScrollView ) as? UIScrollView
  

将自己设置为滚动视图事件的delegate 非常容易,如下所示:

pageViewController.scrollView?.delegate = self

【讨论】:

以上是关于获取 UIPageViewController 的滚动位置的主要内容,如果未能解决你的问题,请参考以下文章

在 UIPageViewController 中获取用户滑动方向

如何在 UIPageViewController 中获取当前控制器

我能以某种方式从 UIPageViewController 获取 scrollViewDidScroll: UIScrollView Delegate 方法吗?

减慢 UIPageViewController 分页

如何用 UIScrollView 实现 UIPageViewController?

UIPageViewController 在 childViewController 中具有动态内容