Trovare la direzione di scorrimento in un UIScrollView?


183

Ho un UIScrollViewsolo scorrimento orizzontale consentito e vorrei sapere quale direzione (sinistra, destra) scorre l'utente. Quello che ho fatto è stato di sottoclassare il UIScrollViewe sovrascrivere il touchesMovedmetodo:

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

    }

}

Ma questo metodo a volte non viene chiamato affatto quando mi muovo. Cosa ne pensi?


Vedi la mia risposta qui sotto - dovresti usare i delegati della vista di scorrimento per farlo.
Memmons

Risposte:


392

Determinare la direzione è abbastanza semplice, ma tieni presente che la direzione può cambiare più volte nel corso di un gesto. Ad esempio, se si dispone di una vista di scorrimento con il paging attivato e l'utente scorre per passare alla pagina successiva, la direzione iniziale potrebbe essere rivolta a destra, ma se è stato attivato il rimbalzo, non andrà per un breve periodo in nessuna direzione e poi andiamo brevemente a sinistra.

Per determinare la direzione, dovrai utilizzare il UIScrollView scrollViewDidScrolldelegato. In questo esempio, ho creato una variabile denominata lastContentOffsetche utilizzo per confrontare l'offset del contenuto corrente con quello precedente. Se è maggiore, allora scrollView sta scorrendo a destra. Se è inferiore, scrollView scorre a sinistra:

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

Sto usando il seguente enum per definire la direzione. L'impostazione del primo valore su ScrollDirectionNone ha l'ulteriore vantaggio di rendere quella direzione predefinita quando si inizializzano le variabili:

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

1
Come posso ottenere lastContentOffset
Dev

@JasonZhao Heh - Stavo ottenendo alcuni strani risultati quando stavo testando il codice in modo intenzionale perché non avevo tenuto conto del fatto che il rimbalzo della vista di scorrimento causava la direzione di scorrimento su..er..bounce. Quindi l'ho aggiunto all'enum quando ho trovato risultati inaspettati.
Memmons,

1
@Dev È nel codicelastContentOffset = scrollView.contentOffset.x;
memmons

@ akivag29 Buona cattura del lastContentOffsettipo. Per quanto riguarda le proprietà e le variabili statiche: entrambe le soluzioni sono una buona scelta. Non ho preferenze reali. È un buon punto.
Memmons,

2
@JosueEspinosa Per ottenere la direzione di scorrimento corretta, è necessario inserirlo in scrollViewWillBeginDragging: metodo per la chiamata del setter lastContentOffset.
strawnut,

73

... Vorrei sapere quale direzione (sinistra, destra) scorre l'utente.

In tal caso, su iOS 5 e versioni successive, utilizzare UIScrollViewDelegateper determinare la direzione del gesto di panoramica dell'utente:

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

1
potrebbe essere la soluzione migliore, ma con un avvio molto lento, dx sarà uguale a 0.
trickster77777

Certo che lo fa. Questo dovrebbe essere migliorato molto di più in quanto si tratta più di un modo "nativo" o corretto principalmente perché è necessario utilizzare qualsiasi valore memorizzato dalla proprietà.
Michi,

Questo non ha lo stesso problema di stackoverflow.com/a/26192103/62 in cui non funzionerà correttamente una volta che lo scorrimento del momento si avvia dopo che l'utente ha interrotto la panoramica?
Liron Yahdav,

60

utilizzando scrollViewDidScroll: è un buon modo per trovare la direzione corrente.

Se si desidera conoscere la direzione dopo che l'utente ha terminato lo scorrimento, utilizzare quanto segue:

@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
Questa è un'ottima aggiunta a Justin, tuttavia vorrei aggiungere una modifica. Se la visualizzazione di scorrimento ha il paging abilitato e si esegue un trascinamento che finisce per tornare alla sua posizione iniziale, l'attuale condizione "altro" considererà una "spostata a sinistra", anche se dovrebbe essere "nessuna modifica".
Scott Lieberman,

1
@ScottLieberman hai ragione, ho aggiornato il codice di conseguenza.
Justin Tanner,

50

Non è necessario aggiungere una variabile aggiuntiva per tenerne traccia. Basta usare la UIScrollView's panGestureRecognizerproprietà come questa. Sfortunatamente, questo funziona solo se la velocità non è 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");
}

È possibile utilizzare una combinazione di componenti xey per rilevare su, giù, sinistra e destra.


9
sfortunatamente questo funziona solo durante il trascinamento. la velocità è 0 quando i tocchi vengono rimossi, anche mentre si scorre ancora.
Heiko,

Bello +1. altrimenti parte Velocity 0 perché, l'utente non sta più trascinando, quindi la velocità dei gesti è 0
Pavan

È possibile memorizzare la direzione in variabile. Cambia solo quando la velocità! = 0. Per me funziona
Leszek Zarna,

Funziona con scrollViewWillBeginDragging, fantastico
Demensdeum

40

La soluzione

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

1
Provalo velocityInViewinvece di translationInView. Questo risolve il problema che si presenta quando si cambia direzione nello stesso movimento.
enreas

2
Questa soluzione funziona perfettamente. La risposta più accettata non funziona alcune volte quando vado alla schermata successiva e torno a eseguire le operazioni di scorrimento su e giù.
Anjaneyulu Battula

Il migliore, grazie!
Booharin,

18

Swift 4:

Per lo scorrimento orizzontale puoi semplicemente fare:

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

Per lo scorrimento verticale, cambiare .xcon.y


14

In iOS8 Swift ho usato questo metodo:

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)

}

Ho una vista dinamica che cambia posizione mentre l'utente scorre così la vista può sembrare che sia rimasta nello stesso posto sullo schermo. Sto anche monitorando quando l'utente sta salendo o scendendo.

Ecco anche un modo alternativo:

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
Usa x invece di y?
Esqarrouth,

In realtà voglio rilevarlo per UiCollectionView. Ho lo scorrimento orizzontale. Quindi rileverò in crollViewDidEndDeceleratinggiusto?
Umair Afzal,

8

Questo è ciò che ha funzionato per me (in 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
Questo metodo non viene chiamato quando il valore della proprietà pagingEnabled della vista di scorrimento è SÌ.
Ako,

5

In alternativa, è possibile osservare il percorso chiave "contentOffset". Ciò è utile quando non è possibile impostare / modificare il delegato della vista di scorrimento.

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

Dopo aver aggiunto l'osservatore, ora puoi:

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

Ricorda di rimuovere l'osservatore quando necessario. ad esempio, è possibile aggiungere l'osservatore in viewWillAppear e rimuoverlo in viewWillDisappear


5

In breve tempo:

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

Puoi farlo anche in scrollViewDidScroll.


4

Ecco la mia soluzione per comportamenti come nella risposta @followben, ma senza perdita con avvio lento (quando dy è 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

Ho verificato alcune delle risposte e ho elaborato la risposta di AnswerBot avvolgendo tutto in una selezione nella categoria UIScrollView. "LastContentOffset" viene invece salvato all'interno di uiscrollview e quindi è solo una questione di chiamata:

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

Codice sorgente su https://github.com/tehjord/UIScrollViewScrollingDirection


1

Preferisco fare un po 'di filtraggio, basato sulla risposta di @ memmons

In Objective-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
    } 
}

Quando testato in - (void)scrollViewDidScroll:(UIScrollView *)scrollView:

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

img

self.lastContentOffset cambia molto velocemente, il divario di valore è quasi 0,5 f.

Non è necessario.

E occasionalmente, se gestito in condizioni accurate, il tuo orientamento potrebbe perdersi. (dichiarazioni di implementazione saltate a volte)

ad esempio :

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

Quando il paging è attivato, è possibile utilizzare questo codice.

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

I codici si spiegano, credo. CGFloat differenza1 e differenza2 dichiarate nella stessa interfaccia privata di classe. Buono se contentSize rimane lo stesso.

- (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, quindi per me questa implementazione funziona davvero bene:

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

    }
}

Quindi questo genererà textView per chiudere dopo il termine del trascinamento


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

Utilizzare questo metodo delegato di scrollview.

Se y coordinata di velocità è + ve scroll view scorre verso il basso e se è -ve scrollview scorre verso l'alto. Allo stesso modo è possibile rilevare lo scorrimento sinistro e destro utilizzando la coordinata x.


0

Breve e facile sarebbe, basta controllare il valore della velocità, se è maggiore di zero, quindi scorrere a sinistra altrimenti a destra:

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

Se lavori con UIScrollView e UIPageControl, questo metodo cambierà anche la visualizzazione della pagina di 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)
}

Grazie al codice Swift di @Esq.


0

Swift 2.2 Soluzione semplice che tiene traccia di direzioni singole e multiple senza alcuna perdita.

  // 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 tutte le risposte superiori si utilizzano due modi principali per risolvere il problema panGestureRecognizero contentOffset. Entrambi i metodi hanno i loro pro e contro.

Metodo 1: panGestureRecognizer

Quando usi panGestureRecognizercome suggerito da @followben, se non vuoi scorrere a livello di codice la vista di scorrimento, funziona correttamente.

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

Contro

Ma se si sposta la vista di scorrimento con il seguente codice, il codice superiore non può riconoscerlo:

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

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

Contro

Se vuoi cambiare contentOffset a livello di codice, durante lo scroll (come quando vuoi creare uno scroll infinito), questo metodo crea problemi, perché potresti cambiare contentOffsetdurante la modifica dei luoghi di visualizzazione dei contenuti e in questo momento, il codice superiore salta in quello che scorri verso destra o sinistra.


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

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

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.