¿Cómo puedo forzar la detención del desplazamiento mediante programación en un UIScrollView?

80

Nota: La respuesta dada aquí no funciona para mí.

Tengo un UIScrollView (no una vista de tabla, solo algo personalizado), y cuando el usuario realiza ciertas acciones, quiero eliminar cualquier desplazamiento (arrastre o desaceleración) dentro de la vista. He intentado hacer, por ejemplo, esto:

[scrollView scrollRectToVisible:CGRectInset([scrollView bounds], 10, 10) animated:NO];

en la teoría de que, dado un rect que ya se sabe visible, el desplazamiento simplemente se detendrá donde está, pero resulta que esto no tiene ningún efecto; aparentemente, la vista de desplazamiento ve que el rect dado está dentro de los límites y toma ninguna acción. Yo puedo buscar el rollo de parada, si me dan un rect que es definitivamente fuera de los límites actualmente visibles, pero dentro de la contentSize de la vista. Esto parece detener la vista como se esperaba ... pero también hace que salte a otra ubicación. Probablemente podría jugar un poco en los márgenes para que esto funcione razonablemente bien, pero ¿alguien conoce una forma limpia de detener una vista de desplazamiento que está haciendo lo suyo?

Gracias.

Ben Zotto
fuente

Respuestas:

108

Jugué un poco con tu solución original y parece que funciona bien. Creo que casi lo tienes, pero solo estabas compensando el rect que usaste demasiado y olvidaste que puedes desplazar el rect directamente hacia el rect original.

La solución generalizada para cualquier acción de desplazamiento es la siguiente:

- (void)killScroll 
{
    CGPoint offset = scrollView.contentOffset;
    offset.x -= 1.0;
    offset.y -= 1.0;
    [scrollView setContentOffset:offset animated:NO];
    offset.x += 1.0;
    offset.y += 1.0;
    [scrollView setContentOffset:offset animated:NO];
}

[Editar] A partir de iOS 4.3 (y posiblemente anterior), esto también parece funcionar

- (void)killScroll 
{
    CGPoint offset = scrollView.contentOffset;
    [scrollView setContentOffset:offset animated:NO];
}
David Liu
fuente
1
David: genial, gracias. Me había dado vueltas en esta dirección, pero terminé con cálculos menos triviales sobre elegir un rectángulo 1x1 justo fuera de los límites del desplazamiento. Tomando su sugerencia de compensar y luego restaurar inmediatamente (lo que, francamente, parece aprovechar un comportamiento no publicado en el que las llamadas sucesivas en una ejecución de evento realmente funcionan aunque el "resultado" debería ser una operación no operativa), funciona bien. Voy a editar su respuesta anterior para incluir la solución generalizada que debería funcionar para cualquier dirección de desplazamiento. ¡Gracias!
Ben Zotto
(Espero que no le importen las ediciones, quería aclarar para los viajeros posteriores. ¡Gracias por su respuesta!)
Ben Zotto
se ve muy bien ... por cierto, ¿cómo regresas a donde estabas? ¿Guardas el desplazamiento antes de matarlo y luego vuelves allí?
Lior Frenkel
2
En iOS 7, cuando scrollView se está desacelerando, solo funciona la solución superior (la solución + = 1, - = 1).
Sahil
1
Esta solución se puede mejorar. Use + = 0.1 en lugar de + = 1, por lo que una sola llamada de setContentOffsetserá suficiente. La vista de desplazamiento redondeará el desplazamiento de contenido automáticamente.
Kelin
91

La respuesta genérica es, ¡eso no[scrollView setContentOffset:offset animated:NO] es lo mismo que [scrollView setContentOffset:offset]!

  • [scrollView setContentOffset:offset animated:NO] realmente detiene cualquier animación en ejecución.
  • [scrollView setContentOffset:offset] no detiene ninguna animación en ejecución.
  • Lo mismo para scrollView.contentOffset = offset: no detiene ninguna animación en ejecución.

Eso no está documentado en ninguna parte, pero ese es el comportamiento probado en iOS 6.1 y iOS 7.1, probablemente también antes.

Entonces, la solución para detener una animación / desaceleración en ejecución es tan simple como eso:

[scrollView setContentOffset:scrollView.contentOffset animated:NO];

Básicamente lo que dijo David Liu en su respuesta editada. Pero quería dejar en claro que estas dos API NO son iguales.

Swift 3:

scrollView.setContentOffset(scrollView.contentOffset, animated:false)
calimarkus
fuente
11
Bien hecho en la explicación a los "buscadores de razones" entre nosotros ... (:
Aviel Gross
3
Sin embargo, esto me pone un poco triste.
DCMaxxx
2
No entiendo por qué estas cosas no están documentadas ... urggggghh
user1105951
Aún más interesante en (al menos) iOS 11.3 y 11.4 es: si el contentOffset se anima a, por ejemplo, (300, 0), entonces se asigna "contentOffset = (300, 0)" mientras la animación aún se ejecuta, el contentOffset puede restablecerse a (0, 0)! ¡Esto debe ser un insecto!
Ene
68

Para mí, la respuesta aceptada de David Lui anterior no funcionó para mí. Esto es lo que terminé haciendo:

- (void)killScroll {
    self.scrollView.scrollEnabled = NO;
    self.scrollView.scrollEnabled = YES;
}

Por lo que vale, estoy usando el simulador de iPhone iOS 6.0.

TPoschel
fuente
2
+1 como dijiste, el otro tampoco funcionó para mí, ¡pero esta solución funcionó muy bien!
brenjt
1
Impresionante, funciona perfectamente supportedInterfaceOrientationspara evitar que los eventos de desplazamiento continúen si el usuario gira el dispositivo mientras se desplaza , lo que a veces es un desastre según lo que esté haciendo.
cprcrack
21

Esto es lo que hago para mis vistas de desplazamiento y todas las demás subclases relacionadas:

- (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset
 {
    *targetContentOffset = scrollView.contentOffset;
 }

Este los conjuntos targetContentOffseta la scrollView's de desplazamiento de corriente, con lo que el desplazamiento se detenga porque se ha alcanzado el objetivo. De hecho, tiene sentido utilizar un método cuyo propósito sea que los usuarios puedan establecer el objetivo contentOffset.

Rápido

func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) {
    targetContentOffset.pointee = scrollView.contentOffset
}
funct7
fuente
este parece el que supuestamente usan los desarrolladores de Apple. porque todos los demás conducen a errores en algún momento. Me sorprende que esta no sea la respuesta aceptada.
OlDor
Para eso sirve este método de delegado. gran respuesta.
Ryan
En mi opinión, esta es la solución más elegante como dice Apple en su documentación: "Tu aplicación puede cambiar el valor del parámetro targetContentOffset para ajustar donde la vista de desplazamiento termina su animación de desplazamiento". Me funcionó muy bien
Macistador
Esta es la respuesta correcta. Nada de lo anterior funcionó para mí
sethu
20

Detén el desplazamiento rápido :

scrollView.setContentOffset(scrollView.contentOffset, animated: false)
budidino
fuente
16

En realidad ... La forma más "moderna" sería ->

scrollview.panGestureRecognizer.enabled = false;
scrollview.panGestureRecognizer.enabled = true;

Esto desactiva el reconocimiento de gestos que es responsable de desplazarse por un momento que matará el toque actual. El usuario tendría que levantar el dedo y volver a bajarlo para comenzar a desplazarse nuevamente.

Editar: En realidad, esto simplemente elimina el arrastre actual del usuario, pero no detiene inmediatamente la desaceleración si la vista de desplazamiento se encuentra en este estado actualmente. Para hacer esto, la edición de respuestas aceptadas es prácticamente la mejor manera xD

[scrollview setContentOffset: scrollview.contentOffset animated:false];
Xatian
fuente
Bueno, yo uso esto en una aplicación mía y hace exactamente lo que pide el OP. ¿Puedes dar un poco más de contexto? ¿Qué no funciona o por qué?
Xatian
Oh lo siento ... creo que sé ... acabo de responder la parte de la pregunta que estaba buscando ... la segunda parte que pasé por alto ... :-) -> editado.
Xatian
No detiene la desaceleración.
Rudolf Adamkovič
5

La forma más limpia será subclasificar UIScrollView y proporcionar su propio método setContentOffset. Esto debería transmitir el mensaje, solo si no ha activado su freezepropiedad booleana.

Al igual que:

BOOL freeze; // and the @property, @synthesize lines..

-(void)setContentOffset:(CGPoint)offset
{
    if ( !freeze ) [super setContentOffset:offset];
}

Luego, para congelar:

scrollView.freeze = YES;
mvds
fuente
1
Gracias. Si bien esto detiene el desplazamiento visible, en realidad no detiene la desaceleración interna. Si activa esta opción y la desactiva de nuevo después de un momento, verá la pausa de desplazamiento, y luego saltará y continuará desacelerando. Entonces, el estado interno de scrollview todavía se está desplazando. Por lo tanto, debe congelarse hasta que sepa que la desaceleración se ha detenido, lo que puede hacer en asociación con un delegado, pero me parece un poco ridículo. Espero algo que realmente elimine la desaceleración inmediatamente (por ejemplo, tenga el efecto de un solo toque en la pantalla).
Ben Zotto
bien, este tipo de problemas suenan familiares por experiencias anteriores ... ¿por qué no eliminar la desaceleración configurando decelerationRate = 1e10;while freeze == YES?
mvds
(sin conocer las matemáticas internas, 10 * UIScrollViewDecelerationRateFast puede ser una opción más inteligente que 1e10)
mvds
¡Idea interesante! Desafortunadamente, este valor parece estar restringido. Lo probé, y no parece haber un valor de esto que haga que la desaceleración se detenga de inmediato. Lo mejor que puedo hacer es hacerlo lento. :)
Ben Zotto
El plan B es deshabilitar el desplazamiento por un breve período; tal vez pueda salirse con la suya, ya que está bloqueando setContentOffset de todos modos, con suerte evitando que los efectos secundarios deshabiliten el desplazamiento.
mvds
4

Esta respuesta funcionó para mí: Desactive la desaceleración de UIScrollView

-(void)scrollViewWillBeginDecelerating:(UIScrollView *)scrollView{
    [scrollView setContentOffset:scrollView.contentOffset animated:YES];
}
Johnny Rockex
fuente
Esto posiblemente podría detener el scrollView durante el rebote
Jakub Truhlář
3

Desactive la interacción del usuario con solo desplazarse. (rápido)

scrollView.isScrollEnabled = false

Desactivar durante la animación de desplazamiento después de arrastrar. (rápido)

var scrollingByVelocity = false

func scrollViewWillBeginDecelerating(_ scrollView: UIScrollView) {
    if !scrollingByVelocity {
        scrollView.setContentOffset(scrollView.contentOffset, animated: false)
    }
}
Brownsoo Han
fuente
0

He probado estos métodos en collectionview:

self.collectionView.collectionViewLayout.finalizeCollectionViewUpdates()

Kalerfu
fuente
0

Esto me funciona en Swift 4.2 :

   func killScroll() {
    self.scrollView.isScrollEnabled = false;
    self.scrollView.isScrollEnabled = true;
}

... como una extensión:

extension UIScrollView {
    func killScroll() {
        self.isScrollEnabled = false;
        self.isScrollEnabled = true;

    }
}
StefanLdhl
fuente
0

Quería deshabilitar el desplazamiento solo cuando una determinada UIView dentro de la vista de desplazamiento es la fuente del toque durante el deslizamiento. Habría requerido bastante refactorización para mover UIView fuera de UIScrollView, ya que teníamos una jerarquía de vista compleja.

Como solución alternativa, agregué un único UIPanGestureRecognizer a la subvista en la que quería evitar el desplazamiento. Este UIPanGestureRecognizercancelsTouchesInView que UIScrollView.

Es un poco como un 'truco', pero es un cambio súper fácil, y si está usando un XIB o Storyboard, todo lo que necesita hacer es arrastrar el gesto de panorámica a la subvista en cuestión.

Daniel Williams
fuente