Dodawanie powiększenia pinch do UICollectionView

Intro

Zamierzam opisać efekt, który chcę osiągnąć, a następnie podam szczegóły dotyczące tego, jak obecnie próbuję go wdrożyć i co jest nie tak z jego zachowaniem. Wspomnę również o innym podejściu, na które patrzyłem, ale w ogóle nie mogłem wykonać pracy.

Najbardziej odpowiedni kod znajduje się w dolnej części pytania w celu szybkiego dostępu. MożeszPobierz zamek źródłowy lub pobierz projekt jakoRepozytorium Mercurial w BitBucket. Projekt zawiera teraz poprawki z odpowiedzi poniżej. Jeśli chcesz, aby pierwotnie udostępniona została zepsuta wersja, jest ona oznaczona tagiem„wersja początkowa z błędem”

Projekt jest minimalnym dowodem na koncepcję / skok do oceny, czy efekt jest opłacalny, więc jest dość lekki i prosty!

Pożądany efekt

Aplikacja wyświetli dużą liczbę dyskretnych wierszy informacji, które tworzą pionową tabelę. Tabela będzie przewijana pionowo przez użytkownika. Jest to standardowe zachowanie zUITableViewi możesz użyć aUICollectionView zbyt. Jednak aplikacja musi również obsługiwać skalowanie szczypiące. Kiedy ścisniesz powiększenie na stole,wszystko linii powinno się zgnieść razem. Jak się rozciągasz,wszystko linii powinno się rozdzielić.

W moim dowodzie koncepcji poszczególne komórki nie są zmieniane, są po prostu umieszczane bliżej siebie lub dalej od siebie. Jest to celowe: nie wierzę, że ma to kluczowe znaczenie dla potwierdzenia wykonalności pomysłu.

Oto zrzut ekranu pokazujący, jak bieżąca aplikacja wygląda na powiększoną i pomniejszoną:

Aktualne wdrożenie

UżywamUICollectionView ze zwyczajemUICollectionViewLayout podklasa. Układ ustawiaUICollectionViewCells w ładnej, chwiejnej fali sinusoidalnej na środku ekranu. KażdyUICollectionViewCell jest tylko pojemnikiem naUILabel trzymającindexPath rząd.

TheUICollectionViewLayout podklasa ma parametr umożliwiający ustawienie odstępu pionowego między każdą komórką, którą opisujeUICollectionView a dostosowanie to umożliwia zgniatanie lub rozciąganie stołu w zależności od potrzeb.

MójUICollectionViewController podklasa maUIPinchGestureRecognizer. Gdy urządzenie rozpoznające wykryje zmiany skali, pionowy odstęp komórek wUICollectionViewukład strony jest odpowiednio zmieniany.

Bez dalszych rozważań skalowanie wystąpiłoby z góry zawartości, a nie z centrum gestu dotykowego. TheUICollectionViewjestcontentOffset właściwość jest dostosowywana podczas szczypania, aby zapewnić tę funkcję.

Rozpoznawacz gestów musi również uwzględniać opory, które występują podczas ściskania. Jest to również obsługiwane przez zmianęUICollectionViewjestcontentOffset. Dodatkowy kod umożliwia zmianę punktu środkowego gestu dotykowego w miarę dodawania palców do gestu.

Zauważ, żeUICollectionView, będąc aUIScrollView podklasa, ma swoją własnąUIPanGestureRecognizer który współdziała zUIPinchGestureRecogniser dodane przeze mnie. Nie jestem pewien, czy powoduje to problem, czy nie.

Dodałem kod, aby wyłączyćUICollectionViewjest wbudowany w przewijanie podczas mojego gestu szczypania, ale to nie wydaje się mieć większego znaczenia. Próbowałem użyćgestureRecognizer:shouldRequireFailureOfGestureRecognizer: zrobić mojeUIPinchGestureRecognizer zawieść wbudowanegoUIPanGestureRecognizer, ale zamiast tego wydawało się, że w ogóle nie działa mój szczypta. Nie wiem, czy to ja jestem głupi, czy błąd w iOS.

Jak wspomniano wcześniej, bieżąceUICollectionViewCells nie są zmieniane. Zostały po prostu zmienione. To jest zamierzone. Nie sądzę, aby to ważne dla walidacji tej koncepcji.

Co działa

Działające bity działają całkiem dobrze. Możesz przeciągać stół w górę iw dół. Podczas przeciągania możesz dodać palec i rozpocząć szczyptę, a następnie zwolnić palec i kontynuować przeciąganie, a następnie dodać, uszczypnąć itp. Wszystko jest całkiem gładkie. Na oryginalnym telefonie iPhone 5 płynnie obsługuje szczyptę i panoramowanie z> 200 widokami na ekranie.

Co nie działa 1

Jeśli spróbujesz uszczypnąć i zsunąć się, gdy górna lub dolna część widoku jest na ekranie, to wszystko jest nieco szalone.

W przypadku zwojów widok może przeciągać się, tak aby był wyciągany poza widoczną zawartość (czego chcę, ponieważ jest to standardowe zachowanie dla listy danych na iOS).Jednak w przypadku zmiany skali widok jest przyciągany do tyłu, dzięki czemu treść zostaje zaciśnięta na ekranie (nie chcę tego robić).

Te dwie walki walczą ze sobą podczas gestu szczypania, co sprawia, że ​​zawartość gwałtownie migocze w górę iw dół (czego zdecydowanie nie chcę!).

Co nie działa 2

TheUICollectionViewdomyślne przewijanie ma opóźnienie, jeśli puścisz podczas przewijania, a także płynnie odbija zawartość po przewinięciu poza nią. Obecnie nie są one obsługiwane.

Jeśli zwolnisz gest szczypania podczas przewijania, po prostu się zatrzyma.Jeśli przewiniesz poza zawartość gestem uszczypnięcia, a następnie zwolnisz, pozostanie tam, gdzie jest i nie będzie się odbijał. Gdy ponownie rozpoczniesz przewijanie, przeskakuje zawartość z powrotem.Rzeczy, które próbowałem, ale nie mogłem pracować

UICollectionView, będąc aUIScrollView powinien mieć wbudowanyUIPinchGestureRecogniser jeśli jest poprawnie skonfigurowany do obsługi powiększania. Zastanawiałem się, czy udałoby mi się to wykorzystać zamiast mieć własneUIPinchGestureRecogniser. Próbowałem to ustawić, ustawiając skale min i max oraz dodając obsługę szczypania kontrolera. Jednak tak naprawdę nie rozumiem, co powinienem powrócić z mojej implementacjiviewForZoomingInScrollView:, więc tworzę tylko fikcyjny widok[[UIView alloc] initWithFrame: [[self collectionView] bounds]]. Sprawia, że ​​widok przewijania jest zwinięty w pojedynczą linię, co nie jest tym, czego szukam!

Wreszcie (przed kodem)

To długie pytanie, więc dziękuję za przeczytanie. Jeszcze bardziej dziękuję, jeśli możesz pomóc w odpowiedzi. Przepraszam, jeśli wiele z tego, co powiedziałem lub dodałem, jest nieistotne!

Kod kontrolera widoku
//  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

questionAnswers(1)

yourAnswerToTheQuestion