Znaleźć kierunek przewijania w UIScrollView?


183

Mam UIScrollViewdozwolone tylko przewijanie w poziomie i chciałbym wiedzieć, w którym kierunku (w lewo, w prawo) użytkownik przewija. To, co zrobiłem, było podklasowanie UIScrollViewi zastąpienie touchesMovedmetody:

- (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");

    }

}

Ale ta metoda czasami nie jest wywoływana, kiedy się przeprowadzam. Co myślisz?


Zobacz moją odpowiedź poniżej - w tym celu powinieneś użyć delegatów widoku przewijania.
memmons

najlepsza odpowiedź tutaj: stackoverflow.com/questions/11262583/…
iksnae

Odpowiedzi:


392

Określenie kierunku jest dość proste, ale należy pamiętać, że kierunek może się zmieniać kilka razy w trakcie gestu. Na przykład, jeśli masz widok przewijania z włączonym stronicowaniem, a użytkownik przesuwa palcem, aby przejść do następnej strony, początkowy kierunek może być prawy, ale jeśli masz włączone odbijanie, na krótko nie będzie w żadnym kierunku i potem na krótko idź w lewo.

Aby określić kierunek, musisz użyć UIScrollView scrollViewDidScrolldelegata. W tym przykładzie utworzyłem zmienną o nazwie, lastContentOffsetktórej używam do porównania bieżącego przesunięcia zawartości z poprzednim. Jeśli jest większy, scrollView przewija w prawo. Jeśli jest mniej, scrollView przewija się w lewo:

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

Używam następującego wyliczenia do zdefiniowania kierunku. Ustawienie pierwszej wartości na ScrollDirectionNone ma tę dodatkową zaletę, że ustawia ten kierunek jako domyślny podczas inicjowania zmiennych:

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

1
Jak mogę uzyskać lastContentOffset
Dev

@JasonZhao Heh - Otrzymywałem dziwne wyniki, kiedy testowałem kod, ponieważ nie wziąłem pod uwagę, że odbicie od widoku przewijania powoduje kierunek przewijania w kierunku ... odbicia. Dodałem to do wyliczenia, gdy znalazłem nieoczekiwane wyniki.
memmons,

1
@Dev Jest w kodzielastContentOffset = scrollView.contentOffset.x;
memmons,

@ akivag29 Dobry haczyk na lastContentOffsettyp. Jeśli chodzi o właściwość vs zmienne statyczne: każde rozwiązanie jest dobrym wyborem. Nie mam żadnych preferencji. To dobra uwaga.
memmons

2
@JosueEspinosa Aby uzyskać prawidłowy kierunek przewijania, umieść go w scrollViewWillBeginDragging: metoda wywołania setterent lastContentOffset.
truskawkowy

73

... Chciałbym wiedzieć, w którym kierunku (w lewo, w prawo) użytkownik przewija.

W takim przypadku w systemie iOS 5 i nowszym użyj przycisku, UIScrollViewDelegateaby określić kierunek gestu panoramy użytkownika:

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

1
może być najlepszym rozwiązaniem, ale przy naprawdę powolnym starcie dx wyniesie 0
trickster77777

Jasne, że tak. Powinno to być bardziej uprzywilejowane, ponieważ jest to bardziej „natywny” lub właściwy sposób, głównie dlatego, że konieczne jest użycie wartości przechowywanych w nieruchomości.
Michi

Czy to nie ma tego samego problemu, co stackoverflow.com/a/26192103/62, gdzie nie będzie działał poprawnie po uruchomieniu przewijania pędu, gdy użytkownik przestanie przesuwać?
Liron Yahdav,

59

Używanie scrollViewDidScroll:to dobry sposób na znalezienie bieżącego kierunku.

Jeśli chcesz poznać kierunek po zakończeniu przewijania, użyj następujących poleceń:

@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
To świetny dodatek Justin, jednak chciałbym dodać jedną edycję. Jeśli w widoku przewijania włączona jest funkcja stronicowania i przeciągnięcie zakończy się powrotem do jego początkowej pozycji, bieżący warunek „else” uzna, że ​​„przesunięto w lewo”, nawet jeśli powinno to być „bez zmian”.
Scott Lieberman,

1
@ ScottLieberman masz prawo, odpowiednio zaktualizowałem kod.
Justin Tanner

50

Nie trzeba dodawać dodatkowej zmiennej, aby to śledzić. Wystarczy użyć UIScrollView„s panGestureRecognizerwłasności takiego. Niestety działa to tylko wtedy, gdy prędkość nie wynosi 0:

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

Możesz użyć kombinacji składników x i y do wykrywania w górę, w dół, w lewo i w prawo.


9
niestety to działa tylko podczas przeciągania. prędkość wynosi 0 po usunięciu dotknięć, nawet podczas przewijania.
heiko

Niezły +1. inaczej część Velocity 0, ponieważ użytkownik już nie przeciąga, więc prędkość gestu wynosi 0
Pavan

Możesz przechowywać kierunek w zmiennej. Zmień tylko, gdy prędkość! = 0. Dla mnie działa
Leszek Zarna


40

Rozwiązanie

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

1
Spróbuj velocityInViewzamiast translationInView. To rozwiązuje problem pojawiający się przy zmianie kierunku w tym samym ruchu.
enreas

2
To rozwiązanie działa idealnie. Najczęściej akceptowana odpowiedź nie działa czasami, kiedy przechodzę do następnego ekranu i wracam, wykonując operacje przewijania w górę i w dół.
Anjaneyulu Battula

Najlepsze, dzięki!
Booharin

18

Swift 4:

Aby przewijać w poziomie, możesz po prostu zrobić:

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

Aby przewijać w pionie, zmień za .xpomocą.y


14

W iOS8 Swift zastosowałem tę metodę:

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)

}

Mam widok dynamiczny, który zmienia lokalizacje podczas przewijania przez użytkownika, dzięki czemu widok może wyglądać, jakby pozostał w tym samym miejscu na ekranie. Śledzę również, kiedy użytkownik idzie w górę lub w dół.

Oto także alternatywny sposób:

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
Używać x zamiast y?
Esqarrouth,

Właściwie chcę to wykryć dla UiCollectionView. Mam przewijanie w poziomie. Więc wykryję, crollViewDidEndDeceleratingprawda?
Umair Afzal

8

Oto, co zadziałało dla mnie (w Objective-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
Ta metoda nie jest wywoływana, gdy wartością właściwości pagingEnabled widoku przewijania jest TAK.
Ako

5

Alternatywnie można obserwować kluczową ścieżkę „contentOffset”. Jest to przydatne, gdy nie można ustawić / zmienić delegata widoku przewijania.

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

Po dodaniu obserwatora możesz teraz:

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

Pamiętaj, aby w razie potrzeby usunąć obserwatora. np. możesz dodać obserwatora do widokuWillAppear i usunąć go w viewWillDisappear


5

W krótkim:

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

Możesz to zrobić również w scrollViewDidScroll.


4

Oto moje rozwiązanie dla zachowania jak w odpowiedzi @followben, ale bez strat z powolnym startem (gdy dy wynosi 0)

@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

Sprawdziłem niektóre odpowiedzi i opracowałem odpowiedź AnswerBot, pakując wszystko w kropkę w kategorii UIScrollView. Zamiast tego „lastContentOffset” jest zapisywany w widoku uiscrollview, a następnie wystarczy wywołać:

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

Kod źródłowy na https://github.com/tehjord/UIScrollViewScrollingDirection


1

Wolę filtrować na podstawie odpowiedzi @ memmons

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

Podczas testowania w - (void)scrollViewDidScroll:(UIScrollView *)scrollView:

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

img

self.lastContentOffset zmienia się bardzo szybko, różnica wartości wynosi prawie 0,5f.

To nie jest konieczne.

A czasami, gdy jest obsługiwany w dokładnym stanie, twoja orientacja może się zgubić. (instrukcje implementacji czasami pomijane)

Jak na przykład :

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

W 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

Gdy stronicowanie jest włączone, możesz użyć tego kodu.

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

Wydaje mi się, że kody same się wyjaśniają. CGFloat różnica1 i różnica2 zadeklarowane w prywatnym interfejsie tej samej klasy. Dobrze, jeśli contentSize pozostaje taki sam.

- (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, więc dla mnie ta implementacja działa naprawdę dobrze:

@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];

    }
}

Spowoduje to uruchomienie textView do odrzucenia po zakończeniu przeciągania


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

Skorzystaj z tej metody widoku przewijania.

Jeżeli współrzędna prędkości wynosi + ve, widok przewijania przewija w dół, a jeśli jest -ve, przewijanie przewija w górę. Podobnie przewijanie w lewo i w prawo można wykryć za pomocą współrzędnej x.


0

Krótko i łatwo byłoby, po prostu sprawdź wartość prędkości, jeśli jest większa od zera, to przewijanie w lewo, gdzie indziej:

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

Jeśli pracujesz z UIScrollView i UIPageControl, ta metoda zmieni również widok strony 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)
}

Dzięki kod Swift @Esq.


0

Swift 2.2 Proste rozwiązanie, które śledzi jeden i wiele kierunków bez żadnych strat.

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

We wszystkich górnych odpowiedziach dwoma głównymi sposobami rozwiązania problemu jest użycie panGestureRecognizerlub contentOffset. Obie metody mają swoje wady i zalety.

Metoda 1: panGestureRecognizer

Kiedy używasz panGestureRecognizertego, co sugerował @followben, jeśli nie chcesz programowo przewijać widoku przewijania, działa poprawnie.

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

Cons

Ale jeśli przeniesiesz widok przewijania z następującym kodem, górny kod nie może go rozpoznać:

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

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

Cons

Jeśli chcesz programowo zmienić zawartość Offset, podczas przewijania (np. Gdy chcesz utworzyć przewijanie nieskończone), ta metoda stanowi problem, ponieważ możesz zmienić contentOffsetpodczas zmiany miejsc widoków treści i tym razem przeskoczyć górny kod, przewijając w prawo lub lewo.


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

Odpowiedź od Mos6y https://medium.com/@Mos6yCanSwift/swift-ios-determine-scroll-direction-d48a2327a004

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.