在UIScrollView中如何确定滚动的方向?

198

我有一个只允许水平滚动的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");

    }

}

但是当我移动时,这种方法有时候根本不会被调用。你觉得怎么办?


请使用滚动视图委托来实现此功能。 - memmons
最佳答案在此处:https://dev59.com/yGXWa4cB1Zd3GeqPOI4r - iksnae
24个回答

400

确定手势的方向相对简单,但需要注意的是方向可能会在手势过程中多次改变。例如,如果你有一个开启分页且用户往下滑动以到达下一页的滚动视图,则最初的方向可能是向右的,但如果你开启了反弹效果,那么它将短暂地没有方向,然后短暂地向左。

要确定方向,您需要使用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? - Dev
1
@Dev 这段代码是 lastContentOffset = scrollView.contentOffset.x; - memmons
@akivag29 对于 lastContentOffset 类型的发现很好。关于属性与静态变量:两种解决方案都是不错的选择。我没有真正的偏好。这是一个很好的观点。 - memmons
只需稍微调整一下: - (void)scrollViewDidScroll:(UIScrollView *)sender 应该是- (void)scrollViewDidScroll:(UIScrollView *)scrollViewscrollView - Hugues BR
2
为了获得正确的滚动方向,您需要将其放置在scrollViewWillBeginDragging:方法中,以调用lastContentOffset setter。 - strawnut
显示剩余3条评论

80

...我想知道用户滚动的方向(左、右)。

在这种情况下,对于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
    }
}

2
可能是最佳解决方案,但起步非常缓慢,dx 将等于 0。 - trickster77777
当然可以。这应该得到更多的赞,因为这是更“本地化”或更合适的方式,主要是因为没有必要使用任何存储值的属性。 - Michi
这个问题和 https://dev59.com/mHE85IYBdhLWcg3w64IA#26192103 一样,当用户停止滚动后惯性滚动开始时,它将无法正确工作,不是吗? - Liron Yahdav

64

使用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 if last content offset is greater then current offset
    } else if (self.lastContentOffset < scrollView.contentOffset.x) {
        // moved left if last content offset is less that current offset
    } else {
        // didn't move
    }
}

3
贾斯汀,这是一个很好的补充,但我想增加一处修改。如果你的滚动视图启用了分页功能,并且你进行了一次拖动,最终回到了初始位置,当前的“else”条件将认为它是“向左移动”,尽管它应该是“没有变化”。 - Scott Lieberman
1
@ScottLieberman,你是对的,我已经相应地更新了代码。 - Justin Tanner

56
不需要添加额外的变量来跟踪此操作。只需使用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的组合来检测向上、向下、向左和向右的方向。

11
很遗憾,这仅在拖动时起作用。 当触摸被移除时,速度为0,即使仍在滚动。 - heiko
很好 +1。否则部分速度为0,因为用户不再拖动,所以手势速度为0。 - Pavan
你可以将方向存储在变量中。只有当速度不等于0时才进行更改。对我来说有效。 - Leszek Zarna
适用于scrollViewWillBeginDragging,太棒了。 - demensdeum

48

解决方案

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

3
试试使用velocityInView而不是translationInView。这可以解决在同一次移动中改变方向时出现的问题。 - enreas
2
这个解决方案工作得非常完美。最被接受的答案有时在我切换到下一个屏幕并返回后,执行滚动操作时不起作用。 - Anjaneyulu Battula
最好的,谢谢! - Booharin

25

Swift 4:

对于水平滚动,您可以简单地执行以下操作:

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

对于垂直滚动,请将.x替换为.y


15

在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 中检测它。我有水平滚动。所以我会在 scrollViewDidEndDecelerating 中检测,对吧? - Umair Afzal

9
这是我在Objective-C中使用的方法:
    - (void)scrollViewDidScroll:(UIScrollView *)scrollView{

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

8

在Swift中:

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

你也可以在scrollViewDidScroll中实现它。

8
- (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时,此方法不会被调用。 - Ako

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接