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

    }

}

그러나이 방법은 때로는 움직일 때 전혀 호출되지 않습니다. 어떻게 생각해?


아래의 응답을 참조하십시오-스크롤보기 대리자를 사용 하여이 작업을 수행해야합니다.
memmons

여기에 가장 좋은 답변 : stackoverflow.com/questions/11262583/…
iksnae

답변:


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을 얻는 방법
Dev

@JasonZhao Heh-스크롤 뷰 바운스가 스크롤 방향을 ..er..bounce로 만드는 것을 고려하지 않았기 때문에 코드를 초기에 테스트 할 때 이상한 결과를 얻었습니다. 그래서 예기치 않은 결과를 발견했을 때 열거 형에 추가했습니다.
memmons

1
@Dev 코드에 있습니다lastContentOffset = scrollView.contentOffset.x;
memmons

@ akivag29 lastContentOffset유형 에 잘 맞습니다 . 속성 대 정적 변수와 관련하여 두 솔루션 중 하나를 선택하는 것이 좋습니다. 나는 진정한 선호가 없습니다. 좋은 지적입니다.
memmons

2
@JosueEspinosa 올바른 스크롤 방향을 얻으려면 scrollViewWillBeginDragging : 메서드를 lastContentOffset setter 호출에 넣습니다.
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 4

물론입니다. 이는 속성 저장 값을 사용해야하기 때문에 주로 "네이티브"또는 적절한 방법이므로 더 많이 상향 조정해야합니다.
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 Velocity 0은 사용자가 더 이상 끌지 않기 때문에 동작 속도는 0입니다.
Pavan

방향을 변수에 저장할 수 있습니다. velocity! = 0 일 때만 변경하십시오. 저에게 효과적입니다
Leszek Zarna

scrollViewWillBeginDragging와 함께 작동, 멋진
demensdeum

40

해결책

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

1
velocityInView대신에 사용해보십시오 translationInView. 이것은 같은 움직임으로 방향을 바꿀 때 발생하는 문제를 해결합니다.
enreas

2
이 솔루션은 완벽하게 작동합니다. 다음 화면으로 이동하여 스크롤 업 및 다운 작업을 다시 수행하면 가장 많이 사용되는 답변이 작동하지 않는 경우가 있습니다.
Anjaneyulu Battula

최고, thnx!
Booharin

18

스위프트 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
y 대신 x를 사용 하시겠습니까?
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
스크롤 뷰의 pagingEnabled 속성 값이 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.
    }
   ....

스위프트 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);}

이 스크롤 뷰 대리자 메서드를 사용하십시오.

속도의 y 좌표가 + ve이면 스크롤보기가 아래로 스크롤되고 -ve scrollview이면 위쪽으로 스크롤됩니다. 마찬가지로 x 좌표를 사용하여 왼쪽 및 오른쪽 스크롤을 감지 할 수 있습니다.


0

Short & Easy는 속도 값을 확인하십시오. 속도가 0보다 크면 스크롤이 왼쪽에서 오른쪽으로 이동합니다.

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의 스위프트 코드에 감사드립니다.


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

위의 모든 대답에서 문제를 해결하는 두 가지 주요 방법은 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
    }
}

단점

그러나 다음 코드로 스크롤보기를 이동하면 상위 코드에서 스크롤 뷰를 인식 할 수 없습니다.

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

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.