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.