Находить направление прокрутки в UIScrollView?

183

У меня есть UIScrollViewтолько горизонтальная прокрутка, и я хотел бы знать, в каком направлении (влево, вправо) пользователь прокручивает. Что я сделал, так это UIScrollViewпереопределил и переопределил touchesMovedметод:

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

    }

}

Но этот метод иногда вообще не вызывается, когда я двигаюсь. Что вы думаете?

Alex1987
источник
Смотрите мой ответ ниже - вы должны использовать делегатов с прокруткой для этого.
меммоны
лучший ответ здесь: stackoverflow.com/questions/11262583/…
iksnae

Ответы:

392

Определить направление довольно просто, но имейте в виду, что направление может изменяться несколько раз в течение жеста. Например, если у вас есть представление прокрутки с включенной подкачкой и пользователь проводит пальцем, чтобы перейти на следующую страницу, начальное направление может быть вправо, но если у вас включен отскок, он на короткое время вообще не будет двигаться и затем кратко идти влево.

Чтобы определить направление, вам нужно будет использовать UIScrollView scrollViewDidScrollделегата. В этом примере я создал переменную с именемlastContentOffset которую я использую для сравнения текущего смещения содержимого с предыдущим. Если оно больше, то scrollView прокручивается вправо. Если это меньше, тогда scrollView прокручивает влево:

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

Я использую следующее перечисление, чтобы определить направление. Установка первого значения в ScrollDirectionNone имеет дополнительное преимущество, заключающееся в том, что это направление по умолчанию используется при инициализации переменных:

typedef NS_ENUM(NSInteger, ScrollDirection) {
    ScrollDirectionNone,
    ScrollDirectionRight,
    ScrollDirectionLeft,
    ScrollDirectionUp,
    ScrollDirectionDown,
    ScrollDirectionCrazy,
};
memmons
источник
1
Как я могу получить lastContentOffset
Dev
@JasonZhao Хех - я получал некоторые странные результаты, когда я тестировал код изначально, потому что я не учел, что отскок прокрутки вызывает направление прокрутки к ... или ... отказов. Итак, я добавил это к перечислению, когда обнаружил неожиданные результаты.
меммон
1
@Dev Это в кодеlastContentOffset = scrollView.contentOffset.x;
меммон
@ akivag29 Хороший улов на lastContentOffsetтип. Относительно свойства против статических переменных: любое решение является хорошим выбором. У меня нет никаких реальных предпочтений. Это хороший момент.
мемоны
2
@JosueEspinosa Чтобы получить правильное направление прокрутки, вы помещаете его в scrollViewWillBeginDragging: метод для последнего вызова метода setContentOffset.
клубника
73

... Я хотел бы знать, в каком направлении (влево, вправо) пользователь прокручивает.

В этом случае на iOS 5 и выше используйте, UIScrollViewDelegateчтобы определить направление жеста панорамирования пользователя:

- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView
{ 
    if ([scrollView.panGestureRecognizer translationInView:scrollView.superview].x > 0) {
        // handle dragging to the right
    } else {
        // handle dragging to the left
    }
}
followben
источник
1
может быть лучшим решением, но при очень медленном старте dx будет равен 0.
trickster77777
Конечно, это так. Это должно быть одобрено гораздо больше, поскольку это скорее «родной» или правильный способ, главным образом потому, что необходимо использовать любые значения, хранящиеся в свойствах.
Мичи
Разве это не та же проблема, что и stackoverflow.com/a/26192103/62, где он не будет работать правильно, как только начнется прокрутка импульса после прекращения панорамирования пользователем?
Лирон Яхдав
60

Использование scrollViewDidScroll:- хороший способ найти текущее направление.

Если вы хотите узнать направление после того, как пользователь закончил прокрутку, используйте следующее:

@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
Это отличное дополнение, Джастин, однако я бы хотел добавить одну правку. Если в вашем просмотре прокрутки включена функция подкачки страниц, и вы выполняете перетаскивание, которое в конечном итоге возвращает его к исходному положению, текущее условие «else» будет считать «перемещенным влево», даже если оно должно быть «без изменений».
Скотт Либерман
1
@ ScottLieberman ваше право, я обновил код соответственно.
Джастин Таннер
50

Нет необходимости добавлять дополнительную переменную, чтобы отслеживать это. Просто используйте свойство UIScrollViews panGestureRecognizerвот так. К сожалению, это работает, только если скорость не равна 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");
}

Вы можете использовать комбинацию компонентов x и y для обнаружения вверх, вниз, влево и вправо.

rounak
источник
9
к сожалению, это просто работает при перетаскивании. скорость равна 0, когда касания удаляются, даже при прокрутке.
Хейко
Ницца +1. else part Velocity 0, потому что пользователь больше не перетаскивает, поэтому скорость жеста равна 0
Pavan
Вы можете сохранить направление в переменной. Изменяйте только тогда, когда скорость! = 0. Для меня работает
Лешек Зарна
Работает с scrollViewWillBeginDragging, потрясающе
demensdeum
40

Решение

func scrollViewDidScroll(scrollView: UIScrollView) {
     if(scrollView.panGestureRecognizer.translationInView(scrollView.superview).y > 0)
     {
         print("up")
     }
    else
    {
         print("down")
    } 
}
davidrelgr
источник
1
Попробуйте velocityInViewвместо translationInView. Это решает проблему, возникающую при смене направления при одном и том же движении.
enreas
2
Это решение работает идеально. Наиболее приемлемый ответ не работает несколько раз, когда я перехожу на следующий экран и возвращаюсь, выполняя операции прокрутки вверх и вниз.
Анджанеюлу Баттула
Лучший, спасибо!
Бухарин
18

Свифт 4:

Для горизонтальной прокрутки вы можете просто сделать:

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

Для вертикальной прокрутки изменить .xс.y

Алессандро Орнано
источник
14

В iOS8 Swift я использовал этот метод:

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)

}

У меня есть динамическое представление, которое меняет местоположения при прокрутке пользователя, поэтому представление может показаться, что оно осталось на том же месте на экране. Я также отслеживаю, когда пользователь идет вверх или вниз.

Вот также альтернативный способ:

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
источник
1
Использовать х вместо у?
Esqarrouth
На самом деле я хочу обнаружить его для UiCollectionView. У меня горизонтальная прокрутка. Так что я обнаружу в crollViewDidEndDeceleratingправе?
Умайр Афзал
8

Вот что у меня получилось (в 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
Этот метод не вызывается, когда значением свойства pagingEnabled представления прокрутки является YES.
Ако
5

В качестве альтернативы можно наблюдать ключевой путь «contentOffset». Это полезно, когда вы не можете установить / изменить делегата представления прокрутки.

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

После добавления наблюдателя вы можете теперь:

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

Не забудьте удалить наблюдателя при необходимости. Например, вы можете добавить наблюдателя в viewWillAppear и удалить его в viewWillDisappear

Сюй Хуанце
источник
5

В скором времени:

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

Вы можете сделать это также в scrollViewDidScroll.

Хавьер Калатрава Ллаверия
источник
4

Вот мое решение для поведения, как в ответе @followben, но без потерь при медленном старте (когда 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

Я проверил некоторые из ответов и подробно остановился на ответе AnswerBot, обернув все в одну каплю в категории UIScrollView. Вместо этого lastContentOffset сохраняется внутри uiscrollview, а затем просто вызывается:

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

Исходный код на https://github.com/tehjord/UIScrollViewScrollingDirection

Бжергсен
источник
1

Я предпочитаю делать фильтрацию, основываясь на ответе @ memmons

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

При тестировании в - (void)scrollViewDidScroll:(UIScrollView *)scrollView:

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

IMG

self.lastContentOffset меняется очень быстро, разрыв в значениях составляет почти 0.5f.

Это не обязательно.

И иногда, когда обращаются в точном состоянии, ваша ориентация может потеряться. (операторы реализации иногда пропускаются)

Такие как :

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

В 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

Когда подкачка включена, вы можете использовать этот код.

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

Коды объясняются, я думаю. CGFloat разница1 и разница2 объявлены в частном интерфейсе того же класса. Хорошо, если contentSize остается прежним.

- (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
источник
0

Итак, для меня эта реализация работает очень хорошо:

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

    }
}

Так что это будет запускать textView, чтобы закрыть после окончания перетаскивания.

user2021505
источник
0
- (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset {
NSLog(@"px %f py %f",velocity.x,velocity.y);}

Используйте этот метод делегата scrollview.

Если y-координата скорости + ve, просмотр прокрутки прокручивается вниз, а если -ve - прокрутка прокручивается вверх. Аналогично, левая и правая прокрутка могут быть обнаружены с использованием координаты х.

Нилеш Тупе
источник
0

Short & Easy будет, просто проверьте значение скорости, если оно больше нуля, то прокрутка влево или вправо:

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
источник
0

Если вы работаете с UIScrollView и UIPageControl, этот метод также изменит представление страницы 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)
}

Спасибо @Esq's Swift код.

charles.cc.hsu
источник
0

Swift 2.2 Простое решение, которое отслеживает одно и несколько направлений без потерь.

  // 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
источник
0

Во всех верхних ответах используются два основных способа решения проблемы: panGestureRecognizerили contentOffset. Оба метода имеют свои плюсы и минусы.

Способ 1: panGestureRecognizer

Когда вы используете panGestureRecognizerто, что предлагает @followben, если вы не хотите программно прокручивать представление прокрутки, оно работает правильно.

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

Cons

Но если вы переместите представление прокрутки со следующим кодом, верхний код не сможет его распознать:

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

Способ 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

Если вы хотите изменить contentOffset программно, во время прокрутки (например, когда вы хотите создать бесконечную прокрутку), этот метод создает проблему, потому что вы можете изменить contentOffsetво время смены мест просмотра контента и в это время верхний код переместится при прокрутке вправо или осталось.

Esmaeil
источник
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")
                }
            }

Ответ от Mos6y https://medium.com/@Mos6yCanSwift/swift-ios-determine-scroll-direction-d48a2327a004

slimas
источник