Hinzufügen eines Pinch-Zooms zu einer UICollectionView
Ich werde den Effekt beschreiben, den ich erzielen möchte, und dann werde ich detailliert beschreiben, wie ich derzeit versuche, dies umzusetzen, und was an seinem Verhalten in der jetzigen Form falsch ist. Ich werde auch einen anderen Ansatz erwähnen, den ich mir angesehen habe, der aber überhaupt nicht funktionieren konnte.
Der relevanteste Code befindet sich am Ende der Frage für den schnellen Zugriff. Du kannstLaden Sie eine Zip-Datei der Quelle herunter oder holen Sie sich das Projekt alsMercurial Repository bei BitBucket. Das Projekt enthält jetzt die Korrekturen aus der folgenden Antwort. Wenn Sie möchten, dass die fehlerhafte Version zunächst bereitgestellt wird, ist sie mit einem Tag versehen"Initial-Buggy-Version"
Das Projekt ist ein minimalistischer Proof of Concept / Spike, um zu bewerten, ob der Effekt realisierbar ist. Es ist also ziemlich leicht und einfach!
Gewünschter EffektDie App zeigt eine große Anzahl einzelner Informationszeilen an, die eine vertikale Tabelle bilden. Die Tabelle kann vom Benutzer vertikal gescrollt werden. Dies ist das Standardverhalten bei aUITableView
, und Sie können a verwendenUICollectionView
zu. Die App muss jedoch auch die Pinch-Skalierung unterstützen. Wenn Sie Zoom auf den Tisch kneifen,alle der Linien sollten sich zusammendrücken. Während Sie sich strecken,alle der Leitungen sollten auseinander ziehen.
In meinem Proof of Concept werden die einzelnen Zellen nicht in der Größe verändert, sondern nur enger zusammen oder weiter auseinander positioniert. Dies ist beabsichtigt: Ich glaube nicht, dass es entscheidend ist, die Machbarkeit der Idee zu überprüfen.
Hier sind Screenshots, die zeigen, wie die aktuelle App verkleinert und vergrößert aussieht:
Aktuelle Implementierung
Ich benutze eineUICollectionView
mit einem BrauchUICollectionViewLayout
Unterklasse. Das Layout positioniert dieUICollectionViewCells
in einer schönen wackeligen Sinuswelle in der Mitte des Bildschirms. JederUICollectionViewCell
ist nur ein Container für eineUILabel
halten dieindexPath
Reihe.
DasUICollectionViewLayout
Die Unterklasse verfügt über einen Parameter zum Festlegen des vertikalen Abstands zwischen jeder Zelle, die sie beschreibtUICollectionView
Wenn Sie dies einstellen, kann der Tisch wie gewünscht zusammengedrückt oder vertikal gedehnt werden.
MeineUICollectionViewController
Unterklasse hat aUIPinchGestureRecognizer
. Wenn der Erkenner Skalenänderungen erkennt, wird der vertikale Zellenabstand in derUICollectionView
Das Layout wird entsprechend geändert.
Ohne weitere Überlegungen würde die Skalierung eher vom oberen Rand des Inhalts als von der Mitte der Berührungsgeste aus erfolgen. DasUICollectionView
'scontentOffset
Die Eigenschaft wird während des Einklemmens angepasst, um diese Funktion bereitzustellen.
Der Gestenerkenner muss auch die beim Kneifen auftretenden Bewegungen berücksichtigen. Dies geschieht auch durch Ändern derUICollectionView
'scontentOffset
. Ein zusätzlicher Code ermöglicht, dass sich der Mittelpunkt der Berührungsgeste ändert, wenn der Geste Finger hinzugefügt oder daraus entfernt werden.
Beachten Sie, dassUICollectionView
, seinUIScrollView
Unterklasse, hat seine eigeneUIPanGestureRecognizer
die mit dem interagiertUIPinchGestureRecogniser
von mir hinzugefügt. Ich bin nicht sicher, ob dies ein Problem verursacht oder nicht.
Ich habe Code hinzugefügt, um das zu deaktivierenUICollectionView
Das Scrollen ist während meiner Prise eingebaut, aber das scheint keinen großen Unterschied zu machen. Ich habe versucht zu verwendengestureRecognizer:shouldRequireFailureOfGestureRecognizer:
zu meinem machenUIPinchGestureRecognizer
scheitern die eingebautenUIPanGestureRecognizer
, aber dies schien stattdessen zu verhindern, dass mein Prisenerkenner überhaupt funktionierte. Ich weiß nicht, ob ich dumm bin oder ein Fehler in iOS.
Wie bereits erwähnt, ist die aktuelleUICollectionViewCell
s werden nicht in der Größe geändert. Sie werden nur neu positioniert. Das ist beabsichtigt. Ich halte es nicht für wichtig, dieses Konzept zu validieren.
Die Arbeitsteile funktionieren ganz gut. Sie können die Tabelle nach oben und unten ziehen. Während eines Ziehvorgangs können Sie einen Finger hinzufügen und eine Prise beginnen, dann einen Finger loslassen und den Ziehvorgang fortsetzen, dann hinzufügen und kneifen usw. Es ist alles ziemlich glatt. Auf einem originalen iPhone 5 unterstützt es reibungsloses Drücken und Schwenken mit> 200 Ansichten auf dem Bildschirm.
Was geht nicht 1Wenn Sie versuchen, ein- und auszudrücken, während der obere oder untere Rand der Ansicht auf dem Bildschirm angezeigt wird, ist alles etwas verrückt.
Beim Scrollen darf die Ansicht so gezogen werden, dass sie über den sichtbaren Inhalt hinausgeht (was ich möchte, da dies das Standardverhalten für eine Liste von Daten unter iOS ist).Bei Maßstabsänderungen wird die Ansicht jedoch zurückgeschnappt, sodass der Inhalt auf den Bildschirm geklemmt wird (ich möchte nicht, dass dies geschieht).Diese beiden streiten sich während der Prise Geste, wodurch der Inhalt heftig auf und ab flackert (was ich definitiv nicht will!).
Was geht nicht 2DasUICollectionView
Wenn Sie beim Scrollen loslassen, wird der Bildlauf in der Standardeinstellung verlangsamt. Wenn Sie außerhalb des Bildlaufs einen Bildlauf durchführen, wird der Inhalt ebenfalls reibungslos zurückgespielt. Diese werden derzeit überhaupt nicht behandelt.
UICollectionView
, seinUIScrollView
sollte ein eingebautes habenUIPinchGestureRecogniser
wenn es richtig eingestellt ist, um das Zoomen zu unterstützen. Ich fragte mich, ob ich das nutzen könnte, anstatt mein eigenes zu habenUIPinchGestureRecogniser
. Ich habe versucht, dies einzurichten, indem ich die Min- und Max-Skalierung festlegte und den Pinch-Handler meines Controllers hinzufügte. Ich verstehe jedoch nicht wirklich, was ich von meiner Implementierung von zurückgeben sollviewForZoomingInScrollView:
, also erstelle ich gerade eine Dummy-Ansicht mit[[UIView alloc] initWithFrame: [[self collectionView] bounds]]
. Dadurch wird die Bildlaufansicht auf eine einzelne Zeile "reduziert", nach der ich nicht gesucht habe!
Dies ist eine lange Frage. Vielen Dank, dass Sie sie gelesen haben. Nochmals vielen Dank, wenn Sie mit einer Antwort helfen können. Es tut mir leid, wenn vieles, was ich gesagt oder hinzugefügt habe, irrelevant ist!
Code für den View Controller// 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