Encontrando a direção da rolagem em um UIScrollView?

183

Eu tenho UIScrollViewapenas uma rolagem horizontal permitida e gostaria de saber em qual direção (esquerda, direita) o usuário rola. O que eu fiz foi subclassificar oUIScrollView e substituir o 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");

    }

}

Mas esse método às vezes não é chamado quando mudo. O que você acha?

Alex1987
fonte
Veja minha resposta abaixo - você deve usar os delegados da exibição de rolagem para fazer isso.
memmons
melhor resposta aqui: stackoverflow.com/questions/11262583/…
iksnae

Respostas:

392

Determinar a direção é bastante simples, mas lembre-se de que a direção pode mudar várias vezes ao longo de um gesto. Por exemplo, se você tiver uma visualização de rolagem com a paginação ativada e o usuário deslizar para a próxima página, a direção inicial poderá ser para a direita, mas se você tiver o salto ativado, ela não seguirá brevemente em nenhuma direção e depois, brevemente, indo para a esquerda.

Para determinar a direção, você precisará usar o UIScrollView scrollViewDidScrolldelegado. Nesta amostra, criei uma variável denominada lastContentOffsetque eu uso para comparar o deslocamento do conteúdo atual com o anterior. Se for maior, o scrollView está rolando para a direita. Se for menor, o scrollView está rolando para a esquerda:

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

Estou usando a enumeração a seguir para definir a direção. Definir o primeiro valor como ScrollDirectionNone tem o benefício adicional de tornar essa direção o padrão ao inicializar variáveis:

typedef NS_ENUM(NSInteger, ScrollDirection) {
    ScrollDirectionNone,
    ScrollDirectionRight,
    ScrollDirectionLeft,
    ScrollDirectionUp,
    ScrollDirectionDown,
    ScrollDirectionCrazy,
};
memmons
fonte
1
Como posso obter lastContentOffset
Dev
@ JasonZhao Heh - Eu estava obtendo alguns resultados estranhos quando testava o código inicialmente, porque não levei em conta que o ressalto do scrollview faz com que a direção do scroll seja..er..bounce. Então, eu adicionei isso ao enum quando encontrei resultados inesperados.
memmons
1
@Dev Está no códigolastContentOffset = scrollView.contentOffset.x;
memmons
@ akivag29 Boa captura do lastContentOffsettipo. Em relação à propriedade versus variáveis ​​estáticas: qualquer solução é uma boa escolha. Eu não tenho nenhuma preferência real. É um bom argumento.
memmons
2
@JosueEspinosa Para obter a direção de rolagem correta, coloque-a no método scrollViewWillBeginDragging: to lastContentOffset setter call.
strawnut
73

... Gostaria de saber em qual direção (esquerda, direita) o usuário rola.

Nesse caso, no iOS 5 e superior, use o UIScrollViewDelegatepara determinar a direção do gesto de panorâmica do usuário:

- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView
{ 
    if ([scrollView.panGestureRecognizer translationInView:scrollView.superview].x > 0) {
        // handle dragging to the right
    } else {
        // handle dragging to the left
    }
}
followben
fonte
1
poderia ser a melhor solução, mas com início muito lento, dx será igual a 0.
trickster77777
Claro que sim. Isso deve ser votado muito mais, pois é mais um modo "nativo" ou adequado, principalmente porque há necessidade de usar qualquer valor armazenado em propriedade.
Michi
Isso não tem o mesmo problema que o stackoverflow.com/a/26192103/62, em que não funcionará corretamente quando a rolagem do momento entrar em ação após o usuário parar de fazer o panning?
Liron Yahdav
59

Usar scrollViewDidScroll:é uma boa maneira de encontrar a direção atual.

Se você quiser saber a direção após o usuário terminar a rolagem, use o seguinte:

@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
fonte
3
Esta é uma ótima adição ao Justin, no entanto, gostaria de adicionar uma edição. Se sua visualização de rolagem estiver com a paginação ativada e você executar um arrasto que voltará à sua posição inicial, a condição "else" atual considerará que "moveu-se para a esquerda", mesmo que deva ser "sem alteração".
Scott Lieberman
1
@ScottLieberman seu direito, atualizei o código de acordo.
Justin Tanner
50

Não há necessidade de adicionar uma variável extra para acompanhar isso. Basta usar o UIScrollView's panGestureRecognizerpropriedade como esta. Infelizmente, isso funciona apenas se a velocidade não for 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");
}

Você pode usar uma combinação de componentes xey para detectar cima, baixo, esquerda e direita.

rounak
fonte
9
infelizmente isso funciona enquanto arrasta. a velocidade é 0 quando os toques são removidos, mesmo enquanto a rolagem está rolando.
heiko
Nice +1. outra parte Velocity 0 porque, o usuário não está arrastando mais, então a velocidade gesto é 0
Pavan
Você pode armazenar a direção na variável. ! Mudança só quando a velocidade = 0. Para mim funciona
Leszek Zarna
Funciona com scrollViewWillBeginDragging, awesome
demensdeum 4/18
40

A solução

func scrollViewDidScroll(scrollView: UIScrollView) {
     if(scrollView.panGestureRecognizer.translationInView(scrollView.superview).y > 0)
     {
         print("up")
     }
    else
    {
         print("down")
    } 
}
davidrelgr
fonte
1
Experimente com em velocityInViewvez de translationInView. Isso resolve o problema que surge ao mudar de direção no mesmo movimento.
enreas
2
Esta solução está funcionando perfeitamente. A resposta mais aceita não está funcionando algumas vezes quando vou para a próxima tela e volto a executar operações de rolagem para cima e para baixo.
Anjaneyulu Battula
O melhor, thnx!
Booharin
18

Swift 4:

Para a rolagem horizontal, você pode simplesmente fazer:

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

Para rolagem vertical, altere .xcom.y

Alessandro Ornano
fonte
14

No iOS8 Swift, usei 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)

}

Eu tenho uma visualização dinâmica que altera os locais à medida que o usuário rola, para que a visualização pareça estar no mesmo lugar na tela. Também estou rastreando quando o usuário está subindo ou descendo.

Aqui também é uma maneira 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!")
    }
}
Esqarrouth
fonte
1
Use x em vez de y?
Esqarrouth
Na verdade, eu quero detectá-lo para UiCollectionView. Eu tenho rolagem horizontal. Então eu vou detectar crollViewDidEndDeceleratingcerto?
Umair Afzal
8

Isto é o que funcionou para mim (no Objective-C):

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

        NSString *direction = ([scrollView.panGestureRecognizer translationInView:scrollView.superview].y >0)?@"up":@"down";
        NSLog(@"%@",direction);
    }
Javier Calatrava Llavería
fonte
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
fonte
2
Este método não é chamado quando o valor da propriedade pagingEnabled da exibição de rolagem é YES.
Ako
5

Como alternativa, é possível observar o caminho principal "contentOffset". Isso é útil quando não é possível definir / alterar o delegado da exibição de rolagem.

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

Depois de adicionar o observador, você pode agora:

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

Lembre-se de remover o observador quando necessário. por exemplo, você pode adicionar o observador no viewWillAppear e removê-lo no viewWillDisappear

xu huanze
fonte
5

Em rápida:

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

Você também pode fazer isso em scrollViewDidScroll.

Javier Calatrava Llavería
fonte
4

Aqui está minha solução para o comportamento, como na resposta @followben, mas sem perda com início 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
                }
            }
        }
    }
}
Andrey Zhukov
fonte
1

Eu verifiquei algumas das respostas e elaborei na resposta do AnswerBot envolvendo tudo em uma gota na categoria UIScrollView. O "lastContentOffset" é salvo dentro da visualização uiscroll e, em seguida, basta chamar:

- (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-fonte em https://github.com/tehjord/UIScrollViewScrollingDirection

Bjergsen
fonte
1

Eu prefiro fazer alguma filtragem, com base na resposta de @ memmons

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

Quando testado em - (void)scrollViewDidScroll:(UIScrollView *)scrollView:

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

img

self.lastContentOffset muda muito rápido, a diferença de valor é quase 0,5f.

Não é necessário.

E, ocasionalmente, quando manuseada em condições precisas, sua orientação pode se perder. (as instruções de implementação são ignoradas às vezes)

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

No 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
    }
}
Pérola Negra
fonte
0

Quando a paginação está ativada, você pode usar esses códigos.

- (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");
    }
}
Perada
fonte
0

Códigos se explica, eu acho. Diferença CGFloat1 e diferença2 declaradas na mesma interface privada da classe. Bom se contentSize permanecer o mesmo.

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

Ok, então para mim esta implementação está funcionando muito bem:

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

    }
}

Portanto, isso disparará o textView para descartar após o término do arrasto

user2021505
fonte
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.

Se y coordenada da velocidade for + ve rolagem, a visualização rola para baixo e, se for -ve rolagem, rolagem para cima. Da mesma forma, a rolagem esquerda e direita pode ser detectada usando x coordenadas.

Nilesh Tupe
fonte
0

Short & Easy seria, basta verificar o valor da velocidade, se for maior que zero, então a rolagem para a esquerda ou para a direita:

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
fonte
0

Se você trabalha com UIScrollView e UIPageControl, esse método também altera a exibição da página do 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)
}

Obrigado pelo código Swift de @Esq.

charles.cc.hsu
fonte
0

Swift 2.2 Solução simples que rastreia direções únicas e múltiplas sem nenhuma perda.

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

Nas respostas superiores, duas maneiras principais de resolver o problema é usar panGestureRecognizeror contentOffset. Ambos os métodos têm seus contras e prós.

Método 1: panGestureRecognizer

Quando você usa o panGestureRecognizerque o @followben sugeriu, se você não quiser rolar programaticamente sua exibição de rolagem, ela funcionará corretamente.

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

Contras

Mas se você mover a exibição de rolagem com o seguinte código, o código superior não poderá reconhecê-lo:

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

Se você deseja alterar contentOffset programaticamente, durante a rolagem (como quando você deseja criar rolagem infinita), esse método causa problemas, porque você pode mudar contentOffsetdurante a alteração de locais de visualizações de conteúdo e, nesse momento, o salto superior do código é rolado para a direita ou para a direita. esquerda.

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

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

slimas
fonte