Cómo detectar cuando un UIScrollView ha terminado de desplazarse


UIScrollViewDelegate tiene dos métodos de delegado scrollViewDidScroll: y scrollViewDidEndScrollingAnimation: pero ninguno de estos le indica cuándo se ha completado el desplazamiento. scrollViewDidScroll solo le notifica que la vista de desplazamiento se desplazó y no que ha terminado de desplazarse.

El otro método scrollViewDidEndScrollingAnimation solo parece dispararse si mueve programáticamente la vista de desplazamiento, no si el usuario se desplaza.

¿Alguien sabe de scheme para detectar cuando una vista de desplazamiento ha completado el desplazamiento?

Author: Michael Gaylord, 2009-06-14

15 answers

Los métodos que estás buscando son scrollViewDidEndDragging:willDecelerate: y scrollViewDidEndDecelerating:. El primero siempre se llama después de que el usuario levanta el dedo. Si se desplazan lo suficientemente rápido como para resultar en desaceleración, willDecelerate será YES y el segundo método se llamará después de que se complete la desaceleración.

(Desde los documentos de UIScrollViewDelegate.)

 136
Author: Suvesh Pratapa,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-02-15 07:33:17

Las implementaciones 320 son mucho mejores - aquí hay un parche para obtener un inicio/final consistente del scroll.

-(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];
...
}
 161
Author: Ashley Smart,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-08-28 15:27:31

Creo que scrollViewDidEndDecelerating es lo que quieres. Su Uiscrollviewdelegata el método opcional:

- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView

Le dice al delegado que la vista de desplazamiento ha terminado de desacelerar el movimiento de desplazamiento.

UIScrollViewDelegate documentation

 21
Author: texmex5,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-06-14 17:44:28

Para todos los pergaminos relacionados con las interacciones de arrastre, esto será suficiente:

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

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

Ahora, si su desplazamiento se debe a un setContentOffset/scrollRectVisible programático (con animated = SÍ o obviamente sabe cuándo se termina el desplazamiento):

 - (void)scrollViewDidEndScrollingAnimation {
     _isScrolling = NO;
}

Si su desplazamiento se debe a algo más (como apertura o cierre del teclado), parece que tendrá que detectar el evento con un hackeo porque scrollViewDidEndScrollingAnimation tampoco es útil.

El caso de un desplazamiento PAGINADO vista:

Debido a que, supongo, Apple aplica una curva de aceleración, scrollViewDidEndDecelerating recibe una llamada para cada arrastre, por lo que no es necesario usar scrollViewDidEndDragging en este caso.

 19
Author: Aurelien Porte,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-04-23 23:09:59

Esto se ha descrito en algunas de las otras respuestas, pero aquí está (en código) cómo combinar scrollViewDidEndDecelerating y scrollViewDidEndDragging:willDecelerate para realizar alguna operación cuando el desplazamiento haya terminado:

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

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

- (void)stoppedScrolling
{
    // done, do whatever
}
 17
Author: Wayne Burkett,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-01-10 17:34:38

Acabo de encontrar esta pregunta, que es más o menos la misma que hice: ¿Cómo saber exactamente cuándo se ha detenido el desplazamiento de UIScrollView?

Aunque didEndDecelerating funciona cuando se desplaza, la panorámica con la liberación estacionaria no se registra.

Finalmente encontré una solución. didEndDragging tiene un parámetro WillDecelerate, que es false en la situación de liberación estacionaria.

Comprobando para !decelerar en DidEndDragging, combinado con didEndDecelerating, obtienes ambas situaciones que son el final del desplazamiento.

 7
Author: Aberrant,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 11:54:43

He probado la respuesta de Ashley Smart y funcionó como un encanto. Aquí hay otra idea, usando solo scrollViewDidScroll

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

Solo tenía dos páginas, así que funcionó para mí. Sin embargo, si tiene más de una página, podría ser problemático (puede verificar si el desplazamiento actual es un múltiplo del ancho, pero entonces no sabría si el usuario se detuvo en la 2a página o está en camino a la 3a o más)

 3
Author: Ege Akpinar,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-09-23 14:00:52

Versión rápida de la respuesta aceptada:

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
Author: zzzz,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-08-06 14:44:43

Acaba de desarrollar una solución para detectar cuando el desplazamiento finaliza en toda la aplicación: https://gist.github.com/k06a/731654e3168277fb1fd0e64abc7d899e

Se basa en la idea de rastrear los cambios de los modos de runloop. Y realizar bloques al menos después de 0,2 segundos después del desplazamiento.

Esta es la idea central para rastrear los cambios en los modos de runloop de iOS10+:

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

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

Y solución para objetivos de despliegue bajo como 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
Author: k06a,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-03-17 10:57:54

Para recapitular (y para novatos). No es tan doloroso. Simplemente agregue el protocolo, luego agregue las funciones que necesita para la detección.

En la vista (clase) que contiene UIScrolView, agregue el protocolo, luego agregue cualquier función de aquí a su vista (clase).

// --------------------------------
// 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
Author: bob,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-11-16 11:04:32

Tuve un caso de tocar y arrastrar acciones y me enteré de que el arrastre estaba llamando scrollViewDidEndDecelerating

Y el cambio de desplazamiento manual con código ([_scrollView setContentOffset:contentOffset animated:YES];) estaba llamando a 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
Author: Adriana,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-04-04 17:49:32

Si alguien lo necesita, aquí está Ashley Smart answer en 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
Author: Xernox,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-01-02 16:22:24

Una alternativa sería usar scrollViewWillEndDragging:withVelocity:targetContentOffset que se llama cada vez que el usuario levanta el dedo y contiene el desplazamiento de contenido objetivo donde se detendrá el desplazamiento. El uso de este desplazamiento de contenido en scrollViewDidScroll: identifica correctamente cuándo la vista de desplazamiento ha dejado de desplazarse.

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
Author: Wonder Dog,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-10-26 11:33:27

UIScrollView tiene un método delegado

- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView

Agregue las siguientes líneas de código en el método delegado

- (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
Author: Rahul K Rajan,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-08-19 06:25:15

Hay un método de UIScrollViewDelegate que se puede utilizar para detectar (o mejor decir 'predecir') cuando el desplazamiento realmente ha terminado:

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

De UIScrollViewDelegate que se puede utilizar para detectar (o mejor decir 'predecir') cuando el desplazamiento realmente ha terminado.

En mi caso lo utilicé con desplazamiento horizontal de la siguiente manera (en 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
Author: lexpenz,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-03-10 11:44:34