Trouver la direction du défilement dans un UIScrollView?

184

J'ai un UIScrollViewavec seulement le défilement horizontal autorisé, et j'aimerais savoir dans quelle direction (gauche, droite) l'utilisateur fait défiler. Ce que j'ai fait a été de sous-classer le UIScrollViewet de remplacer la touchesMovedméthode:

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

    }

}

Mais cette méthode n'est parfois pas du tout appelée lorsque je déménage. Qu'est-ce que tu penses?

Alex1987
la source
Voir ma réponse ci-dessous - vous devriez utiliser les délégués de la vue de défilement pour ce faire.
memmons
meilleure réponse ici: stackoverflow.com/questions/11262583/…
iksnae

Réponses:

392

La détermination de la direction est assez simple, mais gardez à l'esprit que la direction peut changer plusieurs fois au cours d'un geste. Par exemple, si vous avez une vue de défilement avec la pagination activée et que l'utilisateur glisse pour aller à la page suivante, la direction initiale peut être vers la droite, mais si vous avez activé le rebond, il n'ira brièvement dans aucune direction du tout et puis aller brièvement vers la gauche.

Pour déterminer la direction, vous devrez utiliser le UIScrollView scrollViewDidScrolldélégué. Dans cet exemple, j'ai créé une variable nommée lastContentOffsetque j'utilise pour comparer le décalage de contenu actuel avec le précédent. S'il est supérieur, le scrollView défile vers la droite. S'il est inférieur, le scrollView défile vers la gauche:

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

J'utilise l'énumération suivante pour définir la direction. La définition de la première valeur sur ScrollDirectionNone présente l'avantage supplémentaire de faire de cette direction la valeur par défaut lors de l'initialisation des variables:

typedef NS_ENUM(NSInteger, ScrollDirection) {
    ScrollDirectionNone,
    ScrollDirectionRight,
    ScrollDirectionLeft,
    ScrollDirectionUp,
    ScrollDirectionDown,
    ScrollDirectionCrazy,
};
Memmons
la source
1
Comment puis-je obtenir lastContentOffset
Dev
@JasonZhao Heh - J'obtenais des résultats étranges lorsque je testais le code initialement parce que je n'avais pas pris en compte le fait que le rebond de la vue de défilement entraînait la direction du défilement vers..er..bounce. Donc, j'ai ajouté cela à l'énumération lorsque j'ai trouvé des résultats inattendus.
memmons
1
@Dev C'est dans le codelastContentOffset = scrollView.contentOffset.x;
memmons
@ akivag29 Bonne prise sur le lastContentOffsettype. Concernant la propriété vs les variables statiques: l'une ou l'autre des solutions est un bon choix. Je n'ai pas vraiment de préférence. C'est un bon point.
memmons
2
@JosueEspinosa Pour obtenir la direction de défilement correcte, vous le placez dans scrollViewWillBeginDragging: méthode pour appeler le setter lastContentOffset.
fraise le
74

... Je voudrais savoir dans quelle direction (gauche, droite) l'utilisateur fait défiler.

Dans ce cas, sur iOS 5 et supérieur, utilisez le UIScrollViewDelegatepour déterminer la direction du mouvement panoramique de l'utilisateur:

- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView
{ 
    if ([scrollView.panGestureRecognizer translationInView:scrollView.superview].x > 0) {
        // handle dragging to the right
    } else {
        // handle dragging to the left
    }
}
Followben
la source
1
pourrait être la meilleure solution, mais avec un démarrage très lent, dx sera égal à 0.
trickster77777
Bien sûr que oui. Cela devrait être davantage voté car il s'agit plutôt d'une manière «native» ou appropriée, principalement parce qu'il est nécessaire d'utiliser des valeurs stockées dans la propriété.
Michi
Cela n'a-t-il pas le même problème que stackoverflow.com/a/26192103/62 où il ne fonctionnera pas correctement une fois que le défilement d'élan entrera en vigueur après que l'utilisateur arrête le panoramique?
Liron Yahdav
60

L'utilisation scrollViewDidScroll:est un bon moyen de trouver la direction actuelle.

Si vous souhaitez connaître la direction une fois que l'utilisateur a terminé le défilement, utilisez ce qui suit:

@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
la source
3
C'est un excellent ajout Justin, mais j'aimerais ajouter une modification. Si votre scrollview a activé la pagination, et que vous effectuez un glissement qui finit par revenir à sa position initiale, la condition "else" actuelle considérera que "déplacé vers la gauche", même si cela devrait être "aucun changement".
Scott Lieberman
1
@ScottLieberman votre droite, j'ai mis à jour le code en conséquence.
Justin Tanner du
51

Pas besoin d'ajouter une variable supplémentaire pour garder une trace de cela. Il suffit d' utiliser la UIScrollViewde » panGestureRecognizerpropriété comme celui - ci. Malheureusement, cela ne fonctionne que si la vitesse n'est pas de 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");
}

Vous pouvez utiliser une combinaison de composants x et y pour détecter le haut, le bas, la gauche et la droite.

Rounak
la source
9
malheureusement cela ne fonctionne que pendant le glissement. la vélocité est de 0 lorsque les touches sont supprimées, même pendant le défilement.
heiko
Nice +1. else part Velocity 0 parce que l'utilisateur ne fait plus glisser, donc la vitesse du geste est de 0
Pavan
Vous pouvez stocker la direction dans la variable. Changer uniquement lorsque la vitesse! = 0. For me works
Leszek Zarna
Fonctionne avec scrollViewWillBeginDragging, awesome
demensdeum
41

La solution

func scrollViewDidScroll(scrollView: UIScrollView) {
     if(scrollView.panGestureRecognizer.translationInView(scrollView.superview).y > 0)
     {
         print("up")
     }
    else
    {
         print("down")
    } 
}
davidrelgr
la source
2
Essayez-le avec velocityInViewau lieu de translationInView. Cela résout le problème qui se pose lors du changement de direction dans le même mouvement.
enreas le
2
Cette solution fonctionne parfaitement. La réponse la plus acceptée ne fonctionne pas parfois lorsque je passe à l'écran suivant et que je reviens pour effectuer des opérations de défilement vers le haut et vers le bas.
Anjaneyulu Battula
Le meilleur, thnx!
Booharin
20

Swift 4:

Pour le défilement horizontal, vous pouvez simplement faire:

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

Pour un changement de défilement vertical .xavec.y

Alessandro Ornano
la source
14

Dans iOS8 Swift, j'ai utilisé cette méthode:

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)

}

J'ai une vue dynamique qui change d'emplacement au fur et à mesure que l'utilisateur fait défiler afin que la vue puisse sembler restée au même endroit sur l'écran. Je suis également le suivi lorsque l'utilisateur monte ou descend.

Voici également une alternative:

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
la source
1
Utilisez x au lieu de y?
Esqarrouth
En fait, je veux le détecter pour UiCollectionView. J'ai un défilement horizontal. Alors je vais détecter en crollViewDidEndDeceleratingdroit?
Umair Afzal
8

Voici ce que cela a fonctionné pour moi (en Objective-C):

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

        NSString *direction = ([scrollView.panGestureRecognizer translationInView:scrollView.superview].y >0)?@"up":@"down";
        NSLog(@"%@",direction);
    }
Javier Calatrava Llavería
la source
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
la source
2
Cette méthode n'est pas appelée lorsque la valeur de la propriété pagingEnabled de la vue de défilement est YES.
Ako
6

En rapide:

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

Vous pouvez également le faire dans scrollViewDidScroll.

Javier Calatrava Llavería
la source
5

Alternativement, il est possible d'observer le chemin de clé "contentOffset". Ceci est utile lorsqu'il n'est pas possible pour vous de définir / modifier le délégué de la vue de défilement.

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

Après avoir ajouté l'observateur, vous pouvez maintenant:

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

N'oubliez pas de retirer l'observateur si nécessaire. par exemple, vous pouvez ajouter l'observateur dans viewWillAppear et le supprimer dans viewWillDisappear

xu huanze
la source
4

Voici ma solution pour un comportement comme dans @followben answer, mais sans perte avec démarrage lent (quand dy vaut 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 Joukov
la source
2
//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")
                }
            }

Réponse de Mos6y https://medium.com/@Mos6yCanSwift/swift-ios-determine-scroll-direction-d48a2327a004

slimas
la source
1
Merci. C'est une bonne solution.
Mohd Haider le
1

J'ai vérifié une partie de la réponse et développé la réponse AnswerBot en enveloppant tout dans une goutte dans la catégorie UIScrollView. Le "lastContentOffset" est enregistré à la place dans uiscrollview, puis il suffit d'appeler:

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

Code source sur https://github.com/tehjord/UIScrollViewScrollingDirection

Bjergsen
la source
1

Je préfère faire du filtrage, basé sur la réponse de @ memmons

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

Lors d'un test dans - (void)scrollViewDidScroll:(UIScrollView *)scrollView:

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

img

self.lastContentOffset change très rapidement, l'écart de valeur est de près de 0,5f.

Ce n'est pas nécessaire.

Et parfois, lorsqu'il est manipulé dans des conditions précises, votre orientation peut se perdre. (instructions d'implémentation sautées parfois)

tel que :

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

Dans 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
    }
}
perle noire
la source
0

Lorsque la pagination est activée, vous pouvez utiliser ce code.

- (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");
    }
}
Poiré
la source
0

Codes s'explique, je suppose. CGFloat difference1 et difference2 déclarées dans la même interface privée de classe. Bon si contentSize reste le même.

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

       }
user2511630
la source
0

Ok donc pour moi cette implémentation fonctionne vraiment bien:

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

    }
}

Donc, cela déclenchera textView pour le rejeter après la fin du glissement

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

Utilisez cette méthode déléguée de scrollview.

Si la coordonnée y de la vitesse est + ve, la vue de défilement défile vers le bas et si elle est de -ve, la vue de défilement défile vers le haut. De même, le défilement gauche et droit peut être détecté en utilisant la coordonnée x.

Nilesh Tupe
la source
0

Short & Easy serait, il suffit de vérifier la valeur de la vitesse, si elle est supérieure à zéro, alors son défilement à gauche ou à droite:

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
la source
0

Si vous travaillez avec UIScrollView et UIPageControl, cette méthode modifiera également la vue de page du 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)
}

Merci le code Swift de @Esq.

charles.cc.hsu
la source
0

Swift 2.2 Solution simple qui suit des directions simples et multiples sans aucune perte.

  // 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
la source
0

Dans toutes les réponses supérieures, deux façons principales de résoudre le problème consistent à utiliser panGestureRecognizerou contentOffset. Les deux méthodes ont leurs inconvénients et leurs avantages.

Méthode 1: panGestureRecognizer

Lorsque vous utilisez panGestureRecognizercomme ce que @followben a suggéré, si vous ne voulez pas faire défiler votre vue de défilement par programme, cela fonctionne correctement.

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

Les inconvénients

Mais si vous déplacez la vue de défilement avec le code suivant, le code supérieur ne peut pas le reconnaître:

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

Méthode 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
}

Les inconvénients

Si vous voulez changer contentOffset par programme, pendant le défilement (comme lorsque vous voulez créer un défilement infini), cette méthode pose problème, car vous pouvez changer contentOffsetlors du changement de lieux d'affichage du contenu et dans ce temps, le code supérieur saute dans lequel vous faites défiler vers la droite ou la gauche.

Esmaeil
la source