bestsource

UIScrollView 스크롤이 완료되었을 때 감지하는 방법

bestsource 2023. 6. 28. 21:51
반응형

UIScrollView 스크롤이 완료되었을 때 감지하는 방법

에는 두 UIScrollViewDelegate가 있습니다.scrollViewDidScroll:그리고.scrollViewDidEndScrollingAnimation:하지만 이 두 가지 모두 스크롤이 완료된 시점은 알 수 없습니다. scrollViewDidScroll스크롤 보기가 스크롤을 완료하지 않았음을 알리는 메시지만 표시됩니다.

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

스크롤 뷰가 스크롤을 완료했을 때 감지할 수 있는 계획을 아는 사람이 있습니까?

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];
...
}
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView {
    [self stoppedScrolling];
}

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

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

이는 일부 다른 답변에서 설명되었지만, 다음은 (코드로) 결합 방법입니다.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
}

드래그 상호 작용과 관련된 모든 스크롤의 경우 이 정도면 충분합니다.

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

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

이 프로그래밍 의 setContentOffset/with 이스프이크으로방로롤제래식그밍의인경우한contentoffsc▁nowset▁set으로이with제▁yourcontentvis(visscoff▁ifible▁is/,matic▁scroll경▁programrectroll▁(▁a▁to인ContentOffset/scrollRectVisible())) 때문에 발생한 경우animated예 또는 스크롤이 종료되는 시점을 명확하게 알 수 있습니다.):

 - (void)scrollViewDidEndScrollingAnimation {
     _isScrolling = NO;
}

스크롤이 다른 이유(예: 키보드 열기 또는 키보드 닫기)로 인해 발생한 경우 다음과 같은 이유로 인해 이벤트를 탐지해야 합니다.scrollViewDidEndScrollingAnimation또한 유용하지 않습니다.

페이지화된 스크롤 뷰의 경우:

곡선을 하기 때문에, 애이가곡속적용때문에기하선을플,,때▁because애▁curve에▁an.scrollViewDidEndDecelerating모든 드래그에 대해 호출되므로 사용할 필요가 없습니다.scrollViewDidEndDragging이 경우에는

ScrollViewDidEndDecelerating이 당신이 원하는 것 같습니다.UIScrollViewDelegate 옵션 메서드:

- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView

스크롤 보기가 스크롤 이동의 감속을 종료했음을 딜러에게 알립니다.

UIScrollView 딜러 문서 보기

방금 이 질문을 발견했는데, 제가 질문한 것과 거의 같습니다.UIScrollView의 스크롤이 중지된 시기를 정확히 알 수 있는 방법은 무엇입니까?

스크롤할 때 EndDecelerating이 작동했지만 고정 해제를 사용한 Paning은 등록되지 않습니다.

결국 해결책을 찾았습니다. didDragging에는 WillDecelerate 매개 변수가 있습니다. 이 매개 변수는 고정 릴리스 상황에서 거짓입니다.

DidEndDraging에서 !decelerate를 확인하고 DidEndDecelerating과 결합하면 스크롤의 끝인 두 상황을 모두 얻을 수 있습니다.

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

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

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

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

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

필요한 경우 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)
    ...
}

애슐리 스마트의 답변을 시도해 보았는데 매력적으로 작동했습니다.여기 스크롤만 사용하는 다른 아이디어가 있습니다. ViewDidScroll

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

저는 2페이지밖에 없어서 잘 됐어요.그러나 두 페이지 이상인 경우 문제가 될 수 있습니다(현재 오프셋이 너비의 배수인지 확인할 수 있지만 사용자가 두 번째 페이지에서 멈추거나 세 번째 페이지 이상으로 이동 중인지 알 수 없습니다).

수락된 답변의 빠른 버전:

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
}

앱 전체로 스크롤이 완료되었을 때 감지할 수 있는 방금 개발된 솔루션: https://gist.github.com/k06a/731654e3168277fb1fd0e64abc7d899e

런루프 모드의 변화를 추적하는 아이디어를 기반으로 합니다.그리고 스크롤 후 최소 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 ]];
}

요약(및 초보자용).그렇게 아프지는 않아요.프로토콜을 추가한 다음 탐지에 필요한 기능을 추가하기만 하면 됩니다.

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

작업을 탭하고 끄는 경우가 있었는데 드래그가 scrollViewDidEndDecelerating을 호출하는 것을 발견했습니다.

코드([_scrollView setContentOffset:contentOffset 애니메이션: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
}

의 방법이 있습니다.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
}

다른 은 대은다같다습니과음안다를 사용하는 것입니다.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")
    }

9 일부 의 iOS에서는 9, 10을 사용할 수 .scrollViewDidEndDecelerating스크롤 보기를 터치하여 갑자기 중지하면 트리거되지 않습니다.

하지만 현재 버전(iOS 13)에서는scrollViewDidEndDecelerating확실히 작동될 것입니다(제가 알기로는).

따라서 앱이 이전 버전도 대상으로 했다면 애슐리 스마트가 언급한 것과 같은 해결 방법이 필요하거나 다음과 같은 방법을 사용할 수 있습니다.


    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"가 주석을 단 두 줄에 사용됩니다.

저는 모든 순열을 조사했고 제가 찾은 최고의 코드는 이것이었습니다.흥미로운 부분은 'scrollViewWillBeginDragging'과 속도가 0인지 감지하는 것입니다.애니메이션 스크롤 도중 스크롤에 브레이크를 걸기 위해 사용자의 손가락이 화면에 표시될 때 발생할 수 있습니다.이 작업을 수행해도 다른 위임 '중지' API가 트리거되지 않습니다.

public func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {        
    let velocity = scrollView.panGestureRecognizer.velocity(in: scrollView.superview)
    if (velocity.equalTo(CGPoint.zero)){
        scrollViewDidEndScrollingAnimation(scrollView)
    }
}


public func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
    if (decelerate == false){
        scrollViewDidEndScrollingAnimation(scrollView)
    }
}

public func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
    scrollViewDidEndScrollingAnimation(scrollView)
}

public func scrollViewDidEndScrollingAnimation(_ scrollView: UIScrollView) {
    // do whatever when scrolling stops
}

모든 확대/축소 및 스크롤 제스처가 종료되고 감속 및 확대/축소 바운스와 같은 모든 애니메이션이 종료되면 작동하는 콜백이 필요했습니다.

UIScrollView우리가 확인할 수 있는 몇 가지 플래그가 있습니다, 예를 들어.isTracking그리고.isDecelerating저희는 다양한 것들도 가지고 있습니다.scrollViewDidEnd...콜백을 위임합니다.그러나 이러한 플래그와 콜백의 업데이트는 일관되지 않은 순서로 이루어지며 모든 에지 사례를 다루는 것은 매우 어려워 보입니다.

제가 지금 하고 있는 일은 스크롤 보기의 플래그 검사를 다음 실행 루프로 연기하는 것입니다.이렇게 하면 모든 플래그가 업데이트됩니다.

func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
  dispatchScrollViewInteractionUpdate()
}

func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
  dispatchScrollViewInteractionUpdate()
}

func scrollViewDidEndScrollingAnimation(_ scrollView: UIScrollView) {
  dispatchScrollViewInteractionUpdate()
}

func scrollViewDidEndZooming(_ scrollView: UIScrollView, with view: UIView?, atScale scale: CGFloat) {
  dispatchScrollViewInteractionUpdate()
}

private func dispatchScrollViewInteractionUpdate() {
  DispatchQueue.main.async {
    self.updateScrollViewInteraction()
  }
}

private func updateScrollViewInteraction() {    
  if !scrollView.isZooming
  && !scrollView.isDragging
  && !scrollView.isDecelerating
  && !scrollView.isZoomBouncing
  && !scrollView.isTracking {
    print("All animations and interactions have ended.")
  }
}

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

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년 현재 사용 중입니다.

RXSwift 버전

commentsTableView.rx.didEndScrollingAnimation
    .bind(
        onNext: { _ in
            debugPrint(":DEBUG:", "didEndScrollingAnimation")
        }
    )
    .disposed(by: disposeBag)

언급URL : https://stackoverflow.com/questions/993280/how-to-detect-when-a-uiscrollview-has-finished-scrolling

반응형