Cách phát hiện khi UIScrollView cuộn xong


145

UIScrollViewDelegate đã có hai phương thức ủy nhiệm scrollViewDidScroll:scrollViewDidEndScrollingAnimation:cả hai phương thức này không cho bạn biết khi cuộn đã hoàn thành. scrollViewDidScrollchỉ thông báo cho bạn rằng chế độ xem cuộn đã cuộn không phải là nó đã cuộn xong.

Phương pháp khác scrollViewDidEndScrollingAnimationdường như chỉ kích hoạt nếu bạn lập trình di chuyển chế độ xem cuộn nếu người dùng cuộn.

Có ai biết sơ đồ để phát hiện khi chế độ xem cuộn đã hoàn thành cuộn không?


Xem thêm, nếu bạn muốn phát hiện cuộn xong sau khi cuộn theo chương trình: stackoverflow.com/questions/2358046/iêu
Trevor

Câu trả lời:


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

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

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

13
Có vẻ như nó chỉ hoạt động nếu nó đang giảm tốc. Nếu bạn xoay chậm, sau đó thả ra, nó không gọi didEndDecelerating.
Sean Clark Hess

4
Mặc dù đó là API chính thức. Nó thực sự không luôn luôn hoạt động như chúng ta mong đợi. @Ashley Smart đã đưa ra một giải pháp thiết thực hơn.
Di Wu

Hoạt động hoàn hảo và lời giải thích hợp lý
Steven Elliott

Điều này chỉ hoạt động cho cuộn là do kéo tương tác. Nếu cuộn của bạn là do một cái gì đó khác (như mở bàn phím hoặc đóng bàn phím), có vẻ như bạn sẽ phải phát hiện sự kiện bằng hack và scrollViewDidEndScrollingAnimation cũng không hữu ích.
Aurelien Porte

176

Các 320 triển khai là tốt hơn rất nhiều - đây là một bản vá để có được phù hợp bắt đầu / kết thúc của cuộn.

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

Câu trả lời hữu ích duy nhất tại SO cho vấn đề cuộn của tôi. Cảm ơn!
Di Wu

4
nên [tự performSelector: @selector (scrollViewDidEndScrollingAnimation :) withObject: người gửi afterDelay: 0,3]
Brainware

1
Trong năm 2015, đây vẫn là giải pháp tốt nhất.
lukas

2
Tôi thực sự không thể tin rằng tôi vào tháng 2 năm 2016, iOS 9.3 và đây vẫn là giải pháp tốt nhất cho vấn đề này. Cảm ơn, đã làm việc như một

1
Bạn đã cứu tôi. Giải pháp tốt nhất.
Baran Emre

21

Tôi nghĩ scrollViewDidEndDecelerating là thứ bạn muốn. Phương pháp tùy chọn UIScrollViewDelegates của nó:

- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView

Nói với đại biểu rằng chế độ xem cuộn đã kết thúc giảm tốc độ di chuyển.

Tài liệu UIScrollViewDelegate


Er, tôi đã đưa ra câu trả lời tương tự. :)
Suvesh Pratapa

Doh. Một cái gì đó được đặt tên mật mã nhưng đó chính xác là những gì tôi đang tìm kiếm. Nên đọc tài liệu tốt hơn. Đó là một ngày dài ...
Michael Gaylord

Điều này một giải quyết vấn đề của tôi. Sửa nhanh cho tôi! Cảm ơn bạn.
Dody Rachmat Wicaksono

20

Đối với tất cả các cuộn liên quan đến kéo tương tác, điều này sẽ là đủ:

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

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

Bây giờ, nếu cuộn của bạn là do một bộ lập trìnhContent Offerset / scrollRectVisible (với animated= CÓ hoặc bạn rõ ràng biết khi nào cuộn kết thúc):

 - (void)scrollViewDidEndScrollingAnimation {
     _isScrolling = NO;
}

Nếu cuộn của bạn là do một thứ khác (như mở bàn phím hoặc đóng bàn phím), có vẻ như bạn sẽ phải phát hiện sự kiện bằng một bản hack vì scrollViewDidEndScrollingAnimationcũng không hữu ích.

Trường hợp của chế độ xem cuộn PAGINATED :

Bởi vì, tôi đoán, Apple áp dụng đường cong tăng tốc, scrollViewDidEndDeceleratingđược gọi cho mỗi lần kéo nên không cần sử dụng scrollViewDidEndDraggingtrong trường hợp này.


Trường hợp xem cuộn được phân trang của bạn có một vấn đề: nếu bạn phát hành cuộn chính xác ở vị trí cuối trang (khi không cần cuộn), scrollViewDidEndDeceleratingsẽ không được gọi
Shadow Of

19

Điều này đã được mô tả trong một số câu trả lời khác, nhưng đây là (cách mã) cách kết hợp scrollViewDidEndDeceleratingscrollViewDidEndDragging:willDeceleratethực hiện một số thao tác khi cuộn đã kết thúc:

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

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

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

7

Tôi chỉ tìm thấy câu hỏi này, tương tự như tôi đã hỏi: Làm thế nào để biết chính xác thời điểm cuộn của UIScrollView đã dừng?

Mặc dù didEndDecelerating hoạt động khi cuộn, di chuyển với bản phát hành cố định không đăng ký.

Cuối cùng tôi đã tìm thấy một giải pháp. didEndDragging có một tham số WillDecelerate, sai trong tình huống phát hành tĩnh.

Bằng cách kiểm tra! Giảm tốc trong DidEndDragging, kết hợp với didEndDecelerating, bạn sẽ có được cả hai tình huống là kết thúc cuộn.


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
}

4

Nếu bạn vào Rx, bạn có thể mở rộng UIScrollView như thế này:

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

sẽ cho phép bạn làm như thế này:

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

Điều này sẽ tính đến cả kéo và giảm tốc.


Điều này thật đúng với gì mà tôi đã tìm kiếm. Cảm ơn!
nomnom

3

Tôi đã thử câu trả lời của Ashley Smart và nó hoạt động như một cơ duyên. Đây là một ý tưởng khác, với việc chỉ sử dụng scrollViewDidScroll

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

Tôi chỉ có hai trang để nó làm việc cho tôi. Tuy nhiên, nếu bạn có nhiều hơn một trang, nó có thể có vấn đề (bạn có thể kiểm tra xem phần bù hiện tại có phải là bội số của chiều rộng hay không nhưng sau đó bạn sẽ không biết liệu người dùng dừng lại ở trang thứ 2 hay đang trên đường đến trang 3 hoặc hơn)


3

Phiên bản Swift của câu trả lời được chấp nhận:

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

Nếu ai đó cần, đây là câu trả lời của Ashley Smart trong Swift

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

Chỉ cần phát triển giải pháp để phát hiện khi cuộn xong toàn ứng dụng: https://gist.github.com/k06a/731654e3168277fb1fd0e64abc7d899e

Nó dựa trên ý tưởng theo dõi các thay đổi của chế độ runloop. Và thực hiện các khối ít nhất sau 0,2 giây sau khi cuộn.

Đây là ý tưởng cốt lõi để theo dõi các chế độ runloop thay đổi iOS10 +:

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

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

Và giải pháp cho các mục tiêu triển khai thấp như 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 ]];
}

Phát hiện cuộn trên toàn ứng dụng Lol - như trong, nếu bất kỳ ứng dụng nào trong UIScrollVIew của ứng dụng hiện đang cuộn ... có vẻ như vô dụng ...
Isaac Carol Weisberg

@IsaacCarolweisberg bạn có thể trì hoãn các hoạt động nặng cho đến khi cuộn hoàn thành để giữ cho nó trơn tru.
k06a

hoạt động nặng nề, bạn nói ... những người mà tôi đang thực hiện trong hàng đợi công văn đồng thời toàn cầu, có lẽ
Isaac Carol Weisberg

1

Để tóm tắt (và cho người mới). Nó không đau lắm đâu. Chỉ cần thêm giao thức, sau đó thêm các chức năng bạn cần để phát hiện.

Trong chế độ xem (lớp) có chứa UIScrolView, hãy thêm giao thức, sau đó thêm bất kỳ chức năng nào từ đây vào chế độ xem (lớp) của bạn.

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

Tôi đã có một trường hợp chạm và kéo hành động và tôi phát hiện ra rằng việc kéo đó đang gọi scrollViewDidEndDecelerating

Và phần bù thay đổi theo cách thủ công với mã ([_scrollView setContent Offerset: contentPackset hoạt hình: YES];) đã gọi 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

Một cách khác là sử dụng scrollViewWillEndDragging:withVelocity:targetContentOffsetcái được gọi bất cứ khi nào người dùng nhấc ngón tay lên và chứa phần bù nội dung đích nơi cuộn sẽ dừng lại. Sử dụng nội dung này bù vào scrollViewDidScroll:xác định chính xác khi chế độ xem cuộn đã dừng cuộn.

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 có phương thức đại biểu

- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView

Thêm các dòng mã dưới đây trong phương thức ủy nhiệm

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

Có một phương pháp UIScrollViewDelegatecó thể được sử dụng để phát hiện (hoặc tốt hơn là nói 'dự đoán') khi quá trình cuộn thực sự kết thúc:

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

của UIScrollViewDelegate đó có thể được sử dụng để phát hiện (hoặc tốt hơn là nói 'dự đoán') khi quá trình cuộn đã thực sự kết thúc.

Trong trường hợp của tôi, tôi đã sử dụng nó với cuộn ngang như sau (trong 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

Sử dụng logic thông minh Ashley và đang được chuyển đổi thành Swift 4.0 trở lên.

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

Logic trên giải quyết các vấn đề như khi người dùng cuộn khỏi chế độ xem bảng. Nếu không có logic, khi bạn cuộn khỏi chế độ xem bảng, didEndsẽ được gọi nhưng nó sẽ không thực thi bất cứ điều gì. Hiện đang sử dụng nó vào năm 2020.


0

Trên một số phiên bản iOS trước đó (như iOS 9, 10), scrollViewDidEndDeceleratingsẽ không được kích hoạt nếu cuộn Xem đột ngột bị dừng khi chạm vào.

Nhưng trong phiên bản hiện tại (iOS 13), scrollViewDidEndDeceleratingchắc chắn sẽ được kích hoạt (Theo như tôi biết).

Vì vậy, nếu Ứng dụng của bạn cũng nhắm mục tiêu các phiên bản trước đó, bạn có thể cần một cách giải quyết như phiên bản được đề cập bởi Ashley Smart hoặc bạn có thể sử dụng phiên bản sau.


    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
    }

Giải trình

UIScrollView sẽ bị dừng theo ba cách:
- nhanh chóng cuộn và dừng lại
- nhanh chóng cuộn và dừng bằng cách chạm ngón tay (như phanh khẩn cấp)
- cuộn từ từ và dừng lại

Phương pháp đầu tiên có thể được phát hiện bằng scrollViewDidEndDeceleratingvà các phương pháp tương tự khác trong khi hai phương pháp khác không thể.

May mắn thay, UIScrollViewcó ba trạng thái chúng ta có thể sử dụng để xác định chúng, được sử dụng trong hai dòng được nhận xét bởi "// 1" và "// 2".

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.