Tìm hướng di chuyển trong UIScrollView?


183

Tôi chỉ có một UIScrollViewcuộn ngang cho phép và tôi muốn biết người dùng cuộn theo hướng nào (trái, phải). Những gì tôi đã làm là phân lớp UIScrollViewvà ghi đè touchesMovedphương thức:

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

    }

}

Nhưng phương pháp này đôi khi không được gọi chút nào khi tôi di chuyển. Bạn nghĩ sao?


Xem phản hồi của tôi dưới đây - bạn nên sử dụng các đại biểu xem cuộn để làm điều này.
memmons

câu trả lời hay nhất tại đây: stackoverflow.com/questions/11262583/ Cách
iksnae

Câu trả lời:


392

Xác định hướng là khá đơn giản, nhưng hãy nhớ rằng hướng có thể thay đổi nhiều lần trong suốt một cử chỉ. Ví dụ: nếu bạn có chế độ xem cuộn với bật trang và người dùng vuốt sang trang tiếp theo, hướng ban đầu có thể là đúng, nhưng nếu bạn bật lên, nó sẽ không đi theo hướng nào cả và sau đó một thời gian ngắn sẽ đi về phía trái.

Để xác định hướng, bạn sẽ cần sử dụng UIScrollView scrollViewDidScrollđại biểu. Trong mẫu này, tôi đã tạo một biến có tên lastContentOffsetmà tôi sử dụng để so sánh bù nội dung hiện tại với biến trước đó. Nếu nó lớn hơn, thì scrollView đang cuộn sang phải. Nếu ít hơn thì scrollView sẽ cuộn sang trái:

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

Tôi đang sử dụng enum sau để xác định hướng. Đặt giá trị đầu tiên thành ScrollDirectionNone có thêm lợi ích là biến hướng đó thành mặc định khi khởi tạo biến:

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

1
Làm cách nào tôi có thể nhận được LastContent Offerset
Dev

@JasonZhao Heh - Tôi đã nhận được một số kết quả kỳ lạ khi tôi đang kiểm tra mã một cách thân mật vì tôi đã không tính đến việc thoát khỏi chế độ xem cuộn gây ra hướng cuộn tới..er..bounce. Vì vậy, tôi đã thêm nó vào enum khi tôi tìm thấy kết quả bất ngờ.
memmons

1
@Dev Nó nằm trong mãlastContentOffset = scrollView.contentOffset.x;
memmons

@ akivag29 Bắt tốt về lastContentOffsetchủng loại. Về tài sản và các biến tĩnh: một trong hai giải pháp là một lựa chọn tốt. Tôi không có sở thích thực sự. Đó là một điểm tốt.
memmons

2
@JosueEspinosa Để có được hướng cuộn chính xác, bạn đặt nó trong scrollViewWillBeginDragging: phương thức để gọi lệnh setter lastContent Offerset.
rơm

73

... Tôi muốn biết người dùng cuộn theo hướng nào (trái, phải).

Trong trường hợp đó, trên iOS 5 trở lên, hãy sử dụng UIScrollViewDelegateđể xác định hướng của cử chỉ xoay của người dùng:

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

1
có thể là giải pháp tốt nhất, nhưng với khởi đầu thực sự chậm, dx sẽ bằng 0.
trickster77777

Chắc chắn là có. Điều này nên được nâng cao hơn nữa vì đây là cách bản địa hay nhất của chủ yếu bởi vì cần phải sử dụng bất kỳ giá trị được lưu trữ thuộc tính nào.
Michi

Điều này không có vấn đề tương tự như stackoverflow.com/a/26192103/62 khi nó sẽ không hoạt động chính xác một khi cuộn đà bắt đầu chạy sau khi người dùng ngừng quay?
Liron Yahdav

59

Sử dụng scrollViewDidScroll:là một cách tốt để tìm hướng hiện tại.

Nếu bạn muốn biết hướng sau khi người dùng cuộn xong, hãy sử dụng như sau:

@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
Đây là một bổ sung tuyệt vời Justin, tuy nhiên tôi muốn thêm một chỉnh sửa. Nếu cuộn xem của bạn đã kích hoạt phân trang và bạn thực hiện thao tác kéo trở về vị trí ban đầu, điều kiện "khác" hiện tại sẽ xem xét rằng "di chuyển sang trái", mặc dù nó sẽ là "không thay đổi".
Scott Lieberman

1
@ScottLieberman quyền của bạn, tôi đã cập nhật mã phù hợp.
Justin Tanner

50

Không cần thêm một biến phụ để theo dõi điều này. Chỉ cần sử dụng UIScrollView's panGestureRecognizertài sản như thế này. Thật không may, điều này chỉ hoạt động nếu vận tốc không bằng 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");
}

Bạn có thể sử dụng kết hợp các thành phần x và y để phát hiện lên, xuống, trái và phải.


9
Thật không may, điều này chỉ hoạt động trong khi kéo. vận tốc là 0 khi chạm được loại bỏ, ngay cả khi vẫn cuộn.
heiko

Đẹp +1. phần khác Vận tốc 0 vì, người dùng không kéo nữa, vì vậy vận tốc cử chỉ là 0
Pavan

Bạn có thể lưu trữ hướng trong biến. Chỉ thay đổi khi vận tốc! = 0. Đối với tôi hoạt động
Leszek Zarna

Hoạt động với scrollViewWillBeginDragging, tuyệt vời
demensdeum

40

Giải pháp

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

1
Hãy thử velocityInViewthay vì translationInView. Điều này giải quyết vấn đề phát sinh khi thay đổi hướng trong cùng một chuyển động.
enreas

2
Giải pháp này đang hoạt động hoàn hảo. Câu trả lời được chấp nhận nhiều nhất là không hoạt động một số lần khi tôi chuyển sang màn hình tiếp theo và quay lại thực hiện thao tác cuộn lên xuống.
Anjaneyulu Battula

Tốt nhất, thnx!
Booharin

17

Swift 4:

Đối với cuộn ngang bạn chỉ cần làm:

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

Để thay đổi cuộn dọc .xvới.y


14

Trong iOS8 Swift tôi đã sử dụng phương pháp này:

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)

}

Tôi có chế độ xem động thay đổi vị trí khi người dùng cuộn để chế độ xem có thể giống như ở cùng một vị trí trên màn hình. Tôi cũng đang theo dõi khi người dùng đang lên hoặc xuống.

Đây cũng là một cách khác:

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
Sử dụng x thay vì y?
Esqarrouth

Thật ra tôi muốn phát hiện nó cho UiCollectionView. Tôi có cuộn ngang. Vì vậy, tôi sẽ phát hiện trong crollViewDidEndDeceleratingphải không?
Umair Afzal

8

Đây là những gì nó hoạt động với tôi (trong Mục tiêu-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
Phương thức này không được gọi khi giá trị thuộc tính pagingEnables của chế độ xem cuộn là CÓ.
Ako

5

Ngoài ra, có thể quan sát đường dẫn chính "contentPackset". Điều này hữu ích khi bạn không thể đặt / thay đổi đại biểu của chế độ xem cuộn.

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

Sau khi thêm người quan sát, bây giờ bạn có thể:

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

Hãy nhớ loại bỏ người quan sát khi cần thiết. ví dụ: bạn có thể thêm trình quan sát trong viewWillAppear và xóa nó trong viewWillDisappear


5

Trong nhanh chóng:

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

Bạn cũng có thể làm điều đó trong scrollViewDidScroll.


4

Đây là giải pháp của tôi cho hành vi như trong câu trả lời @followben, nhưng không mất đi khi bắt đầu chậm (khi dy bằng 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

Tôi đã kiểm tra một số câu trả lời và giải thích chi tiết về câu trả lời của answerBot bằng cách gói mọi thứ trong danh mục UIScrollView. Thay vào đó, "lastContent Offerset" được lưu bên trong uiscrollview và sau đó chỉ là vấn đề gọi:

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

Mã nguồn tại https://github.com/tehjord/UIScrollViewScrollingDirection


1

Tôi thích thực hiện một số bộ lọc, dựa trên câu trả lời của @ memmons

Trong Mục tiêu-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
    } 
}

Khi được thử nghiệm trong - (void)scrollViewDidScroll:(UIScrollView *)scrollView:

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

img

self.lastContentOffset thay đổi rất nhanh, khoảng cách giá trị gần 0,5f.

Nó không phải là cần thiết.

Và đôi khi, khi được xử lý trong điều kiện chính xác, định hướng của bạn có thể bị mất. (đôi khi thực hiện bỏ qua)

nhu la :

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

Trong 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

Khi phân trang được bật, bạn có thể sử dụng các mã này.

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

Mã giải thích chính tôi đoán. CGFloat Dif1 và Dif2 được khai báo trong cùng một giao diện riêng. Tốt nếu contentSize giữ nguyên.

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

Ok vì vậy đối với tôi việc thực hiện này đang hoạt động rất tốt:

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

    }
}

Vì vậy, điều này sẽ kích hoạt textView để loại bỏ sau khi kết thúc kéo


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

Sử dụng phương pháp đại biểu này của scrollview.

Nếu y phối hợp của vận tốc là + ve xem cuộn sẽ cuộn xuống dưới và nếu đó là cuộn -view cuộn cuộn lên trên. Tương tự cuộn trái và phải có thể được phát hiện bằng cách sử dụng x phối hợp.


0

Short & Easy sẽ là, chỉ cần kiểm tra giá trị vận tốc, nếu nó lớn hơn 0 thì cuộn sang trái phải:

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

Nếu bạn làm việc với UIScrollView và UIPageControl, phương pháp này cũng sẽ thay đổi chế độ xem trang của 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)
}

Cảm ơn mã Swift của @Esq.


0

Swift 2.2 Giải pháp đơn giản theo dõi một hướng và nhiều hướng mà không bị mất.

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

Trong tất cả các câu trả lời trên, hai cách chính để giải quyết vấn đề là sử dụng panGestureRecognizerhoặc contentOffset. Cả hai phương pháp đều có nhược điểm và ưu điểm.

Phương pháp 1: panGestureRecognizer

Khi bạn sử dụng panGestureRecognizernhư những gì @followben đề xuất, nếu bạn không muốn lập trình cuộn chế độ xem cuộn của mình, nó sẽ hoạt động bình thường.

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

Nhược điểm

Nhưng nếu bạn di chuyển chế độ xem cuộn với mã sau, mã trên không thể nhận ra nó:

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

Phương pháp 2: contentPackset

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
}

Nhược điểm

Nếu bạn muốn thay đổi nội dung Chương trình cài đặt theo chương trình, trong khi cuộn (như khi bạn muốn tạo cuộn vô hạn), phương pháp này gây ra sự cố, bởi vì bạn có thể thay đổi contentOffsettrong khi thay đổi vị trí xem nội dung và trong thời gian này, mã phía trên nhảy vào mà bạn cuộn sang phải hoặc trái.


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

Trả lời từ Mos6y https://medium.com/@Mos6yCanSwift/swift-ios-determine-scroll-direction-d48a2327a004

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.