简体   繁体   English

在 UIScrollView 中查找滚动方向?

[英]Finding the direction of scrolling in a UIScrollView?

I have a UIScrollView with only horizontal scrolling allowed, and I would like to know which direction (left, right) the user scrolls.我有一个只允许水平滚动的UIScrollView ,我想知道用户滚动的方向(左,右)。 What I did was to subclass the UIScrollView and override the touchesMoved method:我所做的是touchesMoved 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");

    }

}

But this method sometimes doesn't get called at all when I move.但是当我移动时,有时根本不会调用此方法。 What do you think?你怎么认为?

Determining the direction is fairly straightforward, but keep in mind that the direction can change several times over the course of a gesture.确定方向相当简单,但请记住,在一个手势的过程中,方向可能会发生多次变化。 For example, if you have a scroll view with paging turned on and the user swipes to go to the next page, the initial direction could be rightward, but if you have bounce turned on, it will briefly be going in no direction at all and then briefly be going leftward.例如,如果您有一个滚动视图并打开了分页并且用户滑动以转到下一页,则初始方向可能是向右,但是如果您打开了弹跳,它将暂时没有方向并且然后短暂地向左走。

To determine the direction, you'll need to use the UIScrollView scrollViewDidScroll delegate.要确定方向,您需要使用UIScrollView scrollViewDidScroll委托。 In this sample, I created a variable named lastContentOffset which I use to compare the current content offset with the previous one.在此示例中,我创建了一个名为lastContentOffset的变量,用于将当前内容偏移量与前一个内容偏移量进行比较。 If it's greater, then the scrollView is scrolling right.如果它更大,则 scrollView 向右滚动。 If it's less then the scrollView is scrolling left:如果小于则 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.    
}

I'm using the following enum to define direction.我正在使用以下枚举来定义方向。 Setting the first value to ScrollDirectionNone has the added benefit of making that direction the default when initializing variables:将第一个值设置为 ScrollDirectionNone 具有在初始化变量时将该方向设为默认值的额外好处:

typedef NS_ENUM(NSInteger, ScrollDirection) {
    ScrollDirectionNone,
    ScrollDirectionRight,
    ScrollDirectionLeft,
    ScrollDirectionUp,
    ScrollDirectionDown,
    ScrollDirectionCrazy,
};

...I would like to know which direction (left, right) the user scrolls. ...我想知道用户滚动的方向(左,右)。

In that case, on iOS 5 and above, use the UIScrollViewDelegate to determine the direction of the user's pan gesture:在这种情况下,在 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
    }
}

Using scrollViewDidScroll: is a good way to find the current direction.使用scrollViewDidScroll:是查找当前方向的好方法。

If you want to know the direction after the user has finished scrolling, use the following:如果您想知道用户完成滚动的方向,请使用以下命令:

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

No need to add an extra variable to keep track of this.无需添加额外的变量来跟踪这一点。 Just use the UIScrollView 's panGestureRecognizer property like this.只需像这样使用UIScrollViewpanGestureRecognizer属性。 Unfortunately, this works only if the velocity isn't 0:不幸的是,这仅在速度不为 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");
}

You can use a combination of x and y components to detect up, down, left and right.您可以使用 x 和 y 分量的组合来检测向上、向下、向左和向右。

The solution解决方案

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

Swift 4:斯威夫特 4:

For the horizontal scrolling you can simply do :对于水平滚动,您可以简单地执行以下操作:

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

For vertical scrolling change .x with .y对于垂直滚动,将.x更改为.y

In iOS8 Swift I used this method:在 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)

}

I have a dynamic view which changes locations as the user scrolls so the view can seem like it stayed in the same place on the screen.我有一个动态视图,它会随着用户滚动而改变位置,因此视图看起来好像停留在屏幕上的同一位置。 I am also tracking when user is going up or down.我也在跟踪用户何时上升或下降。

Here is also an alternative way:这也是另一种方式:

func scrollViewWillEndDragging(scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) {
    if targetContentOffset.memory.y < scrollView.contentOffset.y {
        println("Going up!")
    } else {
        println("Going down!")
    }
}

This is what it worked for me (in Objective-C):这对我有用(在Objective-C中):

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

        NSString *direction = ([scrollView.panGestureRecognizer translationInView:scrollView.superview].y >0)?@"up":@"down";
        NSLog(@"%@",direction);
    }
- (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");
    }
}

In swift:迅速:

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

You can do it also in scrollViewDidScroll.您也可以在 scrollViewDidScroll 中执行此操作。

Alternatively, it is possible to observe key path "contentOffset".或者,可以观察关键路径“contentOffset”。 This is useful when it's not possible for you to set/change the delegate of the scroll view.当您无法设置/更改滚动视图的委托时,这很有用。

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

After adding the observer, you could now:添加观察者后,您现在可以:

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

Do remember to remove the observer when needed.请记住在需要时移除观察者。 eg you could add the observer in viewWillAppear and remove it in viewWillDisappear例如,你可以在viewWillAppear中添加的观察者和viewWillDisappear删除

Here is my solution for behavior like in @followben answer, but without loss with slow start (when dy is 0)这是我对@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
                }
            }
        }
    }
}
//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")
                }
            }

Answer from Mos6y https://medium.com/@Mos6yCanSwift/swift-ios-determine-scroll-direction-d48a2327a004 Mos6y 的回答https://medium.com/@Mos6yCanSwift/swift-ios-determine-scroll-direction-d48a2327a004

I checked some of the answer and elaborated on AnswerBot answer by wrapping everything in a drop in UIScrollView category.我检查了一些答案,并通过将所有内容包装在 UIScrollView 类别中来详细说明 AnswerBot 答案。 The "lastContentOffset" is saved inside the uiscrollview instead and then its just a matter of calling : “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
  }
}

Source code at https://github.com/tehjord/UIScrollViewScrollingDirection源代码在https://github.com/tehjord/UIScrollViewScrollingDirection

I prefer to do some filtering, based on @memmons's answer我更喜欢根据@memmons 的回答进行一些过滤

In Objective-C:在 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
    } 
}

When tested in - (void)scrollViewDidScroll:(UIScrollView *)scrollView :- (void)scrollViewDidScroll:(UIScrollView *)scrollView

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

图片

self.lastContentOffset changes very fast, the value gap is nearly 0.5f. self.lastContentOffset变化非常快,值差距接近 0.5f。

It is not necessary.这不是必要的。

And occasionally, when handled in accurate condition, your orientation maybe get lost.有时,在正确的条件下处理时,您的方向可能会丢失。 (implementation statements skipped sometimes) (有时会跳过实现语句)

such as :如 :

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

In Swift 4:在 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
    }
}

Swift 5斯威夫特 5

More clean solution with enum for vertical scrolling.使用enum进行垂直滚动的更干净的解决方案。

enum ScrollDirection {
    case up, down
}

var scrollDirection: ScrollDirection? {
    let yTranslation = scrollView.panGestureRecognizer.translation(in: scrollView.superview).y

    if yTranslation > 0 {
        return .up
    } else if yTranslation < 0 {
        return .down
    } else {
        return nil
    }
}

Usage用法

switch scrollDirection {
case .up:   print("up")
case .down: print("down")
default:    print("no scroll")
}

When paging is turned on,you could use these code.当分页打开时,您可以使用这些代码。

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

Codes explains itself I guess.我猜代码解释了自己。 CGFloat difference1 and difference2 declared in same class private interface. CGFloat 的差异 1 和差异 2 在同一个类私有接口中声明。 Good if contentSize stays same.如果 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;

       }

Ok so for me this implementation is working really good:好的,对我来说,这个实现非常好:

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

    }
}

So this will fire textView to dismiss after drag ends所以这将在拖动结束后触发 textView 关闭

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

Use this delegate method of scrollview.使用滚动视图的这个委托方法。

If y co-ordinate of velocity is +ve scroll view scrolls downwards and if it is -ve scrollview scrolls upwards.如果速度的 y 坐标是 +ve 滚动视图向下滚动,如果是 -ve 滚动视图向上滚动。 Similarly left and right scroll can be detected using x co-ordinate.类似地,可以使用 x 坐标检测左右滚动。

Short & Easy would be, just check the velocity value, if its greater than zero then its scrolling left else right: 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
    }
}

If you work with UIScrollView and UIPageControl, this method will also change the PageControl's page view.如果你使用 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)
}

Thanks @Esq 's Swift code.感谢 @Esq 的 Swift 代码。

Swift 2.2 Simple solution which tracks single and multiple directions without any loss. 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
  }

In all upper answers two major ways to solve the problem is using panGestureRecognizer or contentOffset .在所有上面的答案中,解决问题的两种主要方法是使用panGestureRecognizercontentOffset Both methods have their cons and pros.这两种方法各有优缺点。

Method 1: panGestureRecognizer方法一:panGestureRecognizer

When you use panGestureRecognizer like what @followben suggested, if you don't want to programmatically scroll your scroll view, it works properly.当您像@followben 建议的那样使用panGestureRecognizer ,如果您不想以编程方式滚动滚动视图,它可以正常工作。

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

Cons缺点

But if you move scroll view with following code, upper code can not recognize it:但是,如果您使用以下代码移动滚动视图,则上层代码无法识别它:

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

Method 2: contentOffset方法二: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缺点

If you want to change contentOffset programmatically, during scroll (like when you want to create infinite scroll), this method makes problem, because you might change contentOffset during changing content views places and in this time, upper code jump in that you scroll to right or left.如果您想以编程方式更改 contentOffset,在滚动期间(例如您想要创建无限滚动时),此方法会产生问题,因为您可能会在更改内容视图位置期间更改contentOffset ,此时,上层代码跳转到您向右滚动或离开。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM