Adicionando zoom de pitada a um UICollectionView
Vou descrever o efeito que quero alcançar e, em seguida, darei detalhes sobre como estou tentando implementar isso e o que há de errado com o seu comportamento atual. Mencionarei também outra abordagem que observei, mas que não conseguiu dar certo.
O código mais relevante está embutido na parte inferior da pergunta para acesso rápido. Você podeFaça o download de um zip da fonte ou obtenha o projeto como umRepositório Mercurial no BitBucket. O projeto agora incorpora as correções da resposta abaixo. Se você deseja que a versão quebrada seja fornecida inicialmente, ela é marcada com"versão inicial com buggy"
O projeto é uma prova de conceito / pico mínimo para avaliar se o efeito é viável, por isso é bastante leve e simples!
Efeito desejadoO aplicativo exibirá um grande número de linhas discretas de informações que formam uma tabela vertical. A tabela será rolada verticalmente pelo usuário. Esse é um comportamento padrão com umUITableView
e você pode usar umUICollectionView
também. No entanto, o aplicativo também deve suportar o dimensionamento de pitada. Quando você aperta o zoom na mesa,tudo das linhas deve esmagar juntos. Enquanto você estica,tudo das linhas deve se separar.
Na minha prova de conceito, as células individuais não são redimensionadas, elas são apenas reposicionadas mais próximas umas das outras ou mais afastadas. Isso é intencional: não acredito que seja crítico validar a viabilidade da ideia.
Aqui estão as capturas de tela que mostram como o aplicativo atual parece com menos e mais zoom:
Implementação atual
Estou usando umUICollectionView
com um costumeUICollectionViewLayout
subclasse. O layout posiciona oUICollectionViewCells
em uma bela onda senoidal oscilante no meio da tela. CadaUICollectionViewCell
é apenas um contêiner para umUILabel
segurando oindexPath
linha.
oUICollectionViewLayout
subclasse tem um parâmetro para definir o espaçamento vertical entre cada célula que descreve para oUICollectionView
e ajustar isso permite que a mesa seja comprimida ou esticada verticalmente, conforme desejado.
MinhasUICollectionViewController
subclasse tem umUIPinchGestureRecognizer
. Quando o reconhecedor detecta alterações de escala, o espaçamento vertical da célula noUICollectionView
O layout do é alterado de acordo.
Sem uma análise mais aprofundada, a escala ocorreria na parte superior do conteúdo, e não no centro do gesto de toque. oUICollectionView
écontentOffset
A propriedade é ajustada durante a compressão para fornecer esse recurso.
O reconhecedor de gestos também precisa acomodar arrastões que ocorrem durante o beliscão. Isso também é tratado por alterações,UICollectionView
écontentOffset
. Algum código adicional permite que o ponto central do gesto de toque seja alterado conforme os dedos são adicionados / removidos do gesto.
Observe queUICollectionView
, Começar umUIScrollView
subclasse, tem sua própriaUIPanGestureRecognizer
que interage com oUIPinchGestureRecogniser
adicionado por mim. Não tenho certeza se isso está causando um problema ou não.
Eu adicionei código para desativar oUICollectionView
é construído em rolagem durante o meu gesto de pinça, mas isso não parece fazer muita diferença. Eu tentei usargestureRecognizer:shouldRequireFailureOfGestureRecognizer:
fazer o meuUIPinchGestureRecognizer
falhar o construído emUIPanGestureRecognizer
, mas isso pareceu impedir que meu reconhecedor de pitada funcionasse. Não sei se estou sendo idiota ou se há um bug no iOS.
Como mencionado anteriormente, atualize oUICollectionViewCell
s não são redimensionados. Eles são apenas reposicionados. Isso é intencional. Não acho importante validar esse conceito.
Os bits de trabalho funcionam muito bem. Você pode arrastar a tabela para cima e para baixo. Durante um arrasto, você pode adicionar um dedo e iniciar uma pitada, depois solte um dedo e continue o arrasto, depois adicione e aperte, etc. Tudo é bem suave. Em um iPhone 5 original, ele suporta suavemente pitada e movimento com> 200 visualizações na tela.
O que não funciona 1Se você tentar entrar e sair quando a parte superior ou inferior da exibição estiver na tela, tudo ficará um pouco louco.
Nos pergaminhos, a visualização pode arrastar para que seja puxada além do conteúdo visível (o que eu quero, pois é um comportamento padrão para uma lista de dados no iOS).Em mudanças de escala, no entanto, a visualização é retrocedida para que o conteúdo fique preso na tela (não quero que isso aconteça).Esses dois brigam entre si durante o gesto de beliscar, o que faz o conteúdo vibrar violentamente para cima e para baixo (o que eu definitivamente não quero!).
O que não funciona 2oUICollectionView
A rolagem padrão da câmera tem desaceleração se você soltar a rolagem e também devolve o conteúdo suavemente quando você rola para fora dele. No momento, elas não são tratadas.
UICollectionView
, Começar umUIScrollView
deve ter um construído emUIPinchGestureRecogniser
se estiver configurado corretamente para suportar o zoom. Gostaria de saber se eu poderia aproveitar isso em vez de ter o meu próprioUIPinchGestureRecogniser
. Tentei configurar isso configurando as escalas mín e máx e adicionando o manipulador de pitada do meu controlador. No entanto, eu realmente não entendo o que eu deveria retornar da minha implementação doviewForZoomingInScrollView:
, então estou apenas criando uma visualização fictícia com[[UIView alloc] initWithFrame: [[self collectionView] bounds]]
. Faz com que a exibição de rolagem "desmorone" em uma única linha, que não é o que eu estou procurando!
Essa é uma pergunta longa, então obrigado por lê-la. Agradeço ainda mais se puder ajudar com uma resposta. Sinto muito se muito do que eu disse ou adicionei é irrelevante!
Código para o controlador de exibição// 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