web-dev-qa-db-fra.com

Comment détecter quand UIScrollView a fini de faire défiler

UIScrollViewDelegate a deux méthodes de délégation, scrollViewDidScroll: et scrollViewDidEndScrollingAnimation:, mais aucune de ces méthodes ne vous indique que le défilement est terminé. scrollViewDidScroll vous avertit seulement que la vue de défilement n'a pas fait défiler l'écran.

L'autre méthode scrollViewDidEndScrollingAnimation ne semble se déclencher que si vous déplacez la vue de défilement par programme, pas si l'utilisateur fait défiler.

Est-ce que quelqu'un sait de schéma pour détecter quand une vue de défilement a terminé le défilement?

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

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

- (void)stoppedScrolling {
    // ...
}
140
Suvesh Pratapa

Les implémentations 320 sont tellement meilleures - voici un correctif pour obtenir des débuts/fins cohérents du défilement.

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

Je pense que scrollViewDidEndDecelerating est celui que vous voulez. Sa méthode facultative UIScrollViewDelegates:

- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView

Indique au délégué que la vue de défilement a fini de décélérer le mouvement de défilement. 

Documentation UIScrollViewDelegate

21
texmex5

Pour tous les parchemins liés aux interactions de glissement, cela sera suffisant:

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

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

Maintenant, si votre défilement est dû à un setContentOffset/scrollRectVisible par programme (avec animated = YES ou si vous savez évidemment quand son défilement est terminé):

 - (void)scrollViewDidEndScrollingAnimation {
     _isScrolling = NO;
}

Si votre défilement est dû à quelque chose d'autre (comme l'ouverture ou la fermeture du clavier), il semble que vous deviez détecter l'événement avec un hack, car scrollViewDidEndScrollingAnimation n'est pas utile non plus.

Le cas d'une vuePAGINÉEscroll:

Parce que, je suppose, Apple applique une courbe d’accélération, scrollViewDidEndDecelerating se fait appeler à chaque glissement, il n’est donc pas nécessaire d’utiliser scrollViewDidEndDragging dans ce cas.

20
Aurelien Porte

Cela a été décrit dans certaines des autres réponses, mais voici (dans le code) comment combiner scrollViewDidEndDecelerating et scrollViewDidEndDragging:willDecelerate pour effectuer certaines opérations lorsque le défilement est terminé:

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

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

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

Je viens juste de trouver cette question, qui est à peu près la même chose que celle que j’avais posée: Comment savoir exactement quand le défilement d’UIScrollView s’est arrêté?

Bien que didEndDecelerating fonctionne lors du défilement, le panoramique avec la libération stationnaire ne s’enregistre pas.

J'ai finalement trouvé une solution. didEndDragging a un paramètre WillDecelerate, qui est faux dans la situation de publication stationnaire.

En cochant! Déceler dans DidEndDragging, en combinaison avec didEndDecelerating, vous obtenez les deux situations qui constituent la fin du défilement.

7
Aberrant

Version rapide de la réponse acceptée: 

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
zzzz

J'ai essayé la réponse d'Ashley Smart et cela a fonctionné à merveille. Voici une autre idée, en utilisant uniquement scrollViewDidScroll

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

Je viens d'avoir deux pages alors ça a fonctionné pour moi. Cependant, si vous avez plus d'une page, cela peut poser problème (vous pouvez vérifier si le décalage actuel est un multiple de la largeur mais vous ne saurez pas si l'utilisateur s'est arrêté à la 2e page ou s'il est en route pour la 3e ou plus)

3
Ege Akpinar
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
}
2
Umair

Vient de développer une solution pour détecter le défilement terminé dans l’ensemble de l’application: https://Gist.github.com/k06a/731654e3168277fb1fd0e64abc7d899e

Il est basé sur l'idée de suivre les modifications des modes runloop. Et effectuez des blocs au moins 0,2 seconde après le défilement.

C'est l'idée de base pour suivre les modifications des modes de cycle d'exécution iOS10 +:

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

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

Et solution pour les cibles à faible déploiement comme 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 ]];
}
2
k06a

J'ai eu un cas de taper et de glisser des actions et j'ai découvert que le glisser appelait scrollViewDidEndDecelerating

Et le changement a été compensé manuellement avec le code ([_scrollView setContentOffset: contentOffset animé: YES];) appelait 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
Adriana

Si quelqu'un a besoin d'aide, voici la réponse de Ashley Smart dans 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)
    ...
}
1
Xernox

Pour récapituler (et pour les débutants). Ce n'est pas si douloureux. Ajoutez simplement le protocole, puis ajoutez les fonctions dont vous avez besoin pour la détection.

Dans la vue (classe) contenant UIScrolView, ajoutez le protocole, puis ajoutez les fonctions d’ici à votre vue (classe).

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

Une alternative serait d'utiliser scrollViewWillEndDragging:withVelocity:targetContentOffset qui est appelé chaque fois que l'utilisateur lève le doigt et contient le décalage du contenu cible à l'endroit où le défilement s'arrête. L'utilisation de ce contenu dans scrollViewDidScroll: identifie correctement le moment où l'affichage de défilement a cessé de défiler.

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")
    }
1
Dog

Il existe une méthode de UIScrollViewDelegate qui peut être utilisée pour détecter (ou mieux dire 'prédire') lorsque le défilement est vraiment terminé:

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

de UIScrollViewDelegate qui peut être utilisé pour détecter (ou mieux dire 'prédire') lorsque le défilement est vraiment terminé.

Dans mon cas, je l'ai utilisé avec un défilement horizontal comme suit (in 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
lexpenz

Si vous aimez Rx, vous pouvez étendre UIScrollView comme ceci:

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

ce qui vous permettra de faire comme ceci:

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

Cela tiendra compte à la fois de la traînée et de la décélération.

0
Zappel

UIScrollview a une méthode déléguée

- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView

Ajoutez les lignes de code ci-dessous dans la méthode delegate

- (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
Rahul K Rajan