Adición de zoom de pellizco a una UICollectionView


Intro

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

El código más relevante está en línea en la parte inferior de la pregunta para un acceso rápido. Puedes Descargar un zip de la fuente o obtener el proyecto como un Mercurial Repositorio en BitBucket. El proyecto ahora incorpora las correcciones de la respuesta a continuación. Si desea que la versión rota provista inicialmente, se etiqueta con "initial-buggy-version"

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

Efecto deseado

La Aplicación mostrará un gran número de filas discretas de información que forman una tabla vertical. La mesa será ser desplazable verticalmente por el usuario. Este es el 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. Al pellizcar zoom en la tabla, todas las de las líneas deben aplastarse juntas. A medida que se estira, todas las de las líneas deben separarse.

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

Aquí están las capturas de pantalla que muestran cómo la aplicación actual se ve alejada y ampliada:

Imagen ampliadaImagen alejada

Aplicación actual

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

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

Mi subclase UICollectionViewController tiene un UIPinchGestureRecognizer. Cuando el reconocedor detecta cambios de escala, el espaciado vertical de celdas en el diseño de UICollectionView se cambia en consecuencia.

Sin más consideración, el escalado se produciría desde la parte superior del contenido, en lugar del centro del gesto táctil. La propiedad UICollectionView's contentOffset se ajusta durante el pellizco para proporcionar esto función.

El reconocedor de gestos también necesita acomodar los arrastres que ocurren mientras se pellizca. Esto también se maneja cambiando los UICollectionView's contentOffset. Algún código adicional permite que el punto central del gesto táctil cambie a medida que se agregan / eliminan los dedos del gesto.

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

He añadido código a desactivar el UICollectionView's construido en el desplazamiento durante mi gesto de pellizco, pero esto no parece hacer mucha diferencia. Traté de usar gestureRecognizer:shouldRequireFailureOfGestureRecognizer: para hacer que mi UIPinchGestureRecognizer fallara con el built in UIPanGestureRecognizer, pero esto en su lugar pareció detener el trabajo de mi reconocedor de pellizco. No se si este soy yo siendo estúpido, o un error en iOS.

Como se mencionó anteriormente, las UICollectionViewCellactuales no se redimensionan. Solo están reposicionados. Esto es intencional. No creo que sea importante para validar este concepto.

¿Qué Obras

Los bits de trabajo funcionan bastante bien. Puede arrastrar la mesa hacia arriba y hacia abajo. Durante un arrastre puede agregar un dedo y comenzar un pellizco, luego soltar un dedo y continuar el arrastre, luego agregar y pellizcar, etc. Todo es muy suave. En un iPhone 5 original, soporta suavemente pellizcar y panear con > 200 vistas en pantalla.

Lo que no funciona 1

Si intenta pellizcar dentro y fuera cuando la parte superior o inferior de la vista está en la pantalla, todo va un poco loco.

  • En los pergaminos, se permite arrastrar la vista para que se tire más allá del contenido visible (lo que quiero, ya que es el comportamiento estándar para una lista de datos en iOS).
  • En los cambios de escala, sin embargo, la vista se retuerce para que el contenido se sujete a la pantalla (no quiero que esto suceda).

Estos dos se pelean entre sí durante el gesto de pellizco, lo que hace que el contenido parpadee violentamente hacia arriba y hacia abajo (lo que definitivamente no hago ¡quiero!).

Lo que no funciona 2

El desplazamiento predeterminado de UICollectionView tiene desaceleración si lo sueltas mientras te desplazas, y también rebota suavemente el contenido cuando te desplazas fuera de él. Estos no se manejan en absoluto actualmente.

  • Si suelta el gesto de pellizco mientras se desplaza, simplemente se detiene.
  • Si te desplazas más allá del contenido con el gesto de pellizco y luego lo sueltas, se queda donde está y no se recupera. Cuando empieces un pergamino de nuevo salta el contenido de nuevo.

Cosas que he intentado pero no pude llegar a trabajar

UICollectionView, ser un UIScrollView debería tener un built in UIPinchGestureRecogniser si está configurado correctamente para soportar el zoom. Me preguntaba si podría aprovechar esto en lugar de tener mi propio UIPinchGestureRecogniser. Traté de configurar esto estableciendo escalas min y max, 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 solo estoy creando una vista ficticia con [[UIView alloc] initWithFrame: [[self collectionView] bounds]]. Hace que la vista de desplazamiento "colapse" en una sola línea, ¡que no es lo que busco!

Finalmente (Antes del código)

Esta es una pregunta larga, así que gracias por leerla. Gracias aún más si usted puede ayudar con una respuesta. Lo siento si mucho de lo que he dicho o añadido 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
Author: Benjohn, 2014-01-03

1 answers

La parte buena-cómo hacer que funcione

Algunos ajustes muy menores al código anterior han resuelto Lo que no funciona 1 & Lo que no Funciona 2 en la pregunta.

He añadido las siguientes líneas en el 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é hecha de pequeños círculos. A medida que se acerca y se aleja, estos se redimensionan. Así es como se ve ahora (zoom out y zoom in):

Imagen ampliadaImagen ampliada

Durante un zoom, las vistas de los círculos no se vuelven a dibujar, sino que se interpolan a partir de su tamaño previo al zoom. El nuevo dibujo se pospone hasta que finalice el zoom. Aquí hay una captura de cómo se ve después de un zoom de varias veces:

Durante el zoom

Sería genial que el redibujado durante el zoom ocurra en un hilo de fondo para que los artefactos sean menos notorios, pero eso está bien fuera del alcance de esta pregunta y no he trabajado en ello sin embargo, tampoco.

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

La Parte Mala-No se por qué funciona

Esperaba que con esta pregunta respondida, tendría mucha certeza sobre UIScrollView el zoom. Yo no.

Por lo que he leído sobre UIScrollView, esta "corrección" no debería haber hecho ninguna diferencia y ya debería haber funcionado en primer lugar de todos modos.

A UIScrollView no se supone que habilite el desplazamiento hasta que le dé un delegado que implemente viewForZoomingInScrollView:, lo cual no he hecho.

 16
Author: Benjohn,
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-08 13:05:21