检测UIScrollView页面更改


Answers:


17

实现UIScrollView的委托。方法是您要寻找的。

- (void)scrollViewDidScroll:(UIScrollView *)scrollView

28
在这种方法中检测pagechange效率不高,因为每次用户滑动时都会运行代码。因此,最好在scrollViewDidEndDecelerating方法中执行此操作。
jianpx

2
@jianpx-此解决方案有一个问题:如果用户停止在页面末尾精确滚动(这很有可能),则不会调用此函数!
MaciejKozieł14年

@MaciejKozieł您是否找到该问题的解决方案?
Zorayr

@Zorayr是的,我不再为此担心;)我可以想到几种替代方法(原始答案虽然效率较低,但是是其中一种)。我需要它作为页面指示器,而这种边缘情况不是应用中断问题;一旦减速,它将刷新。
MaciejKozieł16年

@jianpx,您通过该评论为我解决了一个非常具体的问题。谢谢。
StoriKnow

193

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

- (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:方法中执行上述操作。


4
我知道这很旧,但也许会有所帮助,使用此方法可以返回负数,也可以返回超出scrollview范围的页面。如果您使用该页面访问阵列,这显然会导致问题,因此可能需要在页面上进行检查。
ScottPetit

2
在里面编写上面的代码会更好...-(void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
Nishant

1
我不明白为什么这个答案被投票通过。滚动视图的每一个动作都将调用上述方法。确实没有必要。使用scrollViewDidEndDecelerating是更好的解决方案,但不是一个完整的解决方案。我添加了自己的解决方案,完全为我解决了。
塞格夫2013年

1
这取决于他们何时对页面更改做出反应。如果他们只需要知道scrollview何时停止滚动,则最好在减速后再进行滚动。否则,did scroll方法是最好的。
Michael Waterfall

@segev-scrollViewDidEndDecelerating不起作用。
Fattie

64

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

- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView

scrollViewDidScroll每次动作都会被调用。在启用分页的视图的上下文中,可以使用它来查看何时滚动足够的时间以移动到下一页(如果此时停止拖动)。


2
该解决方案存在一个问题:如果用户停止在页面末尾精确滚动(这很有可能),则不会调用此函数!
MaciejKozieł14年

@MaciejKozieł一个更好的解决方案是由我给出另一个答案
Segev 2014年

@Sagev如何解决根本不调用- (void)scrollViewWillBeginDecelerating:(UIScrollView *)scrollView和的情况- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
MaciejKozieł14年

42

如何结合两种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
   }
}

当您同时启用了垂直和水平手势时,此功能将无效。
Sumit Gera

24

Google在页面检测方面的第一个结果,因此我认为必须以更好的解决方案来回答。(即使这个问题是在两年半之前提出的。)

我不想打电话scrollViewDidScroll只是为了跟踪页码。多数民众赞成在这样简单的事情的矫kill过正。使用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;
}

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


你完全正确。这是无限滚动的好方法。
fabrizotus

2
如果用户尝试快速连续滑动多个页面,它将不会达到预期的效果。但是,尽管这并不完美,但对我来说这是一个快速修复,直到我有更多时间致力于更好的解决方案。
Pier-Luc Gendreau 2014年

@ Pier-LucGendreau就我的测试而言,尚无这种情况。用户不能滑动两次。在其中插入一个断点scrollViewWillBeginDecelerating,您将了解到用户从第一次滑动就将手指抬起的毫秒级,您将到达该断点。
塞格夫2014年

2
我知道,但是如果您在第一个滑动动画完成之前再次滑动,则第二次滑动会丢失。
2014年

1
先生,谢谢您为改善旧答案承担责任。
TigerCoding 2015年

18

斯威夫特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)


}

2
这是正确的答案,即使scrollView滚动不足以触发分页更改,也会调用其他方法。
Romulo BM

2

这是快速的解决方案。

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

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

然后在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
           }
       }
   }

1

剪切并粘贴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”。

在这样的示例中,您只需要记住,必须首先在任何此类调用函数中设置我们可能的QuantumPage值。没有真正通用的方法来处理开始情况。

毕竟,举例来说,您可能想要“快速滚动”到弹出页面,并且谁知道在QuantumPage情况下“意味着”什么。因此,请确保在启动时根据情况仔细初始化量子页面系统。

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


0

对于斯威夫特

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
    }
}

0
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
    }
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.