Añadiendo zoom de pellizco a un UICollectionView

Introducción

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

El código más relevante está en línea en la parte inferior de la pregunta para un acceso rápido. Usted puedeDescarga un zip de la fuente. o conseguir el proyecto comoRepositorio mercurial en BitBucket. El proyecto ahora incorpora los arreglos de la respuesta a continuación. Si desea que la versión rota se proporcione inicialmente, está etiquetada con"versión inicial de buggy"

El proyecto es una prueba mínima de concepto / aumento para evaluar si el efecto es viable, ¡por lo 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 el comportamiento estándar con unaUITableView, y puedes usar unUICollectionView también. Sin embargo, la aplicación también debe ser compatible con la escala de pellizco. Cuando pellizcas el zoom sobre la mesa,todos de las líneas deben aplastarse juntas. Mientras te estiras,todos de las líneas deben separarse.

En mi prueba de concepto, las celdas individuales no se redimensionan, simplemente se reposicionan juntas o separadas. Esto es intencional: no creo que sea fundamental para validar la viabilidad de la idea.

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

Implementación actual

Estoy usando unUICollectionView con una costumbreUICollectionViewLayout subclase El diseño posiciona elUICollectionViewCells en una agradable onda sinusoidal tambaleante en el centro de la pantalla. CadaUICollectionViewCell es solo un contenedor para unUILabel sosteniendo elindexPath fila.

losUICollectionViewLayout La subclase tiene un parámetro para establecer el espaciado vertical entre cada celda que describe a laUICollectionView y este ajuste permite que la mesa se aplaste o se estire verticalmente como se desee.

MiUICollectionViewController subclase tiene unaUIPinchGestureRecognizer. Cuando el reconocedor detecta cambios de escala, el espaciado vertical de las celdas en elUICollectionViewLa disposición de 'se cambia en consecuencia.

Sin más consideraciones, la escala se produciría desde la parte superior del contenido, en lugar del centro del gesto táctil. losUICollectionViewescontentOffset La propiedad se ajusta durante el pellizco 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 elUICollectionViewescontentOffset. 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 queUICollectionView, ser unUIScrollView subclase, tiene su propioUIPanGestureRecognizer que interactúa con elUIPinchGestureRecogniser agregado por mi No estoy seguro de si esto está causando un problema o no.

He añadido código para deshabilitar elUICollectionViewEstá integrado en el desplazamiento durante mi gesto de pellizco, pero esto no parece hacer mucha diferencia. Traté de usargestureRecognizer:shouldRequireFailureOfGestureRecognizer: para hacer miUIPinchGestureRecognizer fallar el construido enUIPanGestureRecognizer, pero esto en cambio pareció detener el funcionamiento del reconocedor de pellizcos. No sé si este soy yo siendo estúpido, o un error en iOS.

Como se mencionó anteriormente, el actualUICollectionViewCells no son redimensionados. 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 vuelve a ajustar para que el contenido se fije en la pantalla (no quiero que esto ocurra).

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

Lo que no funciona 2

losUICollectionViewEl desplazamiento predeterminado 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 probado pero no pude ir a trabajar.

UICollectionView, ser unUIScrollView debe tener un construido enUIPinchGestureRecogniser si está configurado correctamente para soportar el zoom. Me pregunté si podría aprovechar esto en lugar de tener mi propiaUIPinchGestureRecogniser. Intenté configurar esto configurando las 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 deviewForZoomingInScrollView:, así que estoy creando una vista ficticia con[[UIView alloc] initWithFrame: [[self collectionView] bounds]]. Hace que la vista de desplazamiento se "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 puedes ayudar con una respuesta. Lo siento si mucho de lo que he dicho o agregado 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

Respuestas a la pregunta(1)

Su respuesta a la pregunta