Finden Sie die Richtung des Bildlaufs in einer UIScrollView?


183

Ich habe ein UIScrollViewnur horizontales Scrollen erlaubt und möchte wissen, in welche Richtung (links, rechts) der Benutzer scrollt. Was ich getan habe, war, UIScrollViewdie touchesMovedMethode zu unterordnen und zu überschreiben :

- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
    [super touchesMoved:touches withEvent:event];

    UITouch *touch = [touches anyObject];
    float now = [touch locationInView:self].x;
    float before = [touch previousLocationInView:self].x;
    NSLog(@"%f %f", before, now);
    if (now > before){
        right = NO;
        NSLog(@"LEFT");
    }
    else{
        right = YES;
        NSLog(@"RIGHT");

    }

}

Aber diese Methode wird manchmal überhaupt nicht aufgerufen, wenn ich mich bewege. Was denken Sie?


Siehe meine Antwort unten - Sie sollten die Delegierten der Bildlaufansicht verwenden, um dies zu tun.
Memmons

Antworten:


392

Das Bestimmen der Richtung ist ziemlich einfach, aber denken Sie daran, dass sich die Richtung im Verlauf einer Geste mehrmals ändern kann. Wenn Sie beispielsweise eine Bildlaufansicht mit aktiviertem Paging haben und der Benutzer wischt, um zur nächsten Seite zu gelangen, kann die Anfangsrichtung nach rechts gerichtet sein. Wenn Sie jedoch den Sprung aktiviert haben, geht sie kurzzeitig in keine Richtung und dann kurz nach links gehen.

Um die Richtung zu bestimmen, müssen Sie den UIScrollView scrollViewDidScrollDelegaten verwenden. In diesem Beispiel habe ich eine Variable mit dem Namen erstellt, mit lastContentOffsetder ich den aktuellen Inhaltsoffset mit dem vorherigen vergleiche. Wenn es größer ist, scrollt die scrollView nach rechts. Wenn es weniger ist, scrollt die scrollView nach links:

// somewhere in the private class extension
@property (nonatomic, assign) CGFloat lastContentOffset;

// somewhere in the class implementation
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {

    ScrollDirection scrollDirection;

    if (self.lastContentOffset > scrollView.contentOffset.x) {
        scrollDirection = ScrollDirectionRight;
    } else if (self.lastContentOffset < scrollView.contentOffset.x) {
        scrollDirection = ScrollDirectionLeft;
    }

    self.lastContentOffset = scrollView.contentOffset.x;

    // do whatever you need to with scrollDirection here.    
}

Ich benutze die folgende Aufzählung, um die Richtung zu definieren. Das Setzen des ersten Werts auf ScrollDirectionNone hat den zusätzlichen Vorteil, dass diese Richtung beim Initialisieren von Variablen als Standard festgelegt wird:

typedef NS_ENUM(NSInteger, ScrollDirection) {
    ScrollDirectionNone,
    ScrollDirectionRight,
    ScrollDirectionLeft,
    ScrollDirectionUp,
    ScrollDirectionDown,
    ScrollDirectionCrazy,
};

1
Wie kann ich lastContentOffset
Dev

@JasonZhao Heh - Ich habe beim ersten Testen des Codes einige seltsame Ergebnisse erzielt, weil ich nicht berücksichtigt habe, dass der Bildlauf in der Bildlaufansicht dazu führt, dass die Bildlaufrichtung ... er ... springt. Also habe ich das der Aufzählung hinzugefügt, als ich unerwartete Ergebnisse gefunden habe.
Memmons

1
@ Dev Es ist im CodelastContentOffset = scrollView.contentOffset.x;
Memmons

@ akivag29 Guter Fang für den lastContentOffsetTyp. In Bezug auf Eigenschaften und statische Variablen: Jede Lösung ist eine gute Wahl. Ich habe keine wirkliche Präferenz. Das ist ein guter Punkt.
Memmons

2
@JosueEspinosa Um die korrekte Bildlaufrichtung zu erhalten, fügen Sie sie in die scrollViewWillBeginDragging: -Methode für den Setzeraufruf lastContentOffset ein.
Strawnut

73

... Ich möchte wissen, in welche Richtung (links, rechts) der Benutzer scrollt.

Verwenden Sie in diesem Fall unter iOS 5 und höher die UIScrollViewDelegate, um die Richtung der Schwenkgeste des Benutzers zu bestimmen:

- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView
{ 
    if ([scrollView.panGestureRecognizer translationInView:scrollView.superview].x > 0) {
        // handle dragging to the right
    } else {
        // handle dragging to the left
    }
}

1
könnte die beste Lösung sein, aber mit einem wirklich langsamen Start wird dx gleich 0 sein.
trickster77777

Sicher tut es das. Dies sollte viel mehr positiv bewertet werden, da dies eher ein "nativer" oder angemessener Weg ist, hauptsächlich weil alle in der Eigenschaft gespeicherten Werte verwendet werden müssen.
Michi

Hat dies nicht das gleiche Problem wie stackoverflow.com/a/26192103/62, bei dem es nicht richtig funktioniert, sobald das Momentum-Scrollen einsetzt, nachdem der Benutzer das Schwenken beendet hat?
Liron Yahdav

59

Verwenden von scrollViewDidScroll: ist eine gute Möglichkeit, die aktuelle Richtung zu ermitteln.

Wenn Sie die Richtung wissen möchten, nachdem der Benutzer den Bildlauf beendet hat, verwenden Sie Folgendes:

@property (nonatomic) CGFloat lastContentOffset;

- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView {

    self.lastContentOffset = scrollView.contentOffset.x;
}

- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView {

    if (self.lastContentOffset < scrollView.contentOffset.x) {
        // moved right
    } else if (self.lastContentOffset > scrollView.contentOffset.x) {
        // moved left
    } else {
        // didn't move
    }
}

3
Dies ist eine großartige Ergänzung, Justin, aber ich möchte eine Bearbeitung hinzufügen. Wenn in Ihrer Bildlaufansicht Paging aktiviert ist und Sie ein Ziehen ausführen, das an die ursprüngliche Position zurückkehrt, wird bei der aktuellen Bedingung "else" davon ausgegangen, dass ein "nach links verschoben" angezeigt wird, obwohl dies "keine Änderung" sein sollte.
Scott Lieberman

1
@ScottLieberman Ihr Recht, ich habe den Code entsprechend aktualisiert.
Justin Tanner

50

Sie müssen keine zusätzliche Variable hinzufügen, um dies zu verfolgen. Verwenden Sie einfach die UIScrollView‚s panGestureRecognizerEigenschaft wie folgt. Leider funktioniert dies nur, wenn die Geschwindigkeit nicht 0 ist:

CGFloat yVelocity = [scrollView.panGestureRecognizer velocityInView:scrollView].y;
if (yVelocity < 0) {
    NSLog(@"Up");
} else if (yVelocity > 0) {
    NSLog(@"Down");
} else {
    NSLog(@"Can't determine direction as velocity is 0");
}

Sie können eine Kombination aus x- und y-Komponenten verwenden, um nach oben, unten, links und rechts zu erkennen.


9
Leider funktioniert dies nur beim Ziehen. Die Geschwindigkeit ist 0, wenn Berührungen entfernt werden, auch wenn noch gescrollt wird.
Heiko

Schön +1. sonst Teil Geschwindigkeit 0, weil der Benutzer nicht mehr zieht, so ist die
Gestengeschwindigkeit

Sie können die Richtung in einer Variablen speichern. Ändern Sie nur, wenn Geschwindigkeit! = 0. Für mich funktioniert
Leszek Zarna

Funktioniert mit scrollViewWillBeginDragging, awesome
demensdeum

40

Die Lösung

func scrollViewDidScroll(scrollView: UIScrollView) {
     if(scrollView.panGestureRecognizer.translationInView(scrollView.superview).y > 0)
     {
         print("up")
     }
    else
    {
         print("down")
    } 
}

1
Probieren Sie es mit velocityInViewstatt translationInView. Dies löst das Problem, das beim Richtungswechsel in derselben Bewegung auftritt.
Enreas

2
Diese Lösung funktioniert perfekt. Die am meisten akzeptierte Antwort funktioniert manchmal nicht, wenn ich zum nächsten Bildschirm gehe und zurückkomme und Scroll-Up- und Down-Operationen durchführe.
Anjaneyulu Battula

Das Beste, danke!
Booharin

17

Swift 4:

Für das horizontale Scrollen können Sie einfach Folgendes tun:

if scrollView.panGestureRecognizer.translation(in: scrollView.superview).x > 0 {
   print("left")
} else {
   print("right")
}

Für vertikales Scrollen ändern Sie .xmit.y


14

In iOS8 Swift habe ich diese Methode verwendet:

override func scrollViewDidScroll(scrollView: UIScrollView){

    var frame: CGRect = self.photoButton.frame
    var currentLocation = scrollView.contentOffset.y

    if frame.origin.y > currentLocation{
        println("Going up!")
    }else if frame.origin.y < currentLocation{
        println("Going down!")
    }

    frame.origin.y = scrollView.contentOffset.y + scrollHeight
    photoButton.frame = frame
    view.bringSubviewToFront(photoButton)

}

Ich habe eine dynamische Ansicht, die beim Scrollen des Benutzers die Position ändert, sodass die Ansicht so aussehen kann, als ob sie an derselben Stelle auf dem Bildschirm geblieben wäre. Ich verfolge auch, wann der Benutzer nach oben oder unten geht.

Hier ist auch ein alternativer Weg:

func scrollViewWillEndDragging(scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) {
    if targetContentOffset.memory.y < scrollView.contentOffset.y {
        println("Going up!")
    } else {
        println("Going down!")
    }
}

1
Verwenden Sie x anstelle von y?
Esqarrouth

Eigentlich möchte ich es für UiCollectionView erkennen. Ich habe horizontales Scrollen. Also werde ich in crollViewDidEndDeceleratingrichtig erkennen?
Umair Afzal

8

Das hat bei mir funktioniert (in Ziel-C):

    - (void)scrollViewDidScroll:(UIScrollView *)scrollView{

        NSString *direction = ([scrollView.panGestureRecognizer translationInView:scrollView.superview].y >0)?@"up":@"down";
        NSLog(@"%@",direction);
    }

7
- (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset {

    CGPoint targetPoint = *targetContentOffset;
    CGPoint currentPoint = scrollView.contentOffset;

    if (targetPoint.y > currentPoint.y) {
        NSLog(@"up");
    }
    else {
        NSLog(@"down");
    }
}

2
Diese Methode wird nicht aufgerufen, wenn der Wert der pagingEnabled-Eigenschaft der Bildlaufansicht YES ist.
Ako

5

Alternativ ist es möglich, den Schlüsselpfad "contentOffset" zu beobachten. Dies ist nützlich, wenn Sie den Delegaten der Bildlaufansicht nicht festlegen / ändern können.

[yourScrollView addObserver:self forKeyPath:@"contentOffset" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:nil];

Nachdem Sie den Beobachter hinzugefügt haben, können Sie jetzt:

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context{
    CGFloat newOffset = [[change objectForKey:@"new"] CGPointValue].y;
    CGFloat oldOffset = [[change objectForKey:@"old"] CGPointValue].y;
    CGFloat diff = newOffset - oldOffset;
    if (diff < 0 ) { //scrolling down
        // do something
    }
}

Denken Sie daran, den Beobachter bei Bedarf zu entfernen. zB könnten Sie den Beobachter in hinzufügen viewWillAppear und entfernen Sie sie in viewWillDisappear


5

In Kürze:

    func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
    if scrollView.panGestureRecognizer.translation(in: scrollView).y < 0 {
        print("down")
    } else {
        print("up")
    }
}

Sie können dies auch in scrollViewDidScroll tun.


4

Hier ist meine Lösung für Verhalten wie in @followben Antwort, aber ohne Verlust bei langsamem Start (wenn dy 0 ist)

@property (assign, nonatomic) BOOL isFinding;
@property (assign, nonatomic) CGFloat previousOffset;

- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView {
    self.isFinding = YES;
}
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
    if (self.isFinding) {
        if (self.previousOffset == 0) {
            self.previousOffset = self.tableView.contentOffset.y;

        } else {
            CGFloat diff = self.tableView.contentOffset.y - self.previousOffset;
            if (diff != 0) {
                self.previousOffset = 0;
                self.isFinding = NO;

                if (diff > 0) {
                    // moved up
                } else {
                    // moved down
                }
            }
        }
    }
}

1

Ich habe einen Teil der Antwort überprüft und die AnswerBot-Antwort ausgearbeitet, indem ich alles in einen Drop in der Kategorie UIScrollView eingeschlossen habe. Das "lastContentOffset" wird stattdessen in der uiscrollview gespeichert und muss dann nur noch aufgerufen werden:

- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView {
  [scrollView setLastContentOffset:scrollView.contentOffset];
}

- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate {
  if (scrollView.scrollDirectionX == ScrollDirectionRight) {
    //Do something with your views etc
  }
  if (scrollView.scrollDirectionY == ScrollDirectionUp) {
    //Do something with your views etc
  }
}

Quellcode unter https://github.com/tehjord/UIScrollViewScrollingDirection


1

Ich ziehe es vor, basierend auf der Antwort von @ memmons zu filtern

In Ziel-C:

// in the private class extension
@property (nonatomic, assign) CGFloat lastContentOffset;

// in the class implementation
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {

    if (fabs(self.lastContentOffset - scrollView.contentOffset.x) > 20 ) {
        self.lastContentOffset = scrollView.contentOffset.x;
    }

    if (self.lastContentOffset > scrollView.contentOffset.x) {
        //  Scroll Direction Left
        //  do what you need to with scrollDirection here.
    } else {
        //  omitted 
        //  if (self.lastContentOffset < scrollView.contentOffset.x)

        //  do what you need to with scrollDirection here.
        //  Scroll Direction Right
    } 
}

Beim Testen in - (void)scrollViewDidScroll:(UIScrollView *)scrollView:

NSLog(@"lastContentOffset: --- %f,   scrollView.contentOffset.x : --- %f", self.lastContentOffset, scrollView.contentOffset.x);

img

self.lastContentOffset ändert sich sehr schnell, die Wertlücke beträgt fast 0.5f.

Es ist nicht erforderlich.

Und gelegentlich kann Ihre Orientierung verloren gehen, wenn Sie in genauem Zustand behandelt werden. (Implementierungsanweisungen werden manchmal übersprungen)

sowie :

- (void)scrollViewDidScroll:(UIScrollView *)scrollView{

    CGFloat viewWidth = scrollView.frame.size.width;

    self.lastContentOffset = scrollView.contentOffset.x;
    // Bad example , needs value filtering

    NSInteger page = scrollView.contentOffset.x / viewWidth;

    if (page == self.images.count + 1 && self.lastContentOffset < scrollView.contentOffset.x ){
          //  Scroll Direction Right
          //  do what you need to with scrollDirection here.
    }
   ....

In Swift 4:

var lastContentOffset: CGFloat = 0

func scrollViewDidScroll(_ scrollView: UIScrollView) {

     if (abs(lastContentOffset - scrollView.contentOffset.x) > 20 ) {
         lastContentOffset = scrollView.contentOffset.x;
     }

     if (lastContentOffset > scrollView.contentOffset.x) {
          //  Scroll Direction Left
          //  do what you need to with scrollDirection here.
     } else {
         //  omitted
         //  if (self.lastContentOffset < scrollView.contentOffset.x)

         //  do what you need to with scrollDirection here.
         //  Scroll Direction Right
    }
}

0

Wenn Paging aktiviert ist, können Sie diesen Code verwenden.

- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
{
    self.lastPage = self.currentPage;
    CGFloat pageWidth = _mainScrollView.frame.size.width;
    self.currentPage = floor((_mainScrollView.contentOffset.x - pageWidth / 2) / pageWidth) + 1;
    if (self.lastPage < self.currentPage) {
        //go right
        NSLog(@"right");
    }else if(self.lastPage > self.currentPage){
        //go left
        NSLog(@"left");
    }else if (self.lastPage == self.currentPage){
        //same page
        NSLog(@"same page");
    }
}

0

Codes erklärt sich wohl selbst. CGFloat-Differenz1 und -Differenz2 werden in derselben privaten Schnittstelle der Klasse deklariert. Gut, wenn contentSize gleich bleibt.

- (void)scrollViewDidScroll:(UIScrollView *)scrollView
        {

        CGFloat contentOffSet = scrollView.contentOffset.y;
        CGFloat contentHeight = scrollView.contentSize.height;

        difference1 = contentHeight - contentOffSet;

        if (difference1 > difference2) {
            NSLog(@"Up");
        }else{
            NSLog(@"Down");
        }

        difference2 = contentHeight - contentOffSet;

       }

0

Ok, für mich funktioniert diese Implementierung wirklich gut:

@property (nonatomic, assign) CGPoint lastContentOffset;


- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView {
    _lastContentOffset.x = scrollView.contentOffset.x;
    _lastContentOffset.y = scrollView.contentOffset.y;

}


- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView {

    if (_lastContentOffset.x < (int)scrollView.contentOffset.x) {
        // moved right
        NSLog(@"right");
    }
    else if (_lastContentOffset.x > (int)scrollView.contentOffset.x) {
        // moved left
        NSLog(@"left");

    }else if (_lastContentOffset.y<(int)scrollView.contentOffset.y){
        NSLog(@"up");

    }else if (_lastContentOffset.y>(int)scrollView.contentOffset.y){
        NSLog(@"down");
        [self.txtText resignFirstResponder];

    }
}

Dadurch wird textView ausgelöst und nach dem Ende des Ziehens geschlossen


0
- (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset {
NSLog(@"px %f py %f",velocity.x,velocity.y);}

Verwenden Sie diese Delegatmethode für die Bildlaufansicht.

Wenn Ihre Geschwindigkeitskoordinate + ve ist, wird die Bildlaufansicht nach unten und bei -ve Bildlaufansicht nach oben gescrollt. In ähnlicher Weise kann der linke und rechte Bildlauf mit der x-Koordinate erkannt werden.


0

Short & Easy wäre, überprüfen Sie einfach den Geschwindigkeitswert, wenn er größer als Null ist, dann scrollen Sie nach links oder rechts:

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

    var targetOffset = Float(targetContentOffset.memory.x)
    println("TargetOffset: \(targetOffset)")
    println(velocity)

    if velocity.x < 0 {
        scrollDirection = -1 //scrolling left
    } else {
        scrollDirection = 1 //scrolling right
    }
}

0

Wenn Sie mit UIScrollView und UIPageControl arbeiten, ändert diese Methode auch die Seitenansicht von PageControl.

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

    let targetOffset = targetContentOffset.memory.x
    let widthPerPage = scrollView.contentSize.width / CGFloat(pageControl.numberOfPages)

    let currentPage = targetOffset / widthPerPage
    pageControl.currentPage = Int(currentPage)
}

Vielen Dank an den Swift-Code von @Esq.


0

Swift 2.2 Einfache Lösung, die einzelne und mehrere Richtungen ohne Verlust verfolgt.

  // Keep last location with parameter
  var lastLocation:CGPoint = CGPointZero

  // We are using only this function so, we can
  // track each scroll without lose anyone
  override func scrollViewWillBeginDragging(scrollView: UIScrollView) {
    let currentLocation = scrollView.contentOffset

    // Add each direction string
    var directionList:[String] = []

    if lastLocation.x < currentLocation.x {
      //print("right")
      directionList.append("Right")
    } else if lastLocation.x > currentLocation.x {
      //print("left")
      directionList.append("Left")
    }

    // there is no "else if" to track both vertical
    // and horizontal direction
    if lastLocation.y < currentLocation.y {
      //print("up")
      directionList.append("Up")
    } else if lastLocation.y > currentLocation.y {
      //print("down")
      directionList.append("Down")
    }

    // scrolled to single direction
    if directionList.count == 1 {
      print("scrolled to \(directionList[0]) direction.")
    } else if directionList.count > 0  { // scrolled to multiple direction
      print("scrolled to \(directionList[0])-\(directionList[1]) direction.")
    }

    // Update last location after check current otherwise,
    // values will be same
    lastLocation = scrollView.contentOffset
  }

0

In allen oberen Antworten verwenden panGestureRecognizeroder verwenden zwei Hauptmethoden zur Lösung des ProblemscontentOffset . Beide Methoden haben ihre Vor- und Nachteile.

Methode 1: panGestureRecognizer

Wenn Sie panGestureRecognizerwie von @followben vorgeschlagen verwenden und Ihre Bildlaufansicht nicht programmgesteuert scrollen möchten, funktioniert dies ordnungsgemäß.

- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView
{ 
    if ([scrollView.panGestureRecognizer translationInView:scrollView.superview].x > 0) {
        // handle dragging to the right
    } else {
        // handle dragging to the left
    }
}

Nachteile

Wenn Sie jedoch die Bildlaufansicht mit folgendem Code verschieben, kann der obere Code ihn nicht erkennen:

setContentOffset(CGPoint(x: 100, y: 0), animation: false)

Methode 2: contentOffset

var lastContentOffset: CGPoint = CGPoint.zero

func scrollViewDidScroll(_ scrollView: UIScrollView) {
    if (self.lastContentOffset.x > scrollView.contentOffset.x) {
        // scroll to right
    } else if self.lastContentOffset.x < scrollView.contentOffset.x {
        // scroll to left
    }
    self.lastContentOffset = self.scrollView.contentOffset
}

Nachteile

Wenn Sie contentOffset während des Bildlaufs programmgesteuert ändern möchten (z. B. wenn Sie einen unendlichen Bildlauf erstellen möchten), ist diese Methode problematisch, da Sie contentOffsetbeim Ändern der Positionen der Inhaltsansichten möglicherweise Änderungen vornehmen und in dieser Zeit der obere Code springt, indem Sie nach rechts oder scrollen links.


0
//Vertical detection
    var lastVelocityYSign = 0
            func scrollViewDidScroll(_ scrollView: UIScrollView) {
                let currentVelocityY =  scrollView.panGestureRecognizer.velocity(in: scrollView.superview).y
                let currentVelocityYSign = Int(currentVelocityY).signum()
                if currentVelocityYSign != lastVelocityYSign &&
                    currentVelocityYSign != 0 {
                    lastVelocityYSign = currentVelocityYSign
                }
                if lastVelocityYSign < 0 {
                    print("SCROLLING DOWN")
                } else if lastVelocityYSign > 0 {
                    print("SCOLLING UP")
                }
            }

Antwort von Mos6y https://medium.com/@Mos6yCanSwift/swift-ios-determine-scroll-direction-d48a2327a004

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.