ios uicollectionview pinchzoom uicollectionviewlayout uipinchgesturerecognizer
zip de la fuentedescargar un

ios - Añadiendo zoom de pellizco a un UICollectionView



pinchzoom uicollectionviewlayout (1)

Lo bueno - cómo hacer que funcione

Algunos ajustes menores al código anterior han resuelto lo que no funciona 1 y lo que no funciona 2 en la pregunta.

He agregado las siguientes líneas al método viewDidLoad de mi UICollectionViewController :

[collectionView setMinimumZoomScale: 0.25]; [collectionView setMaximumZoomScale: 4];

También he actualizado el proyecto de ejemplo para que, en lugar de etiquetas de texto, la vista esté formada por pequeños círculos. A medida que se acerca y se aleja, estos son redimensionados. Esto es lo que parece ahora (reducido y ampliado):

Durante un zoom, las vistas de los círculos no se vuelven a dibujar, sino que se interpolan desde su tamaño de zoom previo. El redibujado se pospone hasta que finaliza el zoom. Aquí hay una captura de cómo se ve un zoom de varias veces:

Sería genial tener el rediseño durante el zoom en un hilo de fondo para que los artefactos sean menos perceptibles, pero eso está fuera del alcance de esta pregunta y aún no he trabajado en ello.

Puede encontrar el proyecto completo, con arreglos, en Bit Bucket para que pueda agarrar los archivos allí.

La parte mala - No sé por qué funciona

Tenía la esperanza de que con esta pregunta respondida, tendría mucha más certeza sobre el zoom de UIScrollView . Yo no.

Por lo que he leído sobre UIScrollView, este "arreglo" no debería haber hecho ninguna diferencia y, de todos modos, ya debería haber funcionado.

Se supone que UIScrollView no permite el desplazamiento hasta que le proporcione un delegado que implemente viewForZoomingInScrollView: cual no he hecho.

Introducción

Voy a describir el efecto que quiero lograr, y luego daré detalles sobre cómo estoy tratando de implementar esto y qué está mal con su comportamiento tal como está. También mencionaré otro enfoque que he analizado pero que no pude hacer.

El código más relevante está en línea en la parte inferior de la pregunta para un acceso rápido. Puede descargar un archivo zip de la fuente o obtener el proyecto como un depósito de Mercurial en BitBucket. El proyecto ahora incorpora los arreglos de la respuesta a continuación. Si quiere la versión defectuosa inicialmente provista, está etiquetada con "initial-buggy-version"

El proyecto es una prueba mínima de concepto / pico para evaluar si el efecto es viable, ¡así que es bastante ligero y simple!

Efecto deseado

La aplicación mostrará una gran cantidad de filas discretas de información que forman una tabla vertical. La tabla será desplazable verticalmente por el usuario. Este es un comportamiento estándar con un UITableView , y también puede usar un UICollectionView . Sin embargo, la aplicación también debe admitir el escalado de pellizco. Cuando pellizcas el zoom sobre la mesa, todas las líneas deben aplastarse juntas. A medida que se estira, todas las líneas deben separarse.

En mi prueba de concepto, las celdas individuales no se redimensionan, solo se reposicionan más cerca o más separadas. Esto es intencional: no creo que sea crítico para validar la viabilidad de la idea.

Aquí hay capturas de pantalla que muestran cómo la aplicación actual parece alejarse y acercarse:

Implementación actual

Estoy usando un UICollectionView con una subclase UICollectionViewLayout personalizada. El diseño posiciona las UICollectionViewCells en una agradable onda sinusoidal en el centro de la pantalla. Cada UICollectionViewCell es solo un contenedor para una UILabel contiene la fila indexPath .

La subclase UICollectionViewLayout tiene un parámetro para establecer el espaciado vertical entre cada celda que describe a UICollectionView y su ajuste permite que la tabla se aplaste o se estire verticalmente como se desee.

Mi subclase UICollectionViewController tiene un UIPinchGestureRecognizer . Cuando el reconocedor detecta cambios de escala, el espacio de celdas vertical en el UICollectionView de UICollectionView se modifica en consecuencia.

Sin más consideraciones, la escala se produciría desde la parte superior del contenido, en lugar de sobre el centro del gesto táctil. La propiedad contentOffset se ajusta durante la compresión para proporcionar esta característica.

El reconocedor de gestos también necesita acomodar los arrastres que ocurren al pellizcar. Esto también se maneja cambiando el UICollectionView de contentOffset . Algunos códigos adicionales permiten cambiar el punto central del gesto táctil a medida que los dedos se agregan o eliminan del gesto.

Tenga en cuenta que UICollectionView , al ser una subclase de UIScrollView , tiene su propio UIPanGestureRecognizer que interactúa con el UIPinchGestureRecogniser agregado por mí. No estoy seguro si esto está causando un problema o no.

He agregado código para desactivar el UICollectionView incorporado de UICollectionView durante mi gesto de pellizco, pero esto no parece marcar la diferencia. Traté de usar gestureRecognizer:shouldRequireFailureOfGestureRecognizer: para hacer que mi UIPinchGestureRecognizer fallara el built in UIPanGestureRecognizer , pero esto pareció detener mi funcionamiento del reconocedor pinch. No sé si este soy yo siendo estúpido, o un error en iOS.

Como se mencionó anteriormente, las UICollectionViewCell actuales no se redimensionan. Sólo se reposicionan. Esto es intencional. No creo que sea importante para validar este concepto.

Que funciona

Los bits de trabajo funcionan bastante bien. Puedes arrastrar la tabla hacia arriba y hacia abajo. Durante un arrastre, puede agregar un dedo e iniciar un pellizco, luego suelte un dedo y continúe arrastrando, luego agregue y pellizque, etc. Todo es bastante suave. En un iPhone 5 original, soporta suavemente pellizcar y desplazar con> 200 vistas en pantalla.

Lo que no funciona 1

Si intentas pellizcar hacia adentro y hacia afuera cuando la parte superior o inferior de la vista está en la pantalla, todo se vuelve un poco loco.

  • En los pergaminos, la vista puede arrastrarse para que se arrastre más allá del contenido visible (lo que quiero, ya que es el comportamiento estándar de una lista de datos en iOS).
  • Sin embargo, en los cambios de escala, la vista se ajusta para que el contenido quede fijo en la pantalla (no quiero que esto suceda).

Estos dos luchan entre ellos durante el gesto de pellizco, lo que hace que el contenido oscile violentamente hacia arriba y hacia abajo (¡lo que definitivamente no quiero!).

Lo que no funciona 2

El desplazamiento por defecto de UICollectionView tiene desaceleración si se suelta mientras se desplaza, y también rebota suavemente el contenido cuando se desplaza fuera de él. Estos no se manejan en absoluto actualmente.

  • Si suelta el gesto de pellizco mientras se desplaza, simplemente se detiene.
  • Si se desplaza más allá del contenido con el gesto de pellizco y luego se suelta, permanece donde está y no se recupera. Cuando vuelves a comenzar un desplazamiento, vuelve a saltar el contenido.

Cosas que he intentado pero que no pude poner en práctica

UICollectionView , al ser un UIScrollView debe tener incorporado UIPinchGestureRecogniser si está configurado correctamente para admitir el zoom. Me preguntaba si podría aprovechar esto en lugar de tener mi propio UIPinchGestureRecogniser . Intenté configurar esto configurando escalas mín. Y máx. Y agregando el controlador de pellizco de mi controlador. Sin embargo, realmente no entiendo lo que debería regresar de mi implementación de viewForZoomingInScrollView: así que estoy creando una vista ficticia con [[UIView alloc] initWithFrame: [[self collectionView] bounds]] . Hace que la vista de desplazamiento se "colapse" a una sola línea, ¡que no es lo que estoy buscando!

Finalmente (antes del código)

Esta es una pregunta larga, así que gracias por leerla. Gracias aún más si puedes ayudar con una respuesta. ¡Lo siento si mucho de lo que dije o añadí es irrelevante!

Código para el controlador de vista

// STViewController.m #import "STViewController.h" #import "STDataColumnsCollectionViewLayout.h" #import "STCollectionViewLabelCell.h" @interface STViewController () <UIGestureRecognizerDelegate> @property (nonatomic, assign) CGFloat pinchStartVerticalPeriod; @property (nonatomic, assign) CGFloat pinchNormalisedVerticalPosition; @property (nonatomic, assign) NSInteger pinchTouchCount; -(void) handlePinch: (UIPinchGestureRecognizer *) pinchRecogniser; @end @implementation STViewController -(void) viewDidLoad { [[self collectionView] registerClass: [STCollectionViewLabelCell class] forCellWithReuseIdentifier: [STCollectionViewLabelCell className]]; UICollectionView *const collectionView = [self collectionView]; [collectionView setAllowsSelection: NO]; [_pinchRecogniser addTarget: self action: @selector(handlePinch:)]; [_pinchRecogniser setDelegate: self]; [_pinchRecogniser setCancelsTouchesInView:YES]; [[self view] addGestureRecognizer: _pinchRecogniser]; } #pragma mark - -(NSInteger) collectionView: (UICollectionView *)collectionView numberOfItemsInSection: (NSInteger)section { return 800; } -(UICollectionViewCell*) collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath { STCollectionViewLabelCell *const cell = [[self collectionView] dequeueReusableCellWithReuseIdentifier: [STCollectionViewLabelCell className] forIndexPath: indexPath]; [[cell label] setText: [NSString stringWithFormat: @"%d", [indexPath row]]]; return cell; } #pragma mark - -(BOOL) gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer { return YES; } #pragma mark - -(void) handlePinch: (UIPinchGestureRecognizer *) pinchRecogniser { UICollectionView *const collectionView = [self collectionView]; STDataColumnsCollectionViewLayout *const layout = (STDataColumnsCollectionViewLayout *)[self collectionViewLayout]; if(([pinchRecogniser state] == UIGestureRecognizerStateBegan) || ([pinchRecogniser numberOfTouches] != _pinchTouchCount)) { const CGFloat normalisedY = [pinchRecogniser locationInView: collectionView].y / [layout collectionViewContentSize].height; _pinchNormalisedVerticalPosition = normalisedY; _pinchTouchCount = [pinchRecogniser numberOfTouches]; } switch ([pinchRecogniser state]) { case UIGestureRecognizerStateBegan: { NSLog(@"Began"); _pinchStartVerticalPeriod = [layout verticalPeriod]; [collectionView setScrollEnabled: NO]; break; } case UIGestureRecognizerStateChanged: { NSLog(@"Changed"); STDataColumnsCollectionViewLayout *const layout = (STDataColumnsCollectionViewLayout *)[self collectionViewLayout]; const CGFloat newVerticalPeriod = _pinchStartVerticalPeriod * [pinchRecogniser scale]; [layout setVerticalPeriod: newVerticalPeriod]; [[self collectionViewLayout] invalidateLayout]; const CGPoint dragCenter = [pinchRecogniser locationInView: [collectionView superview]]; const CGFloat currentY = _pinchNormalisedVerticalPosition * [layout collectionViewContentSize].height; [collectionView setContentOffset: CGPointMake(0, currentY - dragCenter.y) animated: NO]; } case UIGestureRecognizerStateEnded: case UIGestureRecognizerStateCancelled: { [collectionView setScrollEnabled: YES]; } default: break; } } @end