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

    }

}

但是当我移动时,有时根本不会调用此方法。你怎么看?


请在下面查看我的回复-您应该使用滚动视图委托来执行此操作。
记忆

Answers:


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

1
我怎样才能得到lastContentOffset
开发

@JasonZhao Heh-最初测试代码时,我得到了一些奇怪的结果,因为我没有考虑到scrollview反弹会导致滚动方向向..er..bounce倾斜。因此,当我发现意外结果时,将其添加到枚举中。
记忆

1
@Dev在代码中lastContentOffset = scrollView.contentOffset.x;
记忆

@ akivag29不错的lastContentOffset类型。关于属性与静态变量:两种解决方案都是不错的选择。我没有真正的偏爱。这是一个好点。
记忆

2
@JosueEspinosa若要获取正确的滚动方向,请将其放在lastViewOffset setter调用的scrollViewWillBeginDragging:方法中。
–strawnut

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

1
可能是最好的解决方案,但与真正的慢启动,DX将等于0
trickster77777

当然可以。应该更多地采用这种方式,因为这更是一种“本机”或适当的方式,主要是因为需要使用任何属性存储的值。
Michi

这与stackoverflow.com/a/26192103/62是否存在相同的问题,在用户停止平移后一旦动量滚动开始,它将无法正常工作?
Liron Yahdav

59

使用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
贾斯汀(Justin)这是一个很棒的补充,但是我想添加一个编辑。如果您的滚动视图启用了分页,并且您执行的拖动操作最终回到其初始位置,则当前的“ else”条件将认为“向左移动”,即使它应为“不变”。
Scott Lieberman

1
@ScottLieberman您的权利,我已相应地更新了代码。
贾斯汀·坦纳

50

无需添加额外的变量来跟踪此情况。只需使用UIScrollViewpanGestureRecognizer属性即可。不幸的是,这仅在速度不为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分量来检测上,下,左和右。


9
不幸的是,这只是在拖动时起作用。移除触摸时,即使仍在滚动时,速度为0。
heiko

不错+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")
    } 
}

1
试试看velocityInView代替translationInView。这解决了在相同运动中改变方向时出现的问题。
增加了

2
该解决方案是完美的。当我转到下一个屏幕并再次执行向上和向下滚动操作时,最可接受的答案有时无法正常工作。
Anjaneyulu Battula'5

最好,谢谢!
Booharin

17

斯威夫特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!")
    }
}

1
使用x代替y?
Esqarrouth

实际上,我想为UiCollectionView检测它。我有水平滚动。所以我会发现crollViewDidEndDecelerating正确的吗?
Umair Afzal

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
当滚动视图的pagesEnabled属性的值为YES时,不会调用此方法。
Ako

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

我检查了一些答案,并通过将所有内容包装在UIScrollView类别中的下拉列表中来详细说明AnswerBot答案。“ 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在同一类私有接口中声明的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;

       }

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在拖动结束后关闭


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,scrollview向上滚动。同样,可以使用x坐标检测左右滚动。


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

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的Swift代码。


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
  }

0

在所有上层答案中,解决问题的两种主要方法是使用panGestureRecognizercontentOffset。两种方法各有利弊。

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

缺点

但是,如果使用以下代码移动滚动视图,则上层代码将无法识别它:

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
}

缺点

如果要在滚动过程中(例如,要创建无限滚动)以编程方式更改contentOffset,则此方法会引起问题,因为contentOffset在更改内容视图位置时可能会发生更改,此时,上方代码会跳转到向右滚动或剩下。


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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.