Hinzufügen eines Pinch-Zooms zu einer UICollectionView

Intro

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 Effekt

Die 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 derUICollectionViewDas 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 deaktivierenUICollectionViewDas 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 aktuelleUICollectionViewCells 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.

Was funktioniert

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 1

Wenn 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 2

DasUICollectionViewWenn 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.

Wenn Sie die Pinch-Geste während des Bildlaufs loslassen, stoppt sie einfach.Wenn Sie mit der Pinch-Geste über den Inhalt hinaus scrollen und ihn dann loslassen, bleibt er dort, wo er ist, und springt nicht zurück. Wenn Sie dann einen Bildlauf erneut starten, wird der Inhalt zurückgesprungen.Dinge, die ich versucht habe, aber nicht zur Arbeit kommen konnte

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!

Endlich (vor dem Code)

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

Antworten auf die Frage(1)

Ihre Antwort auf die Frage