web-dev-qa-db-ja.com

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

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

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

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

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

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

- (void)stoppedScrolling {
    // ...
}
147
Suvesh Pratapa

20 implementation は非常に優れています-スクロールの一貫した開始/終了を取得するためのパッチがあります。

-(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];
...
}
169
Ashley Smart

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

- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView

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

IScrollViewDelegateドキュメント

21
texmex5

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

- (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も役に立たないため、ハックでイベントを検出する必要があるようです。

ページ区切りスクロールビューの場合:

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

20
Aurelien Porte

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

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

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

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

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

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

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

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

7
Aberrant
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
}
4
Umair

受け入れられた答えの迅速なバージョン:

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
zzzz

Ashley Smartの答えを試してみましたが、それは魅力的でした。 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
Ege Akpinar

スクロールがアプリ全体で終了したことを検出するソリューションを開発したばかりです: https://Gist.github.com/k06a/731654e3168277fb1fd0e64abc7d899e

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

これは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 ]];
}
2
k06a

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

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

1
Zappel

アクションをタップしてドラッグする場合があり、ドラッグが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
Adriana

誰かが必要な場合は、SwiftのAshley Smart answerがあります

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)
    ...
}
1
Xernox

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

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
bob

別の方法は、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")
    }
1
Dog

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

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

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

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

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
lexpenz

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
Rahul K Rajan