Поиск направления прокрутки в UIScrollView?

У меня есть 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");

    }

}

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

Ответ 1

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

Чтобы определить направление, вам нужно использовать делегат 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,
};

Ответ 2

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

В этом случае на 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
    }
}

Ответ 3

Использование 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
    }
}

Ответ 4

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

Ответ 5

Решение

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

Ответ 6

Свифт 4:

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

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

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

Ответ 7

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

Ответ 8

Вот что у меня получилось (в Objective-C):

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

        NSString *direction = ([scrollView.panGestureRecognizer translationInView:scrollView.superview].y >0)[email protected]"up":@"down";
        NSLog(@"%@",direction);
    }

Ответ 9

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

Ответ 10

В качестве альтернативы можно наблюдать ключевой путь "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

Ответ 11

В быстрой:

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

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

Ответ 12

Вот мое решение для поведения, как в ответе @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
                }
            }
        }
    }
}

Ответ 13

Я проверил часть ответа и разработал ответ 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

Ответ 14

Я предпочитаю выполнять фильтрацию, основываясь на ответе @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
    }
}

Ответ 15

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

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

Ответ 16

Коды объясняют, я думаю. CGFloat difference1 и difference2 объявлены в одном и том же классе. Хорошо, если 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;

       }

Ответ 17

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

@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 уволит после завершения перетаскивания.

Ответ 18

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

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

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

Ответ 19

Короткое и простое было бы, просто проверьте значение скорости, если оно больше нуля, а затем его прокрутка влево вправо:

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

Ответ 20

Если вы работаете с 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 Swift.

Ответ 21

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
  }

Ответ 22

Во всех верхних ответах есть два основных способа решения проблемы - использование 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 во время смены мест просмотра контента, и в это время верхний код будет прокручиваться при прокрутке. вправо или влево.