Добавление пинч-масштабирования в UICollectionView

вступление

Я опишу эффект, которого я хочу достичь, а затем подробно расскажу о том, как я сейчас пытаюсь это реализовать, и что не так с его поведением в его нынешнем виде. Я также упомяну другой подход, на который я смотрел, но не мог сделать работу вообще.

Наиболее релевантный код находится в нижней части вопроса для быстрого доступа. Ты можешьСкачать почтовый индекс источника или получить проект какMercurial Repository в BitBucket. Проект теперь включает в себя исправления из ответа ниже. Если вы хотите, чтобы изначально была сломанная версия, она помечается«Начально-багги-версия»

Проект является минимальным доказательством концепции / всплеска, чтобы оценить, является ли эффект жизнеспособным, поэтому он довольно легкий и простой!

Желаемый эффект

Приложение будет отображать большое количество отдельных строк информации, которые образуют вертикальную таблицу. Таблица будет вертикально прокручиваться пользователем. Это стандартное поведение сUITableViewи вы можете использоватьUICollectionView слишком. Тем не менее, приложение также должно поддерживать масштабирование. Когда вы ущипните зум на столе,все из линий должны сжаться вместе. Пока ты растягиваешься,все из линий должны раздвинуться.

В моем доказательстве концепции размеры отдельных ячеек не меняются, они просто перемещаются ближе друг к другу или дальше друг от друга. Это сделано намеренно: я не верю, что это важно для обоснования осуществимости идеи.

Вот скриншоты, показывающие, как выглядит и масштабируется текущее приложение:

Текущая реализация

Я используюUICollectionView с обычаемUICollectionViewLayout подкласс. Расположение размещаетUICollectionViewCells в хорошей шаткой синусоиде вниз по середине экрана. каждыйUICollectionViewCell это просто контейнер дляUILabel держаindexPath ряд.

UICollectionViewLayout у подкласса есть параметр, чтобы установить вертикальный интервал между каждой ячейкой, которую он описывает,UICollectionView и регулировка этого позволяет прижимать или растягивать стол по желанию.

мойUICollectionViewController подкласс имеетUIPinchGestureRecognizer, Когда распознаватель обнаруживает изменения масштаба, вертикальное расстояние между ячейками вUICollectionViewмакет изменился соответственно.

Без дальнейшего рассмотрения масштабирование будет происходить с верхней части содержимого, а не вокруг центра сенсорного жеста.UICollectionView«scontentOffset свойство настраивается во время сжатия, чтобы обеспечить эту функцию.

Распознаватель жестов также должен приспосабливать перетаскивания, возникающие при зажатии. Это также обрабатывается Changi, нгUICollectionView«scontentOffset, Некоторый дополнительный код позволяет изменять центральную точку сенсорного жеста при добавлении / удалении пальцев из жеста.

Обратите внимание, чтоUICollectionView, бытьUIScrollView подкласс, имеет свойUIPanGestureRecognizer который взаимодействует сUIPinchGestureRecogniser добавил я. Я не уверен, вызывает ли это проблему или нет.

Я добавил код для отключенияUICollectionViewвстроена прокрутка во время моего жеста, но это, кажется, не имеет большого значения. Я пытался использоватьgestureRecognizer:shouldRequireFailureOfGestureRecognizer: сделать мойUIPinchGestureRecognizer подвести встроенныйUIPanGestureRecognizer, но это вместо этого, казалось, остановило мой распознаватель пинча вообще. Я не знаю, глуп ли я, или ошибка в iOS.

Как упоминалось ранее, текущаяUICollectionViewCellРазмеры не изменяются. Они просто переставлены. Это намеренно. Я не думаю, что это важно для подтверждения этой концепции.

Что работает

Рабочие биты работают довольно хорошо. Вы можете перетащить стол вверх и вниз. Во время перетаскивания вы можете добавить палец и начать ущипнуть, затем отпустить палец и продолжить перетаскивание, затем добавить и ущипнуть, и т. Д. Это все довольно гладко. На оригинальном iPhone 5 он плавно поддерживает масштабирование и панорамирование с> 200 видами на экране.

Что не работает 1

Если вы попытаетесь ущипнуть, когда верх или низ изображения на экране, все это немного сходит с ума.

На свитках представлению разрешено перетаскивать, чтобы оно вытягивалось за пределы видимого содержимого (что я и хочу, так как это стандартное поведение для списка данных на iOS).При изменении масштаба, однако, представление отодвигается назад, так что содержимое закрепляется на экране (я не хочу, чтобы это происходило).

Эти двое сражаются друг с другом во время жеста «щепотка», из-за которого контент сильно мерцает вверх и вниз (чего я определенно не хочу!).

Что не работает 2

UICollectionViewПо умолчанию прокрутка имеет замедление, если вы отпускаете во время прокрутки, а также плавно отскакивает содержимое назад, когда вы прокручиваете за его пределами. В настоящее время они вообще не обрабатываются.

Если вы отпустите жест щипка во время прокрутки, он просто остановится.Если вы прокручиваете содержимое с помощью жеста, а затем отпускаете, оно остается на прежнем месте и не возвращается назад. Когда вы снова начинаете прокрутку, происходит возврат содержимого обратно.Вещи, которые я пробовал, но не смог добраться до работы

UICollectionView, бытьUIScrollView должен иметь встроенныйUIPinchGestureRecogniser если он настроен правильно для поддержки масштабирования. Я задавался вопросом, могу ли я использовать это вместо того, чтобы иметь свой собственныйUIPinchGestureRecogniser, Я попытался настроить это, установив минимальную и максимальную шкалы и добавив обработчик пинча моего контроллера. Тем не менее, я не очень понимаю, что я должен возвращаться из моей реализацииviewForZoomingInScrollView:так что я просто создаю фиктивный вид с[[UIView alloc] initWithFrame: [[self collectionView] bounds]], Это заставляет представление свитка "свернуться" в одну строку, а это не то, что мне нужно!

Наконец (перед кодом)

Это длинный вопрос, поэтому спасибо, что прочитали его. Спасибо еще больше, если вы можете помочь с ответом. Извините, если многое из того, что я сказал или добавил, не имеет значения!

Код для контроллера вида
//  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

Ответы на вопрос(1)

Ваш ответ на вопрос