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?

Alex1987
źródło
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,
};
memmons
źródło
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
    }
}
followben
źródło
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
    }
}
Justin Tanner
źródło
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.

rounak
źródło
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
Działa z
scrollViewWillBegin Przeciąganie
40

Rozwiązanie

func scrollViewDidScroll(scrollView: UIScrollView) {
     if(scrollView.panGestureRecognizer.translationInView(scrollView.superview).y > 0)
     {
         print("up")
     }
    else
    {
         print("down")
    } 
}
Davidrelgr
źródło
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

Alessandro Ornano
źródło
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!")
    }
}
Esqarrouth
źródło
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);
    }
Javier Calatrava Llavería
źródło
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");
    }
}
Oded Regev
źródło
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

xu huanze
źródło
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.

Javier Calatrava Llavería
źródło
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
                }
            }
        }
    }
}
Andrey Zhukov
źródło
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

Bjergsen
źródło
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
    }
}
czarna Perła
źródło
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");
    }
}
Wino z gruszek
źródło
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;

       }
użytkownik2511630
źródło
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

użytkownik2021505
źródło
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.

Nilesh Tupe
źródło
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
    }
}
iDilip
źródło
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.

charles.cc.hsu
źródło
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
  }
fatihyildizhan
źródło
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.

Esmaeil
źródło
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

slimas
źródło