UIScrollView가 스크롤을 완료 한 시점을 감지하는 방법


145

UIScrollViewDelegate에는 두 가지 대리자 메서드 scrollViewDidScroll:가 scrollViewDidEndScrollingAnimation:있지만 스크롤이 완료되면 두 가지 중 어느 것도 알려주지 않습니다. scrollViewDidScroll스크롤보기가 스크롤을 완료하지 않았다는 것을 알려줍니다.

다른 방법 scrollViewDidEndScrollingAnimation은 사용자가 스크롤하지 않고 프로그래밍 방식으로 스크롤보기를 이동하는 경우에만 발생하는 것으로 보입니다.

누구든지 스크롤보기가 스크롤을 완료했을 때 감지하는 구성표를 알고 있습니까?


프로그래밍 방식으로 스크롤 한 후 완료된 스크롤을 감지하려면 다음을 참조하십시오. stackoverflow.com/questions/2358046/…
— Trevor

답변:


157
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView {
    [self stoppedScrolling];
}

- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate {
    if (!decelerate) {
        [self stoppedScrolling];
    }
}

- (void)stoppedScrolling {
    // ...
}

13
그래도 감속하는 경우에만 작동하는 것처럼 보입니다. 천천히 이동 한 다음 놓으면 didEndDecelerating을 호출하지 않습니다.
— Sean Clark Hess

4
공식 API이지만. 실제로 예상대로 작동하지는 않습니다. @Ashley Smart는보다 실용적인 솔루션을 제공했습니다.
— 디 우

완벽하게 작동하고, 설명은 의미가
— 스티븐 엘리엇

이것은 상호 작용을 끌어서 스크롤에 대해서만 작동합니다. 스크롤이 키보드 열기 또는 키보드 닫기와 같은 다른 것으로 인해 발생하면 해킹으로 이벤트를 감지 해야하는 것처럼 보이며 scrollViewDidEndScrollingAnimation도 유용하지 않습니다.
— Aurelien Porte

176

(320) 구현은 훨씬 더 있습니다 - 여기 스크롤의 일관성을 시작 / 끝을 얻을 수있는 패치입니다.

-(void)scrollViewDidScroll:(UIScrollView *)sender 
{   
[NSObject cancelPreviousPerformRequestsWithTarget:self];
    //ensure that the end of scroll is fired.
    [self performSelector:@selector(scrollViewDidEndScrollingAnimation:) withObject:sender afterDelay:0.3]; 

...
}

-(void)scrollViewDidEndScrollingAnimation:(UIScrollView *)scrollView
{
    [NSObject cancelPreviousPerformRequestsWithTarget:self];
...
}

스크롤 문제에 대한 유일한 유용한 답변입니다. 감사!
— 디 우

4
[0.3 : @ 선택기 (scrollViewDidEndScrollingAnimation withObject :) : 발신자 afterDelay 자기 performSelector]을되어야
— Brainware

1
2015 년에는 이것이 여전히 최고의 솔루션입니다.
— lukas

2
나는 솔직히 내가 2016 년 2 월, iOS 9.3에 있다고 믿을 수 없으며 이것이 여전히이 문제에 대한 최상의 솔루션입니다. 감사합니다, 매력처럼 일했습니다
— gmogames

1
넌 나를 구했다. 최고의 솔루션.
— Baran Emre

21

scrollViewDidEndDecelerating이 원하는 것 같습니다. UIScrollViewDelegates 선택적 메소드 :

- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView

스크롤보기가 스크롤 이동 감속을 종료했음을 대리인에게 알립니다.

UIScrollViewDelegate 문서


어, 나는 같은 대답을했다. :)
— Suvesh Pratapa 2016 년

도 비밀리에 이름이 붙어 있지만 정확히 내가 찾던 것입니다. 설명서를 더 잘 읽어야합니다. 그것은 긴 하루였습니다 ...
— Michael Gaylord

이 라이너는 내 문제를 해결했습니다. 나를 위해 빠른 수정! 감사합니다.
— Dody Rachmat Wicaksono

20

상호 작용 드래그와 관련된 모든 스크롤에 충분합니다.

- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView {
    _isScrolling = NO;
}

- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate {
    if (!decelerate) {
        _isScrolling = NO;
    }
}

이제 스크롤이 프로그래밍 방식의 setContentOffset / scrollRectVisible 때문인 경우 ( animated= YES 또는 스크롤이 종료 된 시점을 분명히 알 수 있음) :

 - (void)scrollViewDidEndScrollingAnimation {
     _isScrolling = NO;
}

스크롤이 키보드 열기 또는 키보드 닫기와 같은 다른 것으로 인한 경우 scrollViewDidEndScrollingAnimation유용하지 않기 때문에 해킹으로 이벤트를 감지 해야하는 것처럼 보입니다 .

PAGINATED 스크롤보기 의 경우 :

애플은 가속 곡선을 적용하고 scrollViewDidEndDecelerating드래그 할 때마다 호출되므로이 scrollViewDidEndDragging경우 사용할 필요가 없다고 생각합니다 .


페이지 매김 스크롤보기의 경우 한 가지 문제가 있습니다. 페이지 끝 위치에서 스크롤을 정확히 해제하면 (스크롤이 필요 scrollViewDidEndDecelerating하지 않은 경우) 호출되지 않습니다
— Shadow Of

19

이것은 다른 답변 중 일부에서 설명되었지만 스크롤이 완료되었을 때 결합 scrollViewDidEndDecelerating하고 scrollViewDidEndDragging:willDecelerate일부 작업을 수행 하는 방법은 다음 과 같습니다 (코드) .

- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
{
    [self stoppedScrolling];
}

- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView 
                  willDecelerate:(BOOL)decelerate
{
    if (!decelerate) {
        [self stoppedScrolling];
    }
}

- (void)stoppedScrolling
{
    // done, do whatever
}

7

: 난 단지 거의 같은 내가 요구되는이 질문에 발견 단지 있는 UIScrollView의 스크롤이 정지했을 때 정확히 어떻게 알 수 있습니까?

스크롤 할 때 didEndDecelerating이 작동하지만 고정 릴리스로 이동하면 등록되지 않습니다.

결국 해결책을 찾았습니다. didEndDragging의 매개 변수는 WillDecelerate이며 고정 해제 상황에서는 false입니다.

didEndDecelerating과 함께 DidEndDragging에서! decelerate를 확인하면 스크롤이 종료되는 두 가지 상황이 발생합니다.


5

Rx를 사용하는 경우 다음과 같이 UIScrollView를 확장 할 수 있습니다.

import RxSwift
import RxCocoa

extension Reactive where Base: UIScrollView {
    public var didEndScrolling: ControlEvent<Void> {
        let source = Observable
            .merge([base.rx.didEndDragging.map { !$0 },
                    base.rx.didEndDecelerating.mapTo(true)])
            .filter { $0 }
            .mapTo(())
        return ControlEvent(events: source)
    }
}

다음과 같이 할 수 있습니다.

scrollView.rx.didEndScrolling.subscribe(onNext: {
    // Do what needs to be done here
})

이것은 드래그와 감속을 모두 고려합니다.


이것이 바로 내가 찾던 것입니다. 감사!
— nomnom

4
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
    scrollingFinished(scrollView: scrollView)
}

func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
    if decelerate {
        //didEndDecelerating will be called for sure
        return
    }
    else {
        scrollingFinished(scrollView: scrollView)
    }
}

func scrollingFinished(scrollView: UIScrollView) {
   // Your code
}

3

Ashley Smart의 답변을 시도했지만 매력처럼 작동했습니다. scrollViewDidScroll 만 사용하는 또 다른 아이디어가 있습니다.

-(void)scrollViewDidScroll:(UIScrollView *)sender 
{   
    if(self.scrollView_Result.contentOffset.x == self.scrollView_Result.frame.size.width)       {
    // You have reached page 1
    }
}

방금 두 페이지를 가지고있어서 나를 위해 일했습니다. 그러나 둘 이상의 페이지가 있으면 문제가 될 수 있습니다 (현재 오프셋이 너비의 배수인지 확인할 수는 있지만 사용자가 두 번째 페이지에서 멈췄는지 또는 세 번째로 갔는지 알 수 없습니다. 더)


3

허용되는 답변의 신속한 버전 :

func scrollViewDidScroll(scrollView: UIScrollView) {
     // example code
}
func scrollViewDidEndDragging(scrollView: UIScrollView, willDecelerate decelerate: Bool) {
        // example code
}
func scrollViewDidEndZooming(scrollView: UIScrollView, withView view: UIView!, atScale scale: CGFloat) {
      // example code
}

3

누군가가 필요한 경우 Swift의 Ashley Smart 답변입니다.

func scrollViewDidScroll(_ scrollView: UIScrollView) {
        NSObject.cancelPreviousPerformRequests(withTarget: self)
        perform(#selector(UIScrollViewDelegate.scrollViewDidEndScrollingAnimation), with: nil, afterDelay: 0.3)
    ...
}

func scrollViewDidEndScrollingAnimation(_ scrollView: UIScrollView) {
        NSObject.cancelPreviousPerformRequests(withTarget: self)
    ...
}

2

스크롤이 완료되면 앱 전체에서 감지 할 수있는 솔루션을 개발했습니다 : https://gist.github.com/k06a/731654e3168277fb1fd0e64abc7d899e

runloop 모드의 변경 사항을 추적한다는 아이디어를 기반으로합니다. 그리고 스크롤 후 최소 0.2 초 후에 블록을 수행하십시오.

이것은 iOS10 +의 런 루프 모드 변경을 추적하기위한 핵심 아이디어입니다.

- (void)tick {
    [[NSRunLoop mainRunLoop] performInModes:@[ UITrackingRunLoopMode ] block:^{
        [self tock];
    }];
}

- (void)tock {
    self.runLoopModeWasUITrackingAgain = YES;
    [[NSRunLoop mainRunLoop] performInModes:@[ NSDefaultRunLoopMode ] block:^{
        [self tick];
    }];
}

그리고 iOS2 +와 같은 저 배포 대상을위한 솔루션 :

- (void)tick {
    [[NSRunLoop mainRunLoop] performSelector:@selector(tock) target:self argument:nil order:0 modes:@[ UITrackingRunLoopMode ]];
}

- (void)tock {
    self.runLoopModeWasUITrackingAgain = YES;
    [[NSRunLoop mainRunLoop] performSelector:@selector(tick) target:self argument:nil order:0 modes:@[ NSDefaultRunLoopMode ]];
}

Lol 앱 ​​전체 스크롤 감지-앱의 UIScrollVIew가 현재 스크롤중인 경우 ... 쓸모없는 것 같습니다.
— Isaac Carol Weisberg

@IsaacCarolWeisberg는 부드러운 스크롤이 끝날 때까지 무거운 작업을 지연시켜 부드럽게 유지할 수 있습니다.
— k06a

글로벌 동시 디스패치 큐에서 실행중인 작업이있을 것입니다.
— Isaac Carol Weisberg

1

요약하자면 (그리고 초보자도). 그렇게 고통스럽지 않습니다. 프로토콜을 추가 한 다음 감지에 필요한 기능을 추가하십시오.

UIScrolView가 포함 된보기 (클래스)에서 프로토콜을 추가 한 후 여기의 기능을보기 (클래스)에 추가하십시오.

// --------------------------------
// In the "h" file:
// --------------------------------
@interface myViewClass : UIViewController  <UIScrollViewDelegate> // <-- Adding the protocol here

// Scroll view
@property (nonatomic, retain) UIScrollView *myScrollView;
@property (nonatomic, assign) BOOL isScrolling;

// Protocol functions
- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView;
- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView;


// --------------------------------
// In the "m" file:
// --------------------------------
@implementation BlockerViewController

- (void)viewDidLoad {
    CGRect scrollRect = self.view.frame; // Same size as this view
    self.myScrollView = [[UIScrollView alloc] initWithFrame:scrollRect];
    self.myScrollView.delegate = self;
    self.myScrollView.contentSize = CGSizeMake(scrollRect.size.width, scrollRect.size.height);
    self.myScrollView.contentInset = UIEdgeInsetsMake(0.0,22.0,0.0,22.0);
    // Allow dragging button to display outside the boundaries
    self.myScrollView.clipsToBounds = NO;
    // Prevent buttons from activating scroller:
    self.myScrollView.canCancelContentTouches = NO;
    self.myScrollView.delaysContentTouches = NO;
    [self.myScrollView setBackgroundColor:[UIColor darkGrayColor]];
    [self.view addSubview:self.myScrollView];

    // Add stuff to scrollview
    UIImage *myImage = [UIImage imageNamed:@"foo.png"];
    [self.myScrollView addSubview:myImage];
}

// Protocol functions
- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView {
    NSLog(@"start drag");
    _isScrolling = YES;
}

- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView {
    NSLog(@"end decel");
    _isScrolling = NO;
}

- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate {
    NSLog(@"end dragging");
    if (!decelerate) {
       _isScrolling = NO;
    }
}

// All of the available functions are here:
// https://developer.apple.com/library/ios/documentation/UIKit/Reference/UIScrollViewDelegate_Protocol/Reference/UIScrollViewDelegate.html

1

탭핑 및 드래그 동작의 경우에 드래그가 scrollViewDidEndDecelerating를 호출한다는 것을 알았습니다.

그리고 코드 ([_scrollView setContentOffset : contentOffset animated : YES] ;;)를 사용하여 수동으로 변경 오프셋이 scrollViewDidEndScrollingAnimation을 호출했습니다.

//This delegate method is called when the dragging scrolling happens, but no when the     tapping
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
{
    //do whatever you want to happen when the scroll is done
}

//This delegate method is called when the tapping scrolling happens, but no when the  dragging
-(void)scrollViewDidEndScrollingAnimation:(UIScrollView *)scrollView
{
     //do whatever you want to happen when the scroll is done
}

1

대안은 scrollViewWillEndDragging:withVelocity:targetContentOffset사용자가 손가락을 들어 올릴 때마다 호출되며 스크롤이 멈출 대상 컨텐츠 오프셋을 포함하는 것입니다. 이 컨텐츠 오프셋을 사용 scrollViewDidScroll:하면 스크롤보기가 스크롤을 중지 한시기를 올바르게 식별합니다.

private var targetY: CGFloat?
public func scrollViewWillEndDragging(_ scrollView: UIScrollView,
                                      withVelocity velocity: CGPoint,
                                      targetContentOffset: UnsafeMutablePointer<CGPoint>) {
       targetY = targetContentOffset.pointee.y
}
public func scrollViewDidScroll(_ scrollView: UIScrollView) {
    if (scrollView.contentOffset.y == targetY) {
        print("finished scrolling")
    }

0

UIScrollview에는 대리자 메서드가 있습니다.

- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView

델리게이트 메소드에 아래 코드를 추가하십시오.

- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
{
CGSize scrollview_content=scrollView.contentSize;
CGPoint scrollview_offset=scrollView.contentOffset;
CGFloat size=scrollview_content.width;
CGFloat x=scrollview_offset.x;
if ((size-self.view.frame.size.width)==x) {
    //You have reached last page
}
}

0

UIScrollViewDelegate스크롤이 실제로 완료되었을 때 감지 (또는 '예측'이라고 말하는 것이 좋습니다)하는 데 사용할 수 있는 방법 이 있습니다.

func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>)

의 UIScrollViewDelegate 어느 정말 완료 스크롤 할 때 ( '예측'말 이상) 감지하는 데 사용할 수 있습니다.

필자의 경우 다음과 같이 가로 스크롤과 함께 사용했습니다 ( Swift 3 ).

func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) {
    perform(#selector(self.actionOnFinishedScrolling), with: nil, afterDelay: Double(velocity.x))
}
func actionOnFinishedScrolling() {
    print("scrolling is finished")
    // do what you need
}

0

Ashley Smart 로직을 사용하며 Swift 4.0 이상으로 변환 중입니다.

    func scrollViewDidScroll(_ scrollView: UIScrollView) {
         NSObject.cancelPreviousPerformRequests(withTarget: self)
        perform(#selector(UIScrollViewDelegate.scrollViewDidEndScrollingAnimation(_:)), with: scrollView, afterDelay: 0.3)
    }

    func scrollViewDidEndScrollingAnimation(_ scrollView: UIScrollView) {
        NSObject.cancelPreviousPerformRequests(withTarget: self)
    }

위의 논리는 사용자가 테이블 뷰를 스크롤 할 때와 같은 문제를 해결합니다. 로직이 없으면 테이블 뷰를 스크롤 할 때 didEnd호출되지만 아무것도 실행되지 않습니다. 현재 2020 년에 사용하고 있습니다.


0

일부 이전 iOS 버전 (예 : iOS 9, 10)에서 scrollViewDidEndDecelerating 에서 scrollView를 터치하여 갑자기 중지하면 트리거되지 않습니다.

그러나 현재 버전 (iOS 13)에서는 scrollViewDidEndDecelerating확실하게 트리거됩니다 (아는 한).

따라서 앱이 이전 버전을 대상으로하는 경우 Ashley Smart에서 언급 한 것과 같은 해결 방법이 필요하거나 다음과 같은 해결 방법이 필요할 수 있습니다.


    func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
        if !scrollView.isTracking, !scrollView.isDragging, !scrollView.isDecelerating { // 1
            scrollViewDidEndScrolling(scrollView)
        }
    }

    func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
        if !decelerate, scrollView.isTracking, !scrollView.isDragging, !scrollView.isDecelerating { // 2
            scrollViewDidEndScrolling(scrollView)
        }
    }

    func scrollViewDidEndScrolling(_ scrollView: UIScrollView) {
        // Do something here
    }

설명

UIScrollView는 세 가지 방법으로
중지됩니다
.-빠르게 스크롤 및 자체 중지 -손가락 터치로 빠르게 스크롤 및 중지 (비상 브레이크)
-천천히 스크롤 및 중지

첫 번째는 scrollViewDidEndDecelerating다른 유사한 방법 으로 감지 할 수 있지만 다른 두 가지는 감지 할 수 없습니다.

운 좋게도 UIScrollView식별 할 수있는 상태는 "// 1"과 "// 2"로 표시된 두 줄에 사용됩니다.

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