UIScrollViewがスクロールを終了したことを検出する方法


145

UIScrollViewDelegateには2つのデリゲートメソッドがscrollViewDidScroll:ありますscrollViewDidEndScrollingAnimation:が、どちらもスクロールが完了したことを通知しません。scrollViewDidScrollスクロールビューがスクロールしたことを通知するだけで、スクロールが終了したことは通知しません。

他のメソッドscrollViewDidEndScrollingAnimationは、ユーザーがスクロールした場合ではなく、プログラムでスクロールビューを移動した場合にのみ発生するようです。

スクロールビューがスクロールを完了したことを検出するスキームを知っている人はいますか?


あなたがプログラム的にスクロールした後、完成しスクロールを検出したい場合は、以下を参照してください。stackoverflow.com/questions/2358046/...
トレバー

回答:


157
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView {
    [self stoppedScrolling];
}

- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate {
    if (!decelerate) {
        [self stoppedScrolling];
    }
}

- (void)stoppedScrolling {
    // ...
}

13
それは減速している場合にのみ機能するようです。ゆっくりパンしてから離しても、didEndDeceleratingは呼び出されません。
Sean Clark Hess、2010年

4
それは公式のAPIですが。実際には、期待どおりに機能するとは限りません。@Ashley Smartは、より実用的なソリューションを提供しました。
Di Wu

完全に機能し、説明は理にかなっている
スティーブン・エリオット

これは、ドラッグ操作によるスクロールでのみ機能します。スクロールが他の原因(キーボードのオープンやキーボードのクローズなど)によるものである場合、ハックでイベントを検出する必要があり、scrollViewDidEndScrollingAnimationも役に立たないようです。
Aurelien Porte 2013

176

320の実装はそんなに優れている-ここでは、スクロールの一貫性の開始/終了を取得するためのパッチです。

-(void)scrollViewDidScroll:(UIScrollView *)sender 
{   
[NSObject cancelPreviousPerformRequestsWithTarget:self];
    //ensure that the end of scroll is fired.
    [self performSelector:@selector(scrollViewDidEndScrollingAnimation:) withObject:sender afterDelay:0.3]; 

...
}

-(void)scrollViewDidEndScrollingAnimation:(UIScrollView *)scrollView
{
    [NSObject cancelPreviousPerformRequestsWithTarget:self];
...
}

私のスクロールの問題に対するSOでの唯一の有用な答え。ありがとう!
Di Wu

4
[self performSelector:@selector(scrollViewDidEndScrollingAnimation :) withObject:sender afterDelay:0.3]
ウェア

1
2015年には、これは依然として最良のソリューションです。
ルカ

2
私は正直に言って、私が2016年2月のiOS 9.3にいるとは信じられません。これはまだこの問題の最良の解決策です。ありがとう、魅力のように働きました
gmogames

1
あなたは私を救いました。最良のソリューション。
Baran Emre

21

scrollViewDidEndDeceleratingはあなたが望むものだと思います。そのUIScrollViewDelegatesオプションメソッド:

- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView

デリゲートに、スクロールビューがスクロール動作の減速を終了したことを通知します。

UIScrollViewDelegateのドキュメント


えっと、同じ答えを出しました。:)
Suvesh Pratapa

どー。やや不可解な名前が付けられていますが、まさに私が探していたものです。ドキュメントをよく読んでいる必要があります。長い一日だった...
マイケルゲイロード

このライナーが私の問題を解決しました。私のクイックフィックス!ありがとうございました。
Dody Rachmat Wicaksono

20

ドラッグ操作に関連するすべてのスクロールでは、これで十分です。

- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView {
    _isScrolling = NO;
}

- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate {
    if (!decelerate) {
        _isScrolling = NO;
    }
}

ここで、スクロールがプログラムによるsetContentOffset / scrollRectVisible(animated= YESまたは、スクロールがいつ終了するか明らかにわかっている)による場合:

 - (void)scrollViewDidEndScrollingAnimation {
     _isScrolling = NO;
}

スクロールが他の原因(キーボードが開いている、キーボードが閉じているなど)によるものである場合、ハックでイベントを検出する必要があるようです。 scrollViewDidEndScrollingAnimationどちらも役に立たない。

の場合 PAGINATEDスクロールビュー:

おそらく、Appleは加速曲線を適用しているため、 scrollViewDidEndDeceleratingドラッグごとに呼び出されるのでscrollViewDidEndDragging、この場合は使用する必要がないためです。


ページ付けされたスクロールビューのケースには1つの問題があります。ページの終了位置で正確にスクロールを解放すると(スクロールが不要な場合)、scrollViewDidEndDecelerating呼び出されません
Shadow Of

19

これは他のいくつかの回答で説明されていますが、ここでは(コードで)結合しscrollViewDidEndDeceleratingscrollViewDidEndDragging:willDecelerateスクロールが完了したときにいくつかの操作を実行する方法を示します。

- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
{
    [self stoppedScrolling];
}

- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView 
                  willDecelerate:(BOOL)decelerate
{
    if (!decelerate) {
        [self stoppedScrolling];
    }
}

- (void)stoppedScrolling
{
    // done, do whatever
}

7

私はこの質問を見つけただけです。これは、私が尋ねたのとほとんど同じです 。UIScrollViewのスクロールがいつ停止したかを正確に知る方法は?

スクロール時にdidEndDeceleratingは機能しますが、固定リリースでのパンは登録されません。

私は最終的に解決策を見つけました。didEndDraggingにはパラメータWillDecelerateがあり、これは定常リリースの状況ではfalseです。

DidEndDraggingで!decelerateをチェックし、didEndDeceleratingと組み合わせると、スクロールが終了した両方の状況が得られます。


5

Rxを使用している場合は、次のようにUIScrollViewを拡張できます。

import RxSwift
import RxCocoa

extension Reactive where Base: UIScrollView {
    public var didEndScrolling: ControlEvent<Void> {
        let source = Observable
            .merge([base.rx.didEndDragging.map { !$0 },
                    base.rx.didEndDecelerating.mapTo(true)])
            .filter { $0 }
            .mapTo(())
        return ControlEvent(events: source)
    }
}

これにより、次のようにすることができます。

scrollView.rx.didEndScrolling.subscribe(onNext: {
    // Do what needs to be done here
})

これは、ドラッグと減速の両方を考慮に入れます。


これはまさに私が探していたものです。ありがとう!
nomnom

4
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
    scrollingFinished(scrollView: scrollView)
}

func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
    if decelerate {
        //didEndDecelerating will be called for sure
        return
    }
    else {
        scrollingFinished(scrollView: scrollView)
    }
}

func scrollingFinished(scrollView: UIScrollView) {
   // Your code
}

3

私はアシュリースマートの答えを試しました、そしてそれは魅力のように働きました。scrollViewDidScrollのみを使用した別のアイデアを次に示します

-(void)scrollViewDidScroll:(UIScrollView *)sender 
{   
    if(self.scrollView_Result.contentOffset.x == self.scrollView_Result.frame.size.width)       {
    // You have reached page 1
    }
}

2ページしかなかったので、うまくいきました。ただし、複数のページがある場合は、問題が発生する可能性があります(現在のオフセットが幅の倍数であるかどうかを確認できますが、ユーザーが2ページ目で停止したのか、3ページ目で停止しているか、もっと)


3

受け入れられた回答のSwiftバージョン:

func scrollViewDidScroll(scrollView: UIScrollView) {
     // example code
}
func scrollViewDidEndDragging(scrollView: UIScrollView, willDecelerate decelerate: Bool) {
        // example code
}
func scrollViewDidEndZooming(scrollView: UIScrollView, withView view: UIView!, atScale scale: CGFloat) {
      // example code
}

3

誰かが必要な場合は、こちらがSwiftでのAshley Smartの回答です

func scrollViewDidScroll(_ scrollView: UIScrollView) {
        NSObject.cancelPreviousPerformRequests(withTarget: self)
        perform(#selector(UIScrollViewDelegate.scrollViewDidEndScrollingAnimation), with: nil, afterDelay: 0.3)
    ...
}

func scrollViewDidEndScrollingAnimation(_ scrollView: UIScrollView) {
        NSObject.cancelPreviousPerformRequests(withTarget: self)
    ...
}

2

スクロールがアプリ全体でいつ終了したかを検出するためのソリューションを開発しました:https : //gist.github.com/k06a/731654e3168277fb1fd0e64abc7d899e

これは、runloopモードの変更を追跡するという考えに基づいています。そして、少なくともスクロール後0.2秒後にブロックを実行します。

これは、runloopモードの変更を追跡するための中心的なアイデアですiOS10 +:

- (void)tick {
    [[NSRunLoop mainRunLoop] performInModes:@[ UITrackingRunLoopMode ] block:^{
        [self tock];
    }];
}

- (void)tock {
    self.runLoopModeWasUITrackingAgain = YES;
    [[NSRunLoop mainRunLoop] performInModes:@[ NSDefaultRunLoopMode ] block:^{
        [self tick];
    }];
}

そして、iOS2 +のような低展開ターゲットのためのソリューション:

- (void)tick {
    [[NSRunLoop mainRunLoop] performSelector:@selector(tock) target:self argument:nil order:0 modes:@[ UITrackingRunLoopMode ]];
}

- (void)tock {
    self.runLoopModeWasUITrackingAgain = YES;
    [[NSRunLoop mainRunLoop] performSelector:@selector(tick) target:self argument:nil order:0 modes:@[ NSDefaultRunLoopMode ]];
}

Lolアプリ全体のスクロール検出-アプリのUIScrollVIewのいずれかが現在スクロールしている場合のように...役に立たないようです...
Isaac Carol Weisberg

@IsaacCarolWeisbergを使用すると、スムーズなスクロールが完了するまで、重い操作を遅らせてスムーズに保つことができます。
k06a

重い操作とは...私がグローバル同時ディスパッチキューで実行している操作、おそらく
Isaac Carol Weisberg

1

要約すると(初心者向け)。それはそれほど痛くないです。プロトコルを追加してから、検出に必要な機能を追加するだけです。

UIScrolViewを含むビュー(クラス)で、プロトコルを追加してから、ここから関数をビュー(クラス)に追加します。

// --------------------------------
// In the "h" file:
// --------------------------------
@interface myViewClass : UIViewController  <UIScrollViewDelegate> // <-- Adding the protocol here

// Scroll view
@property (nonatomic, retain) UIScrollView *myScrollView;
@property (nonatomic, assign) BOOL isScrolling;

// Protocol functions
- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView;
- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView;


// --------------------------------
// In the "m" file:
// --------------------------------
@implementation BlockerViewController

- (void)viewDidLoad {
    CGRect scrollRect = self.view.frame; // Same size as this view
    self.myScrollView = [[UIScrollView alloc] initWithFrame:scrollRect];
    self.myScrollView.delegate = self;
    self.myScrollView.contentSize = CGSizeMake(scrollRect.size.width, scrollRect.size.height);
    self.myScrollView.contentInset = UIEdgeInsetsMake(0.0,22.0,0.0,22.0);
    // Allow dragging button to display outside the boundaries
    self.myScrollView.clipsToBounds = NO;
    // Prevent buttons from activating scroller:
    self.myScrollView.canCancelContentTouches = NO;
    self.myScrollView.delaysContentTouches = NO;
    [self.myScrollView setBackgroundColor:[UIColor darkGrayColor]];
    [self.view addSubview:self.myScrollView];

    // Add stuff to scrollview
    UIImage *myImage = [UIImage imageNamed:@"foo.png"];
    [self.myScrollView addSubview:myImage];
}

// Protocol functions
- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView {
    NSLog(@"start drag");
    _isScrolling = YES;
}

- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView {
    NSLog(@"end decel");
    _isScrolling = NO;
}

- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate {
    NSLog(@"end dragging");
    if (!decelerate) {
       _isScrolling = NO;
    }
}

// All of the available functions are here:
// https://developer.apple.com/library/ios/documentation/UIKit/Reference/UIScrollViewDelegate_Protocol/Reference/UIScrollViewDelegate.html

1

アクションをタップしてドラッグするケースがあり、ドラッグがscrollViewDidEndDeceleratingを呼び出していることがわかりました

そして、コード([_scrollView setContentOffset:contentOffset animated:YES];)を使用して手動でオフセットを変更すると、scrollViewDidEndScrollingAnimationが呼び出されていました。

//This delegate method is called when the dragging scrolling happens, but no when the     tapping
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
{
    //do whatever you want to happen when the scroll is done
}

//This delegate method is called when the tapping scrolling happens, but no when the  dragging
-(void)scrollViewDidEndScrollingAnimation:(UIScrollView *)scrollView
{
     //do whatever you want to happen when the scroll is done
}

1

別の方法としてはscrollViewWillEndDragging:withVelocity:targetContentOffset、ユーザーが指を離すたびに呼び出され、スクロールが停止するターゲットコンテンツのオフセットが含まれるを使用する方法があります。このコンテンツオフセットを使用scrollViewDidScroll:すると、スクロールビューがいつスクロールを停止したかを正しく識別できます。

private var targetY: CGFloat?
public func scrollViewWillEndDragging(_ scrollView: UIScrollView,
                                      withVelocity velocity: CGPoint,
                                      targetContentOffset: UnsafeMutablePointer<CGPoint>) {
       targetY = targetContentOffset.pointee.y
}
public func scrollViewDidScroll(_ scrollView: UIScrollView) {
    if (scrollView.contentOffset.y == targetY) {
        print("finished scrolling")
    }

0

UIScrollviewにはデリゲートメソッドがあります

- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView

以下のコード行をデリゲートメソッドに追加します。

- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
{
CGSize scrollview_content=scrollView.contentSize;
CGPoint scrollview_offset=scrollView.contentOffset;
CGFloat size=scrollview_content.width;
CGFloat x=scrollview_offset.x;
if ((size-self.view.frame.size.width)==x) {
    //You have reached last page
}
}

0

UIScrollViewDelegateスクロールが本当に終了したときを検出する(または「予測」と言う方がよい)ために使用できる方法があります。

func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>)

UIScrollViewDelegate(「予測」と言うこと以上)を検出するために使用することのできるスクロールしたとき、本当に終了しました。

私の場合、(Swift 3で)次のように水平スクロールで使用しました:

func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) {
    perform(#selector(self.actionOnFinishedScrolling), with: nil, afterDelay: Double(velocity.x))
}
func actionOnFinishedScrolling() {
    print("scrolling is finished")
    // do what you need
}

0

Ashley Smartロジックを使用し、Swift 4.0以降に変換されます。

    func scrollViewDidScroll(_ scrollView: UIScrollView) {
         NSObject.cancelPreviousPerformRequests(withTarget: self)
        perform(#selector(UIScrollViewDelegate.scrollViewDidEndScrollingAnimation(_:)), with: scrollView, afterDelay: 0.3)
    }

    func scrollViewDidEndScrollingAnimation(_ scrollView: UIScrollView) {
        NSObject.cancelPreviousPerformRequests(withTarget: self)
    }

上記のロジックは、ユーザーがテーブルビューからスクロールして離れたときなどの問題を解決します。ロジックがなければ、テーブルビューをスクロールオフすると、didEndが呼び出されますが、何も実行されません。現在、2020年に使用しています。


0

一部の以前のiOSバージョン(iOS 9、10など)では、 scrollViewDidEndDecelerating、scrollViewがタッチによって突然停止した場合はトリガーされません。

しかし、現在のバージョン(iOS 13)では、scrollViewDidEndDecelerating確実にトリガーされます(私の知る限り)。

したがって、アプリが以前のバージョンもターゲットにしている場合は、Ashley Smartによって言及されているような回避策が必要になるか、または次の回避策が可能です。


    func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
        if !scrollView.isTracking, !scrollView.isDragging, !scrollView.isDecelerating { // 1
            scrollViewDidEndScrolling(scrollView)
        }
    }

    func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
        if !decelerate, scrollView.isTracking, !scrollView.isDragging, !scrollView.isDecelerating { // 2
            scrollViewDidEndScrolling(scrollView)
        }
    }

    func scrollViewDidEndScrolling(_ scrollView: UIScrollView) {
        // Do something here
    }

説明

UIScrollViewは次の3つの方法
で停止します。- すばやくスクロールして単独で停止します
-指でタッチしてすばやくスクロールして停止します(緊急ブレーキのように)
-ゆっくりスクロールして停止します

最初の1つはscrollViewDidEndDecelerating他の同様の方法で検出できますが、他の2つは検出できません。

幸い、UIScrollViewそれらを識別するために使用できる3つのステータスがあり、「// 1」と「// 2」でコメント化された2行で使用されます。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.