Cómo animar correctamente UIScrollView contentOffset

Tengo la subclass UIScrollView. Su contenido es reutilizable: se usan aproximadamente 4 o 5 vistas para mostrar cientos de elementos (mientras se desplazan objects ocultos reutilizados y saltos a otra position cuando es necesario para verlos)

Lo que necesito: capacidad para desplazar automáticamente mi vista de desplazamiento a cualquier position. Por ejemplo, mi vista de desplazamiento muestra el 4º, 5º y 6º elemento y cuando presiono un button debe desplazarse al 30º elemento. En otras palabras, necesito un comportamiento estándar de UIScrollView.

Esto funciona bien:

[self setContentOffset:CGPointMake(index*elementWidth, 0) animated:YES]; 

pero necesito algo de personalización. Por ejemplo, cambie la duración de la animation, agregue un código para realizar al final de la animation.

Decisión obvia:

 [UIView animateWithDuration:3 delay:0 options:UIViewAnimationOptionBeginFromCurrentState animations:^{ [self setContentOffset:CGPointMake(index*elementWidth, 0)]; } completion:^(BOOL finished) { //some code }]; 

pero tengo algunas acciones conectadas al evento de desplazamiento, y ahora todas están en el bloque de animation y hace que todos los cuadros de la subview se animen también (gracias a algunos elementos reutilizables, todos ellos no se animan como yo quiero)

La pregunta es: ¿Cómo puedo hacer animation personalizada (de hecho necesito la duración personalizada, las acciones al final y la opción BeginFromCurrentState) para el desplazamiento de contenido SIN animar todo el código, conectado al evento scrollViewDidScroll ?

UPD: gracias a la respuesta de Andrew (primera parte) solucioné el problema con la animation dentro de scrollViewDidScroll :

 - (void)scrollViewDidScroll:(UIScrollView *)scrollView{ [UIView performWithoutAnimation:^{ [self refreshTiles]; }]; } 

Pero scrollViewDidScroll debe (para mis propósitos) ejecuta cada marco de animation como lo fue en el caso de

 [self setContentOffset:CGPointMake(index*elementWidth, 0) animated:YES]; 

Sin embargo, ahora se ejecuta solo una vez al comienzo de la animation.

¿Como puedo resolver esto?

¿ scrollViewDidScroll el mismo enfoque, pero con animation deshabilitada en scrollViewDidScroll ?

En iOS 7, puedes intentar envolver tu código en scrollViewDidScroll en

 [UIView performWithoutAnimation:^{ //Your code here }]; 

En las versiones anteriores de iOS, puedes probar:

  [CATransaction begin]; [CATransaction setDisableActions:YES]; //Your code here [CATransaction commit]; 

Actualizar:

Lamentablemente es ahí donde golpeas la parte difícil de todo. setContentOffset: llama al delegado solo una vez, es equivalente a setContentOffset:animated:NO , que de nuevo lo llama solo una vez.

setContentOffset:animated:YES llama al delegado ya que la animation cambia los límites de la vista de desplazamiento y desea eso, pero no desea la animation proporcionada, por lo que la única forma de evitar esto es cambiar gradualmente el contentOffset de la vista de desplazamiento, para que el sistema de animation no salte al valor final, como es el caso en este momento.

Para hacer eso, puedes ver las animaciones de fotogtwigs key, así como para iOS 7:

 [UIView animateKeyframesWithDuration:duration delay:delay options:options animations:^{ [UIView addKeyframeWithRelativeStartTime:0.0 relativeDuration:0.5 animations:^{ [self setContentOffset:CGPointMake(floorf(index/2) * elementWidth, 0)]; }]; [UIView addKeyframeWithRelativeStartTime:0.5 relativeDuration:0.5 animations:^{ [self setContentOffset:CGPointMake(index*elementWidth, 0)]; }]; } completion:^(BOOL finished) { //Completion Block }]; 

Esto le proporcionará dos actualizaciones y, por supuesto, podría usar algunas matemáticas y un ciclo para sumr muchas más con los times adecuados.

En las versiones anteriores de iOS, tendrás que enviarte a CoreAnimation para animaciones de fotogtwigs key, pero básicamente es lo mismo con una syntax un poco diferente.

Método 2: Puede probar el sondeo de la presentadora de la vista de desplazamiento para cualquier cambio con un timer que comience al principio de la animation, ya que, lamentablemente, las properties de la presentadora de presentación no son observables por KVO. O puede usar needsDisplayForKey en una subclass de la capa para recibir una notificación cuando los límites cambian, pero eso requerirá cierto trabajo para configurar y causa el networkingibujado, lo que podría afectar el performance.

Método 3: sería analizar exactamente lo que sucede con el scrollView cuando está animado es SÍ, intente y intercepte la animation que se establece en el scrollview y cambie sus parameters, pero dado que este sería el más hackeable, rompible debido a los cambios de Apple y al método más complicado , No voy a entrar en eso.

Una buena manera de hacerlo es con la biblioteca AnimationEngine . Es una biblioteca muy pequeña: seis files, con tres más si quieres un comportamiento de resorte amortiguado.

Detrás de las escenas usa CADisplayLink para ejecutar su bloque de animation una vez cada fotogtwig. Obtiene una syntax limpia basada en bloques que es fácil de usar, y un montón de funciones de interpolación y relajación que le ahorran time.

Para animar contenido contentOffset :

 startOffset = scrollView.contentOffset; endOffset = .. // Constant speed looks good... const CGFloat kTimelineAnimationSpeed = 300; CGFloat timelineAnimationDuration = fabs(deltaToDesinetworkingX) / kTimelineAnimationSpeed; [INTUAnimationEngine animateWithDuration:timelineAnimationDuration delay:0 easing:INTULinear animations:^(CGFloat progress) { self.videoTimelineView.contentOffset = INTUInterpolateCGPoint(startOffset, endOffset, progress); } completion:^(BOOL finished) { autoscrollEnabled = YES; }];