¿Encontrar la dirección de desplazamiento en un UIScrollView?


183

Tengo un UIScrollViewsolo desplazamiento horizontal permitido, y me gustaría saber en qué dirección (izquierda, derecha) se desplaza el usuario. Lo que hice fue subclasificar UIScrollViewy anular el touchesMovedmétodo:

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

    }

}

Pero este método a veces no recibe ninguna llamada cuando me muevo. ¿Qué piensas?


Vea mi respuesta a continuación: debe usar los delegados de la vista de desplazamiento para hacer esto.
memmons

Respuestas:


392

Determinar la dirección es bastante sencillo, pero tenga en cuenta que la dirección puede cambiar varias veces en el transcurso de un gesto. Por ejemplo, si tiene una vista de desplazamiento con la paginación activada y el usuario desliza el dedo para ir a la página siguiente, la dirección inicial podría ser hacia la derecha, pero si tiene activado el rebote, brevemente no irá en ninguna dirección y luego iré brevemente hacia la izquierda.

Para determinar la dirección, deberá usar el UIScrollView scrollViewDidScrolldelegado. En esta muestra, creé una variable llamada lastContentOffsetque utilizo para comparar el desplazamiento del contenido actual con el anterior. Si es mayor, entonces scrollView se desplaza hacia la derecha. Si es menor, entonces scrollView se desplaza hacia la izquierda:

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

Estoy usando la siguiente enumeración para definir la dirección. Establecer el primer valor en ScrollDirectionNone tiene el beneficio adicional de hacer que esa dirección sea la predeterminada al inicializar variables:

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

1
¿Cómo puedo obtener lastContentOffset
Dev

@JasonZhao Heh: estaba obteniendo algunos resultados extraños cuando estaba probando el código inicialmente porque no tenía en cuenta que el rebote de la vista de desplazamiento causa que la dirección de desplazamiento rebote. Entonces, agregué eso a la enumeración cuando encontré resultados inesperados.
memmons

1
@Dev Está en el códigolastContentOffset = scrollView.contentOffset.x;
memmons

@ akivag29 Buena captura del lastContentOffsettipo. Con respecto a la propiedad frente a las variables estáticas: cualquier solución es una buena opción. No tengo preferencia real. Es un buen punto.
memmons

2
@JosueEspinosa Para obtener la dirección de desplazamiento correcta, póngala en scrollViewWillBeginDragging: método para la última llamada del configuradorContentOffset.
Strawnut

73

... Me gustaría saber en qué dirección (izquierda, derecha) se desplaza el usuario.

En ese caso, en iOS 5 y superior, use UIScrollViewDelegatepara determinar la dirección del gesto panorámico del usuario:

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

1
podría ser la mejor solución, pero con un inicio realmente lento, dx será igual a 0.
trickster77777

Claro que si. Esto debería ser votado más, ya que es más una forma "nativa" o adecuada, principalmente porque es necesario usar cualquier valor almacenado en la propiedad.
Michi

¿No tiene el mismo problema que stackoverflow.com/a/26192103/62, donde no funcionará correctamente una vez que el desplazamiento de impulso se active después de que el usuario deje de desplazarse?
Liron Yahdav

60

Usar scrollViewDidScroll:es una buena manera de encontrar la dirección actual.

Si desea conocer la dirección después de que el usuario haya terminado de desplazarse, use lo siguiente:

@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
Esta es una gran adición Justin, sin embargo, me gustaría agregar una edición. Si su vista de desplazamiento tiene habilitada la paginación, y realiza un arrastre que termina volviendo a su posición inicial, la condición actual de "otra cosa" considerará que se "movió a la izquierda", aunque debería ser "sin cambios".
Scott Lieberman

1
@ScottLieberman a la derecha, he actualizado el código en consecuencia.
Justin Tanner

50

No es necesario agregar una variable adicional para realizar un seguimiento de esto. Sólo tiene que utilizar la UIScrollView's panGestureRecognizerpropiedad como esta. Desafortunadamente, esto funciona solo si la velocidad no es 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");
}

Puede usar una combinación de componentes x e y para detectar arriba, abajo, izquierda y derecha.


9
desafortunadamente esto solo funciona mientras se arrastra. la velocidad es 0 cuando se eliminan los toques, incluso mientras se desplaza.
heiko

Niza +1. de lo contrario, parte Velocidad 0 porque el usuario ya no está arrastrando, por lo que la velocidad del gesto es 0
Pavan

Puede almacenar la dirección en variable. ¡Cambie solo cuando la velocidad! = 0. Para mí funciona
Leszek Zarna

Funciona con scrollViewWillBeginDragging, impresionante
demensdeum

40

La solución

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

1
Pruébalo con en velocityInViewlugar de translationInView. Esto resuelve el problema que surge al cambiar de dirección en el mismo movimiento.
enreas

2
Esta solución está funcionando perfectamente. La respuesta más aceptada no funciona algunas veces cuando voy a la siguiente pantalla y vuelvo a realizar operaciones de desplazamiento hacia arriba y hacia abajo.
Anjaneyulu Battula

Lo mejor, gracias!
Booharin

18

Swift 4:

Para el desplazamiento horizontal, simplemente puede hacer:

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

Para el cambio de desplazamiento vertical .xcon.y


14

En iOS8 Swift utilicé este método:

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)

}

Tengo una vista dinámica que cambia las ubicaciones a medida que el usuario se desplaza para que la vista parezca que se quedó en el mismo lugar en la pantalla. También estoy rastreando cuando el usuario sube o baja.

Aquí también hay una forma alternativa:

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
¿Usar x en lugar de y?
Esqarrouth

En realidad, quiero detectarlo para UiCollectionView. Tengo desplazamiento horizontal. Entonces voy a detectar en la crollViewDidEndDeceleratingderecha?
Umair Afzal

8

Esto es lo que funcionó para mí (en 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
No se llama a este método cuando el valor de la propiedad pagingEnabled de la vista de desplazamiento es YES.
Ako

5

Alternativamente, es posible observar la ruta clave "contentOffset". Esto es útil cuando no es posible establecer / cambiar el delegado de la vista de desplazamiento.

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

Después de agregar el observador, ahora podría:

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

Recuerde quitar el observador cuando sea necesario. por ejemplo, puede agregar el observador en viewWillAppear y eliminarlo en viewWillDisappear


5

En rápido:

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

También puedes hacerlo en scrollViewDidScroll.


4

Aquí está mi solución para el comportamiento como en la respuesta @followben, pero sin pérdida con inicio lento (cuando dy es 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

Revisé algunas de las respuestas y desarrollé la respuesta AnswerBot envolviendo todo en una sola gota en la categoría UIScrollView. El "lastContentOffset" se guarda dentro de uiscrollview en su lugar y luego es solo cuestión de llamar:

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

Código fuente en https://github.com/tehjord/UIScrollViewScrollingDirection


1

Prefiero hacer un filtrado, basado en la respuesta de @ memmons

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

Cuando se prueba en - (void)scrollViewDidScroll:(UIScrollView *)scrollView:

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

img

self.lastContentOffset cambia muy rápido, la brecha de valor es casi 0.5f.

No es necesario.

Y ocasionalmente, cuando se maneja en condiciones precisas, su orientación puede perderse. (las declaraciones de implementación se omiten a veces)

como :

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

En 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

Cuando la paginación está activada, puede usar este código.

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

Códigos se explica, supongo. CGFloat diferencia1 y diferencia2 declaradas en la misma interfaz privada de clase. Bueno si contentSize se mantiene igual.

- (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, para mí esta implementación funciona realmente 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];

    }
}

Entonces, esto activará textView para descartar después de que finalice el arrastre


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

Use este método delegado de scrollview.

Si la coordenada de velocidad y es + ve, la vista de desplazamiento se desplaza hacia abajo y si es -ve, la vista de desplazamiento se desplaza hacia arriba. De manera similar, el desplazamiento hacia la izquierda y hacia la derecha se puede detectar usando la coordenada x.


0

Corto y fácil sería, solo verifique el valor de la velocidad, si es mayor que cero, entonces se desplaza hacia la izquierda o hacia la derecha:

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

Si trabaja con UIScrollView y UIPageControl, este método también cambiará la vista de página de 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)
}

Gracias el código Swift de @Esq.


0

Swift 2.2 Solución simple que rastrea direcciones simples y múltiples sin ninguna pérdida.

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

En todas las respuestas superiores, dos formas principales de resolver el problema es usar panGestureRecognizero contentOffset. Ambos métodos tienen sus desventajas y ventajas.

Método 1: panGestureRecognizer

Cuando usa panGestureRecognizercomo lo que sugirió @followben, si no desea desplazar programáticamente su vista de desplazamiento, funciona correctamente.

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

Contras

Pero si mueve la vista de desplazamiento con el siguiente código, el código superior no puede reconocerlo:

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

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

Contras

Si desea cambiar contentOffset mediante programación, durante el desplazamiento (como cuando desea crear un desplazamiento infinito), este método genera problemas, porque puede cambiar contentOffsetdurante el cambio de las vistas de contenido y, en este momento, el salto de código superior se desplaza hacia la derecha o izquierda.


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

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

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.