So erkennen Sie, wann ein UIScrollView den Bildlauf beendet hat

145

UIScrollViewDelegate hat zwei Delegatmethoden bekam scrollViewDidScroll:und scrollViewDidEndScrollingAnimation:aber keines von beiden sagen , wann abgeschlossen hat Scrollen. scrollViewDidScrollbenachrichtigt Sie nur darüber, dass die Bildlaufansicht nicht gescrollt wurde.

Die andere Methode scrollViewDidEndScrollingAnimationscheint nur ausgelöst zu werden, wenn Sie die Bildlaufansicht programmgesteuert verschieben, nicht, wenn der Benutzer einen Bildlauf durchführt.

Kennt jemand ein Schema, um zu erkennen, wann eine Bildlaufansicht den Bildlauf abgeschlossen hat?

Michael Gaylord
quelle
Siehe auch, wenn Sie das fertige Scrollen nach dem programmgesteuerten Scrollen erkennen möchten: stackoverflow.com/questions/2358046/…
Trevor

Antworten:

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

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

- (void)stoppedScrolling {
    // ...
}
Suvesh Pratapa
quelle
13
Es sieht so aus, als würde das nur funktionieren, wenn es langsamer wird. Wenn Sie langsam schwenken und dann loslassen, wird didEndDecelerating nicht aufgerufen.
Sean Clark Hess
4
Obwohl es die offizielle API ist. Es funktioniert eigentlich nicht immer so, wie wir es erwarten. @Ashley Smart gab eine praktischere Lösung.
Di Wu
Funktioniert perfekt und die Erklärung macht Sinn
Steven Elliott
Dies funktioniert nur für Bildläufe, die auf eine ziehende Interaktion zurückzuführen sind. Wenn Ihr Bildlauf auf etwas anderes zurückzuführen ist (z. B. Öffnen oder Schließen der Tastatur), müssen Sie das Ereignis anscheinend mit einem Hack erkennen, und scrollViewDidEndScrollingAnimation ist ebenfalls nicht hilfreich.
Aurelien Porte
176

Die 320 Implementierungen sind so viel besser - hier ist ein Patch, um konsistente Starts / Enden des Bildlaufs zu erhalten.

-(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];
...
}
Ashley Smart
quelle
Die einzig nützliche Antwort bei SO für mein Scroll-Problem. Vielen Dank!
Di Wu
4
sollte [self performSelector: @selector (scrollViewDidEndScrollingAnimation :) withObject: sender afterDelay: 0.3] sein
Brainware
1
Im Jahr 2015 ist dies immer noch die beste Lösung.
Lukas
2
Ich kann ehrlich gesagt nicht glauben, dass ich im Februar 2016 iOS 9.3 bin und dies ist immer noch die beste Lösung für dieses Problem. Danke, hat wie ein Zauber
funktioniert
1
Du hast mich gerettet. Beste Lösung.
Baran Emre
21

Ich denke, scrollViewDidEndDecelerating ist das, was Sie wollen. Die optionale Methode UIScrollViewDelegates:

- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView

Zeigt dem Delegierten an, dass die Bildlaufansicht die Bildlaufbewegung verlangsamt hat.

UIScrollViewDelegate-Dokumentation

texmex5
quelle
Ähm, ich gab die gleiche Antwort. :)
Suvesh Pratapa
Doh. Etwas kryptisch benannt, aber genau das, wonach ich gesucht habe. Hätte die Dokumentation besser lesen sollen. Es war ein langer Tag ...
Michael Gaylord
Dieser eine Liner hat mein Problem gelöst. Schnelle Lösung für mich! Danke dir.
Dody Rachmat Wicaksono
20

Für alle Schriftrollen, die sich auf das Ziehen von Interaktionen beziehen, ist dies ausreichend:

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

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

Wenn Ihr Bildlauf auf ein programmatisches setContentOffset / scrollRectVisible zurückzuführen ist (mit animated= YES oder Sie wissen offensichtlich, wann der Bildlauf beendet ist):

 - (void)scrollViewDidEndScrollingAnimation {
     _isScrolling = NO;
}

Wenn Ihre Schriftrolle auf etwas anderes zurückzuführen ist (z. B. Öffnen oder Schließen der Tastatur), müssen Sie das Ereignis anscheinend mit einem Hack erkennen, da dies ebenfalls scrollViewDidEndScrollingAnimationnicht hilfreich ist.

Der Fall einer PAGINIERTEN Bildlaufansicht :

Da Apple vermutlich eine Beschleunigungskurve anwendet, wird scrollViewDidEndDeceleratinges für jeden Widerstand aufgerufen, sodass scrollViewDidEndDraggingin diesem Fall keine Verwendung erforderlich ist.

Aurelien Porte
quelle
Ihre paginierte Scroll-Ansicht hat ein Problem: Wenn Sie das Scrollen genau an der Seitenendposition freigeben (wenn kein Scroll erforderlich ist), scrollViewDidEndDeceleratingwird es nicht aufgerufen
Shadow Of
19

Dies wurde in einigen anderen Antworten beschrieben, aber hier ist (im Code), wie man kombiniert scrollViewDidEndDeceleratingund scrollViewDidEndDragging:willDecelerateeinige Operationen ausführt, wenn das Scrollen beendet ist:

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

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

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

Ich habe gerade erst diese Frage gefunden, die ziemlich genau dieselbe ist, die ich gestellt habe: Woher weiß ich genau, wann das Scrollen eines UIScrollView gestoppt wurde?

Obwohl didEndDecelerating beim Scrollen funktioniert, wird das Schwenken mit stationärer Freigabe nicht registriert.

Ich habe schließlich eine Lösung gefunden. didEndDragging hat einen Parameter WillDecelerate, der in der stationären Release-Situation falsch ist.

Wenn Sie in DidEndDragging in Kombination mit didEndDecelerating nach! Decelerate suchen, erhalten Sie beide Situationen, in denen das Scrollen beendet ist.

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

Wenn Sie Rx mögen, können Sie UIScrollView folgendermaßen erweitern:

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

Damit können Sie einfach so vorgehen:

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

Dies berücksichtigt sowohl das Ziehen als auch das Abbremsen.

Zappel
quelle
Genau das habe ich gesucht. Vielen Dank!
Nomnom
3

Ich habe die Antwort von Ashley Smart ausprobiert und es hat wie ein Zauber funktioniert. Hier ist eine andere Idee, bei der nur scrollViewDidScroll verwendet wird

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

Ich hatte gerade zwei Seiten, also hat es bei mir funktioniert. Wenn Sie jedoch mehr als eine Seite haben, kann dies problematisch sein (Sie können überprüfen, ob der aktuelle Versatz ein Vielfaches der Breite beträgt, aber Sie wissen dann nicht, ob der Benutzer auf der 2. Seite angehalten hat oder auf dem Weg zur 3. oder ist Mehr)

Ege Akpinar
quelle
3

Schnelle Version der akzeptierten Antwort:

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
}
zzzz
quelle
3

Wenn jemand braucht, hier ist Ashley Smart Antwort in Swift

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

Gerade entwickelte Lösung zum Erkennen beim Scrollen in der gesamten App: https://gist.github.com/k06a/731654e3168277fb1fd0e64abc7d899e

Es basiert auf der Idee, Änderungen von Runloop-Modi zu verfolgen. Führen Sie Blöcke mindestens nach 0,2 Sekunden nach dem Scrollen aus.

Dies ist die Kernidee für die Verfolgung von Änderungen im Runloop-Modus. IOS10 +:

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

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

Und Lösung für Ziele mit geringer Bereitstellung wie 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 ]];
}
k06a
quelle
Lol App-weite Bildlauferkennung - wie in, wenn einer der UIScrollVIew der App gerade scrollt ... scheint irgendwie nutzlos ...
Isaac Carol Weisberg
@IsaacCarolWeisberg Sie können schwere Operationen bis zu dem Zeitpunkt verzögern, zu dem das reibungslose Scrollen beendet ist, um es reibungslos zu halten.
k06a
Schwere Operationen, sagen Sie ... diejenigen, die ich wahrscheinlich in den globalen Warteschlangen für gleichzeitige Versendungen ausführe
Isaac Carol Weisberg
1

Um es noch einmal zusammenzufassen (und für Neulinge). Es ist nicht so schmerzhaft. Fügen Sie einfach das Protokoll und dann die Funktionen hinzu, die Sie zur Erkennung benötigen.

Fügen Sie in der Ansicht (Klasse), die UIScrolView enthält, das Protokoll hinzu und fügen Sie dann alle Funktionen von hier zu Ihrer Ansicht (Klasse) hinzu.

// --------------------------------
// 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
Bob
quelle
1

Ich hatte einen Fall von Tippen und Ziehen von Aktionen und fand heraus, dass das Ziehen scrollViewDidEndDecelerating aufrief

Und der Änderungsversatz manuell mit Code ([_scrollView setContentOffset: contentOffset animiert: YES];) rief scrollViewDidEndScrollingAnimation auf.

//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
}
Adriana
quelle
1

Eine Alternative wäre die Verwendung, scrollViewWillEndDragging:withVelocity:targetContentOffsetdie aufgerufen wird, wenn der Benutzer den Finger hebt und den Versatz des Zielinhalts enthält, an dem der Bildlauf angehalten wird. Wenn Sie diesen Inhaltsversatz verwenden, wird scrollViewDidScroll:korrekt identifiziert, wann die Bildlaufansicht aufgehört hat zu scrollen.

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")
    }
Hund
quelle
0

UIScrollview verfügt über eine Delegatmethode

- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView

Fügen Sie der Delegate-Methode die folgenden Codezeilen hinzu

- (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
}
}
Rahul K Rajan
quelle
0

Es gibt eine Methode, mit UIScrollViewDelegateder erkannt werden kann (oder besser gesagt "Vorhersagen"), wenn das Scrollen wirklich beendet ist:

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

von UIScrollViewDelegatedenen verwendet werden kann (oder besser ‚vorhersagen‘ zu sagen) zu erkennen , wenn das Scrollen wirklich beendet ist.

In meinem Fall habe ich es mit horizontalem Scrollen wie folgt verwendet (in 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
}
Lexpenz
quelle
0

Verwendet Ashley Smart-Logik und wird in Swift 4.0 und höher konvertiert.

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

Die obige Logik löst Probleme, z. B. wenn der Benutzer aus der Tabellenansicht blättert. Ohne die Logik wird beim Scrollen aus der Tabellenansicht didEndaufgerufen, aber es wird nichts ausgeführt. Derzeit wird es im Jahr 2020 verwendet.

Kelvin Tan
quelle
0

Bei einigen früheren iOS-Versionen (wie iOS 9, 10) scrollViewDidEndDeceleratingwird dies nicht ausgelöst, wenn die scrollView plötzlich durch Berühren gestoppt wird.

Aber in der aktuellen Version (iOS 13) scrollViewDidEndDeceleratingwird sicher ausgelöst (soweit ich weiß).

Wenn Ihre App auch auf frühere Versionen abzielt, benötigen Sie möglicherweise eine Problemumgehung wie die von Ashley Smart erwähnte oder die folgende.


    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
    }

Erläuterung

UIScrollView wird auf drei Arten
gestoppt : - schnell gescrollt und von selbst gestoppt
- schnell gescrollt und per Fingerberührung gestoppt (wie Notbremse)
- langsam gescrollt und gestoppt

Die erste kann mit scrollViewDidEndDeceleratingähnlichen Methoden erkannt werden, die anderen beiden nicht.

Glücklicherweise UIScrollViewhaben wir drei Status, mit denen wir sie identifizieren können. Dies wird in den beiden Zeilen verwendet, die mit "// 1" und "// 2" kommentiert sind.

JsW
quelle