【问题标题】:Detecting UIScrollView page change检测 UIScrollView 页面变化
【发布时间】:2011-07-13 10:26:11
【问题描述】:

当用户在启用分页的 UIScrollView 中更改页面时,有没有办法检测或获取通知?

【问题讨论】:

    标签: iphone objective-c ipad uiscrollview uiscrollviewdelegate


    【解决方案1】:

    使用它来检测当前正在显示哪个页面并在页面更改时执行一些操作:

    - (void)scrollViewDidScroll:(UIScrollView *)scrollView {
        static NSInteger previousPage = 0;
        CGFloat pageWidth = scrollView.frame.size.width;
        float fractionalPage = scrollView.contentOffset.x / pageWidth;
        NSInteger page = lround(fractionalPage);
        if (previousPage != page) {
            // Page has changed, do your thing!
            // ...
            // Finally, update previous page
            previousPage = page;
        }
    }
    

    如果您可以在滚动完全停止后仅对页面更改做出反应,那么最好在 scrollViewDidEndDecelerating: 委托方法而不是 scrollViewDidScroll: 方法中执行上述操作。

    【讨论】:

    • 我知道这是旧的,但也许它会有所帮助,使用此方法可以返回负数以及超出滚动视图范围的页面。如果您使用页面访问数组,这显然会导致问题,因此可能需要对页面进行检查。
    • 在里面写上面的代码会好很多... - (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
    • 我不明白为什么这个答案被投票赞成。滚动视图的每次移动都会调用上述内容。真的没有必要这样做。使用scrollViewDidEndDecelerating 是一个更好的解决方案,但不是一个完整的解决方案。我添加了一个自己的解决方案,为我完全解决了这个问题。
    • 这取决于他们何时想要对页面更改做出反应。如果他们只需要知道滚动视图何时停止滚动,那么最好在减速后进行。否则,did 滚动方法是最好的。
    • @segev - scrollViewDidEndDecelerating 不起作用。
    【解决方案2】:

    在启用分页的滚动视图中,您可以使用scrollViewDidEndDecelerating 了解视图何时在页面上确定(可能是同一页面)。

    - (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
    

    scrollViewDidScroll 在每个动作中都会被调用。在启用分页的上下文中,视图可用于查找何时滚动到足以移动到下一页(如果在该点停止拖动)。

    【讨论】:

    • 这个解决方案有一个问题:如果用户在页面末尾停止滚动(这很有可能),这个函数将不会被调用!
    • @MaciejKozieł 更好的解决方案是我给出另一个答案
    • @Sagev 如何解决根本不调用- (void)scrollViewWillBeginDecelerating:(UIScrollView *)scrollView- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView 的情况?
    【解决方案3】:

    UIScrollViewDelegate 的两个方法组合起来怎么样?

    scrollViewDidEndDragging(_:willDecelerate:)中,如果它立即停止,我们进行页面计算;如果它正在减速,我们让它走,它会被scrollViewDidEndDecelerating(_:)抓住。

    代码使用 XCode 7.1.1 版、Swift 2.1 版测试

    class ViewController: UIViewController, UIScrollViewDelegate {
    
      // MARK: UIScrollViewDelegate
      func scrollViewDidEndDragging(scrollView: UIScrollView, willDecelerate decelerate: Bool) {
        if decelerate == false {
            let currentPage = scrollView.currentPage
            // Do something with your page update
            print("scrollViewDidEndDragging: \(currentPage)")
        }
      }
    
      func scrollViewDidEndDecelerating(scrollView: UIScrollView) {
        let currentPage = scrollView.currentPage
        // Do something with your page update
        print("scrollViewDidEndDecelerating: \(currentPage)")
      }
    
    }
    
    extension UIScrollView {
       var currentPage: Int {
          return Int((self.contentOffset.x+ (0.5*self.frame.size.width))/self.frame.width)+1
       }
    }
    

    【讨论】:

    • 当您同时启用垂直和水平手势时,这将不起作用。
    【解决方案4】:

    谷歌页面检测的第一个结果,所以我不得不用我认为更好的解决方案来回答这个问题。 (即使这个问题是两年半前提出的。)

    我不想打电话给scrollViewDidScroll 只是为了跟踪页码。对于这么简单的事情来说,这太过分了。 使用scrollViewDidEndDecelerating 确实有效并在页面更改时停止但是(这是一个很大的但是)如果用户在屏幕上滑动的速度比正常快两次scrollViewDidEndDecelerating 只会被调用一次。您可以轻松地从第 1 页转到第 3 页,而无需处理第 2 页。

    这完全为我解决了:

    - (void)scrollViewWillBeginDecelerating:(UIScrollView *)scrollView
    {
        scrollView.userInteractionEnabled = NO;
    }
    
    - (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
    {
        //Run your code on the current page
        scrollView.userInteractionEnabled = YES;
    }
    

    这样,用户一次只能滑动一页,而没有上述风险。

    【讨论】:

    • 你完全正确。这是无限滚动的一个很好的解决方案。
    • 如果用户尝试快速连续地滑动多个页面,它将无法按预期运行。然而,虽然这并不完美,但在我有更多时间致力于更好的解决方案之前,这对我来说是一个快速解决方案。
    • @Pier-LucGendreau 就我的测试而言,没有这样的场景。用户不能滑动两次。在scrollViewWillBeginDecelerating 中设置一个断点,您将了解到用户从第一次滑动开始抬起手指的那一秒,您就会到达该断点。
    • 我知道,但是如果您在第一次滑动动画结束之前再次滑动,第二次滑动就会丢失。
    • 感谢您,好心的先生,您负责改进旧答案。
    【解决方案5】:

    斯威夫特 4

    我发现最好的方法是使用scrollViewWillEndDragging(_:withVelocity:targetContentOffset:)。它可以让您预测是否会在您将手指从屏幕上移开时立即发生分页。此示例用于水平分页。

    记得将scrollView.delegate分配给采用UIScrollViewDelegate并实现此方法的对象。

    var previousPageXOffset: CGFloat = 0.0
    
    func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) {
    
        let targetOffset = targetContentOffset.pointee
    
        if targetOffset.x == previousPageXOffset {
            // page will not change
        } else if targetOffset.x < previousPageXOffset {
            // scroll view will page left
        } else if targetOffset.x > previousPageXOffset {
            // scroll view will page right
        }
    
        previousPageXOffset = targetOffset.x
        // If you want to track the index of the page you are on just just divide the previousPageXOffset by the scrollView width.
        // let index = Int(previousPageXOffset / scrollView.frame.width)
    
    
    }
    

    【讨论】:

    • 这是正确的答案,即使 scrollView 没有充分滚动以触发分页更改,也会调用其他方法。
    【解决方案6】:

    实现 UIScrollView 的委托。 This 方法就是你要找的。​​p>

    - (void)scrollViewDidScroll:(UIScrollView *)scrollView
    

    【讨论】:

    • 这种方法检测页面变化效率不高,因为每次用户滑动都会运行代码。所以最好在 scrollViewDidEndDecelerating 方法中进行。
    • @jianpx - 这个解决方案有一个问题:如果用户在页面末尾停止滚动(这很有可能),这个函数将不会被调用!
    • @MaciejKozieł 你找到解决这个问题的方法了吗?
    • @Zorayr 是的,我不再担心它;)我能想到的选择很少(原始答案虽然效率较低,但就是其中之一)。我需要它作为页面指示器,而且这个边缘案例不是应用程序中断问题;一旦发生任何减速,它就会刷新。
    • @jianpx 你用那条评论为我解决了一个非常具体的问题。谢谢。
    【解决方案7】:

    这是解决此问题的快速解决方案。

    在要实现代码的类中创建两个属性 currentPage 和 previousPage 并将它们初始化为 0。

    现在从 scrollViewDidEndDragging(:willDecelerate:) 更新 currentPage 和 scrollViewDidEndDecelerating(:scrollView:)。

    然后在scrollViewDidEndScrollingAnimation(_:scrollView:)中更新previousPage

        //Class Properties
        var currentPage = 0
        var previousPage = 0
    
    func scrollViewDidEndDragging(scrollView: UIScrollView, willDecelerate decelerate: Bool) {
            updatePage(scrollView)
            return
        }
    
     func scrollViewDidEndDecelerating(scrollView: UIScrollView){
            updatePage(scrollView)
            return
        }
    
    
     func updatePage(scrollView: UIScrollView) {
            let pageWidth:CGFloat = scrollView.frame.width
            let current:CGFloat = floor((scrollView.contentOffset.x-pageWidth/2)/pageWidth)+1
            currentPage = Int(current)
    
            if currentPage == 0 {
                  // DO SOMETHING
            }
            else if currentPage == 1{
                  // DO SOMETHING
    
            }
        }
    
    func scrollViewDidEndScrollingAnimation(scrollView: UIScrollView) {
         if previousPage != currentPage {
              previousPage = currentPage
              if currentPage == 0 {
                  //DO SOMETHING
                 }else if currentPage == 1 {
                   // DO SOMETHING
               }
           }
       }
    

    【讨论】:

      【解决方案8】:

      2019 年的剪切和粘贴

      做到这一点并不容易:

      var quantumPage: Int = -100 {   // the UNIQUELY LANDED ON, NEVER REPEATING page
          didSet {
              print(">>>>>> QUANTUM PAGE IS \(quantumPage)")
              pageHasActuallyChanged() // your function
          }
      }
      
      private var possibleQuantumPage: Int = -100 {
          didSet {
              if oldValue != possibleQuantumPage {
                  quantumPage = possibleQuantumPage
              }
          }
      }
      
      public func scrollViewDidEndDragging(
                          _ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
          if decelerate == false {
              possibleQuantumPage = currentPageEvenIfInBetween
          }
      }
      
      public func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
          possibleQuantumPage = currentPageEvenIfInBetween
      }
      
      var currentPageEvenIfInBetween: Int {
         return Int((self.contentOffset.x + (0.5 * self.frame.width)) / self.frame.width)
      }
      

      完美运行。

      pageHasActuallyChanged会在用户更改页面时被调用,而人类认为是“更改页面”。

      注意:提示很棘手:

      这在启动时很难初始化,并且将取决于您如何使用分页系统

      在任何分页系统中,您很可能会有类似“scrollToViewAtIndex...”的内容

      open func scrollToViewAtIndexForBringup(_ index: Int) {
          if index > -1 && index < childViews.count {
              
              let w = self.frame.size.width
              let h = self.frame.size.height
              
              let frame = CGRect(x: CGFloat(index)*w, y: 0, width: w, height: h)
              scrollRectToVisible(frame, animated: false) // NOTE THE FALSE
              
              // AND IMPORTANTLY:
              possibleQuantumPage = currentPageEvenIfInBetween
          }
      }
      

      因此,如果用户在第 17 页打开“书”,则在您的老板类中,您将调用该函数以在启动时将其设置为“17”。

      在这样的示例中,您只需要记住您必须最初设置我们的 possibleQuantumPage任何此类启动功能的价值;没有真正通用的方法来处理起始情况。

      毕竟你可能,例如,想要“快速滚动”到启动页面,谁知道在量子页面情况下这“意味着”什么.因此,请务必在启动期间根据您的情况仔细初始化您的量子页面系统。

      无论如何,只需复制并粘贴顶部的五个函数即可获得完美的量子分页。

      【讨论】:

        【解决方案9】:

        对于斯威夫特

        static var previousPage: Int = 0
        func scrollViewDidScroll(_ scrollView: UIScrollView){
            let pageWidth: CGFloat = scrollView.frame.width
            let fractionalPage: CGFloat = scrollView.contentOffset.x / pageWidth
            let page = lround(Double(fractionalPage))
            if page != previousPage{
                print(page)
                // page changed
            }
        }
        

        【讨论】:

          【解决方案10】:
          var scrollViewPage = 0
          override func viewDidLoad() {
              super.viewDidLoad()
              scrollViewPage = scrollView.currentPage
          }
          
          func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
              if scrollViewPage != scrollView.currentPage {
                  scrollViewPage = scrollView.currentPage
                  // Do something with your page update
                  print("scrollViewDidEndDecelerating: \(scrollViewPage)")
              }
          }
          

          并使用扩展

          extension UIScrollView {
              var currentPage: Int {
                  return Int((self.contentOffset.x + (0.5 * self.frame.size.width)) / 
                  self.frame.width) + 1
              }
          }
          

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2014-03-03
            • 1970-01-01
            • 1970-01-01
            • 2010-09-26
            • 1970-01-01
            相关资源
            最近更新 更多