Bir UIScrollView kaydırmayı bitirdiğinde nasıl tespit edilir


147

UIScrollViewDelegate'in iki delege yöntemi vardır scrollViewDidScroll:ve scrollViewDidEndScrollingAnimation:bunlardan hiçbiri kaydırmanın ne zaman tamamlandığını size söylemez. scrollViewDidScrollyalnızca kaydırma görünümünün kaydırılmadığını değil, kaydırmayı bitirdiğini size bildirir.

Diğer yöntem scrollViewDidEndScrollingAnimationyalnızca, kullanıcı kaydırmazsa kaydırma görünümünü programlı olarak hareket ettirirseniz etkinleşir.

Kaydırma görünümünün kaydırmayı tamamladığını algılayacak düzeni bilen var mı?


Ayrıca, programlı olarak kaydırdıktan sonra bitmiş kaydırmayı algılamak istiyorsanız, bakınız: stackoverflow.com/questions/2358046/…
Trevor

Yanıtlar:


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

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

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

13
Görünüşe göre bu sadece yavaşlıyor olsa da işe yarıyor. Yavaşça kaydırıp ardından serbest bırakırsanız, didEndDecelerating çağırmaz.
Sean Clark Hess

4
Resmi API olmasına rağmen. Aslında her zaman beklediğimiz gibi çalışmıyor. @Ashley Smart daha pratik bir çözüm verdi.
Di Wu

Mükemmel çalışıyor ve açıklama mantıklı
Steven Elliott

Bu, yalnızca sürükleme etkileşiminden kaynaklanan kaydırma için işe yarar. Kaydırmanız başka bir şeyden kaynaklanıyorsa (klavye açılması veya klavye kapanması gibi), olayı bir hack ile tespit etmeniz gerekecek gibi görünüyor ve scrollViewDidEndScrollingAnimation da kullanışlı değil.
Aurelien Porte

175

320 uygulamaları çok daha iyi - burada kaydırma tutarlı başlat / uçlarını almak için bir yamadır.

-(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'daki kaydırma sorunum için tek yararlı cevap. Teşekkürler!
Di Wu

4
[self performSelector: @selector (scrollViewDidEndScrollingAnimation :) withObject: sender afterDelay: 0.3] olmalıdır
Brainware

1
2015'te bu hala en iyi çözüm.
lukas

2
Açıkçası, Şubat 2016, iOS 9.3'te olduğuma inanamıyorum ve bu hala bu soruna en iyi çözüm. Teşekkürler, harika çalıştı
gmogames

1
Beni kurtardın. En iyi çözüm.
Baran Emre

21

Sürükleme etkileşimleriyle ilgili tüm kaydırmalar için bu yeterli olacaktır:

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

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

Şimdi, kaydırmanız programlı bir setContentOffset / scrollRectVisible kaynaklıysa ( animated= YES ile veya kaydırmanın ne zaman bittiğini açıkça biliyorsunuz):

 - (void)scrollViewDidEndScrollingAnimation {
     _isScrolling = NO;
}

Kaydırmanız başka bir şeyden kaynaklanıyorsa (klavye açılması veya klavye kapanması gibi), bir saldırı ile olayı tespit etmeniz gerekecek gibi görünüyor çünkü scrollViewDidEndScrollingAnimationyararlı da değil.

Bir örneği Sayfalandırılmış kaydırma görünümünde:

Çünkü sanırım Apple bir hızlanma eğrisi uyguluyor, scrollViewDidEndDeceleratingher sürükleme için çağrılıyor yani scrollViewDidEndDraggingbu durumda kullanmaya gerek yok .


Sayfalara ayrılmış kaydırma görünümü durumunuzun bir sorunu var: Kaydırmayı tam olarak sayfa son konumunda bırakırsanız (kaydırma gerekmediğinde), scrollViewDidEndDeceleratingçağrılmayacaktır
Shadow Of

20

Bence scrollViewDidEndDecelerating, istediğin şey. UIScrollViewDelegates isteğe bağlı yöntemi:

- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView

Temsilciye, kaydırma görünümünün kaydırma hareketini yavaşlatmayı sona erdirdiğini söyler.

UIScrollViewDelegate belgeleri


Ben de aynı cevabı verdim. :)
Suvesh Pratapa

Doh. Bir şekilde şifreli olarak adlandırıldı ama tam olarak aradığım şey buydu. Belgeleri daha iyi okumalıydım. Uzun bir gün oldu ...
Michael Gaylord

Bu tek astar sorunumu çözdü. Benim için hızlı düzeltme! Teşekkür ederim.
Dody Rachmat Wicaksono

20

Bu, diğer yanıtların bazılarında açıklanmıştır, ancak burada (kod olarak) kaydırma bittiğinde bazı işlemleri nasıl birleştireceğiniz scrollViewDidEndDeceleratingve scrollViewDidEndDragging:willDecelerategerçekleştireceğiniz açıklanmıştır:

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

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

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


5

Rx ile ilgileniyorsanız, UIScrollView'ı şu şekilde genişletebilirsiniz:

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

bu, sadece bunu yapmanıza izin verecek:

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

Bu hem sürüklemeyi hem de yavaşlamayı hesaba katacaktır.


Tam olarak aradığım buydu. Teşekkürler!
nomnom

4

Birinin ihtiyacı varsa işte Swift'de Ashley Smart yanıtı

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

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

3

Ashley Smart'ın cevabını denedim ve harika çalıştı. İşte sadece scrollViewDidScroll kullanarak başka bir fikir

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

Sadece iki sayfam vardı, bu yüzden benim için çalıştı. Bununla birlikte, birden fazla sayfanız varsa, sorunlu olabilir (mevcut ofsetin genişliğin katı olup olmadığını kontrol edebilirsiniz, ancak bu durumda kullanıcının 2. sayfada mı durduğunu yoksa 3. sayfaya mı yoksa Daha)


3

Kabul edilen cevabın hızlı versiyonu:

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
}

2

Uygulama genelinde kaydırma bittiğinde bunu algılamak için yeni bir çözüm geliştirildi: https://gist.github.com/k06a/731654e3168277fb1fd0e64abc7d899e

Runloop modlarındaki değişiklikleri izleme fikrine dayanmaktadır. Ve kaydırdıktan en az 0,2 saniye sonra blokları gerçekleştirin.

Bu, çalışma döngüsü modlarındaki değişiklikleri izlemek için temel fikirdir iOS10 +:

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

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

Ve iOS2 + gibi düşük dağıtım hedefleri için çözüm:

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

@IsaacCarolWeisberg, düzgün kaydırmanın düzgün olması için bitene kadar ağır işlemleri erteleyebilirsiniz.
k06a

ağır operasyonlar diyorsunuz ... küresel eşzamanlı gönderim kuyruklarında yürüttüğüm operasyonlar muhtemelen
Isaac Carol Weisberg

1

Özetlemek için (ve yeni başlayanlar için). O kadar acı verici değil. Sadece protokolü ekleyin, ardından algılama için ihtiyacınız olan işlevleri ekleyin.

UIScrolView içeren görünümde (sınıf), protokolü ekleyin, ardından buradan tüm işlevleri görünümünüze (sınıf) ekleyin.

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

Dokunma ve sürükleme eylemleriyle ilgili bir durum yaşadım ve sürüklemenin scrollViewDidEndDecelerating'ı çağırdığını öğrendim

Ve kod ([_scrollView setContentOffset: contentOffset animated: YES];) ile manuel değişim ofseti, scrollViewDidEndScrollingAnimation'ı çağırıyordu.

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

Bir alternatif scrollViewWillEndDragging:withVelocity:targetContentOffset, kullanıcı parmağı her kaldırdığında çağrılan ve kaydırmanın duracağı hedef içerik ofsetini içeren kullanım olabilir. Bu içerik ofsetinin kullanılması scrollViewDidScroll:, kaydırma görünümünün kaydırmayı ne zaman durdurduğunu doğru şekilde tanımlar.

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 bir temsilci yöntemine sahiptir

- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView

Temsilci yöntemine aşağıdaki kod satırlarını ekleyin

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

UIScrollViewDelegateKaydırma gerçekten bittiğinde tespit etmek (veya daha iyisi 'tahmin' demek) için kullanılabilecek bir yöntem vardır:

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

nın-nin UIScrollViewDelegate hangi gerçekten bittikten kaydırmak zaman ( 'tahmin' demek veya daha iyi) tespit etmek için kullanılabilir.

Benim durumumda, aşağıdaki gibi yatay kaydırma ile kullandım ( Swift 3'te ):

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 mantığını kullanıyor ve Swift 4.0 ve üstüne dönüştürülüyor.

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

Yukarıdaki mantık, kullanıcının tablo görünümünden kaydırması gibi sorunları çözer. Mantık olmadan, tablo görünümünden çıktığınızda didEndçağrılacak, ancak hiçbir şey çalıştırmayacaktır. Şu anda 2020 yılında kullanıyor.


0

Bazı eski iOS sürümlerinde (iOS 9, 10 gibi), scrollViewDidEndDeceleratingscrollView dokunarak aniden durdurulursa tetiklenmez.

Ancak mevcut sürümde (iOS 13) scrollViewDidEndDeceleratingkesin olarak tetiklenecek (Bildiğim kadarıyla).

Dolayısıyla, Uygulamanız daha önceki sürümleri de hedeflediyse, Ashley Smart'ın bahsettiği gibi bir geçici çözüme ihtiyacınız olabilir veya aşağıdakini yapabilirsiniz.


    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
    }

Açıklama

UIScrollView üç şekilde
durdurulacaktır : - hızlıca kaydırılır ve kendi kendine
durdurulur - parmak dokunuşuyla hızlıca kaydırılır ve durdurulur (Acil durum freni gibi)
- yavaşça kaydırılır ve durdurulur

Birincisi scrollViewDidEndDeceleratingve diğer benzer yöntemlerle tespit edilebilirken diğer ikisi tespit edilemez.

Neyse ki, UIScrollView"// 1" ve "// 2" ile yorumlanan iki satırda kullanılan onları tanımlamak için kullanabileceğimiz üç duruma sahiptir.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.