วิธีการตรวจหาเมื่อ 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
ดูเหมือนว่าจะใช้ได้เฉพาะในกรณีที่ชะลอตัวลงเท่านั้น หากคุณเลื่อนอย่างช้า ๆ จากนั้นปล่อยมันจะไม่เรียกว่าสิ้นสุดลดลง
Sean Clark Hess

4
แม้ว่าจะเป็น API อย่างเป็นทางการ อันที่จริงมันไม่ได้ทำงานตามที่เราคาดหวัง @Ashley Smart ให้โซลูชันที่ใช้งานได้จริงมากขึ้น
Di Wu

ทำงานได้อย่างสมบูรณ์แบบและคำอธิบายนั้นสมเหตุสมผล
Steven Elliott

ใช้งานได้เฉพาะกับการเลื่อนที่มีสาเหตุมาจากการลากการโต้ตอบ หากการเลื่อนของคุณเกิดจากสิ่งอื่น (เช่นการเปิดแป้นพิมพ์หรือการปิดแป้นพิมพ์) ดูเหมือนว่าคุณจะต้องตรวจจับเหตุการณ์ด้วยการแฮ็กและ 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];
...
}

คำตอบที่เป็นประโยชน์เฉพาะที่ SO สำหรับปัญหาการเลื่อนของฉัน ขอบคุณ!
Di Wu

4
ควรเป็น [self performSelector: @selector (scrollViewDidEndScrollingAnimation :) withObject: sender afterDelay: 0.3]
Brainware

1
ในปี 2558 นี้ยังคงเป็นทางออกที่ดีที่สุด
Lukas

2
ฉันไม่อยากเชื่อเลยว่าฉันจะอยู่ในกุมภาพันธ์ 2559, iOS 9.3 และนี่ก็ยังเป็นทางออกที่ดีที่สุดสำหรับปัญหานี้ ขอบคุณทำงานเหมือนมีเสน่ห์
gmogames

1
คุณช่วยฉันไว้. ทางออกที่ดีที่สุด
Baran Emre

21

ฉันคิดว่า scrollViewDidEndDecelerating เป็นสิ่งที่คุณต้องการ UIScrollViewDelegates วิธีการที่เป็นตัวเลือก:

- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView

บอกผู้รับมอบสิทธิ์ว่ามุมมองการเลื่อนได้สิ้นสุดลงช้าลงการเคลื่อนที่แบบเลื่อน

เอกสารประกอบ UIScrollViewDelegate


เอ้อฉันตอบเหมือนเดิม :)
Suvesh Pratapa

Doh ค่อนข้างชื่อเข้ารหัส แต่สิ่งที่ฉันกำลังมองหา ควรอ่านเอกสารให้ดีกว่านี้ เป็นวันที่ยาวนาน ...
ไมเคิลเกย์

ซับอันนี้แก้ปัญหาของฉันได้ แก้ไขด่วนสำหรับฉัน! ขอบคุณ.
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 :

เพราะฉันเดาว่า Apple ใช้เส้นโค้งการเร่งความเร็ว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 หยุด?

แม้ว่า didndndecelerating ทำงานเมื่อเลื่อน, ปรากฎว่าด้วยการปล่อยนิ่งไม่ลงทะเบียนของ

ในที่สุดฉันก็พบทางออก didEndDragging มีพารามิเตอร์ WillDecelerate ซึ่งเป็นเท็จในสถานการณ์การวางจำหน่ายแบบคงที่

ด้วยการตรวจสอบ! ชะลอตัวลงใน DidEndDragging เมื่อรวมกับ DidEndDecelerating คุณจะได้รับทั้งสองสถานการณ์ที่ส่วนท้ายของการเลื่อน


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

ฉันลองคำตอบของแอชลีย์สมาร์ทแล้วมันก็ใช้งานได้อย่างมีเสน่ห์ นี่เป็นแนวคิดอื่นโดยใช้ scrollViewDidScroll เท่านั้น

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

ฉันเพิ่งมีสองหน้าดังนั้นมันจึงเหมาะกับฉัน อย่างไรก็ตามหากคุณมีมากกว่าหนึ่งหน้าอาจเป็นปัญหาได้ (คุณสามารถตรวจสอบว่าออฟเซ็ตปัจจุบันมีความกว้างหลายเท่าหรือไม่ แต่คุณไม่ทราบว่าผู้ใช้หยุดทำงานที่หน้า 2 หรือกำลังไปที่ 3 หรือ มากกว่า)


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

หากมีใครต้องการนี่คือคำตอบของ Ashley Smart ใน 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

เพิ่งพัฒนาโซลูชันเพื่อตรวจสอบเมื่อเลื่อนดูแอปที่เสร็จสิ้นแล้ว: https://gist.github.com/k06a/731654e3168277fb1fd0e64abc7d899e

มันขึ้นอยู่กับแนวคิดของการติดตามการเปลี่ยนแปลงของโหมด runloop และดำเนินการบล็อกอย่างน้อยหลังจาก 0.2 วินาทีหลังจากการเลื่อน

นี่คือแนวคิดหลักสำหรับการติดตามโหมด runloop ที่เปลี่ยนแปลง 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 ]];
}

การตรวจจับการเลื่อนทั่วแอปฮ่า ๆ - เช่นเดียวกับในกรณีที่ 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

ใช้แอชลีย์สมาร์ทลอจิกและจะถูกแปลงเป็น 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)
    }

ตรรกะด้านบนแก้ปัญหาเช่นเมื่อผู้ใช้เลื่อนออกจาก tableview โดยไม่ต้องใช้ตรรกะเมื่อคุณเลื่อนออกจาก tableview didEndจะถูกเรียก แต่มันจะไม่ดำเนินการอะไร กำลังใช้งานในปี 2020


0

ใน iOS เวอร์ชันก่อนหน้าบางรุ่น (เช่น iOS 9, 10) scrollViewDidEndDeceleratingจะไม่ถูกเรียกใช้หาก scrollView หยุดทำงานทันทีโดยการแตะ

แต่ในเวอร์ชั่นปัจจุบัน (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"

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.