Подтвердить что ты не робот

Добавление масштабирования зума в UICollectionView

Введение

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

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

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

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

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

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

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

Zoomed in imageZoomed out image

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

Я использую UICollectionView с пользовательским подклассом UICollectionViewLayout. Макет позиционирует UICollectionViewCells в приятной волновой синусоидальной волне по середине экрана. Каждый UICollectionViewCell является просто контейнером для UILabel, содержащего строку indexPath.

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

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

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

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

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

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

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

Что работает

Рабочие биты работают достаточно хорошо. Вы можете перетащить стол вверх и вниз. Во время перетаскивания вы можете добавить палец и начать щепотку, затем отпустить палец и продолжить перетаскивание, затем добавить и ущипнуть и т.д. Все это довольно гладко. На оригинальном iPhone 5 он плавно поддерживает щепотку и панорамирование s > 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
4b9b3361

Ответ 1

Хороший бит - как заставить его работать

Некоторые незначительные изменения в этом коде разрешили Что не работает 1 и Что не работает 2 в вопросе.

Я добавил следующие строки в метод viewDidLoad моего UICollectionViewController:

[collectionView setMinimumZoomScale: 0.25];
[collectionView setMaximumZoomScale: 4];

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

Image zoomed outImage zoomed in

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

During zoom

Было бы здорово, если бы перерисовка во время масштабирования произошла в фоновом потоке, чтобы артефакты были менее заметны, но это хорошо из сферы этого вопроса, и я еще не работал над этим.

Вы можете найти весь проект с исправлениями в битовом ведро, чтобы захватить файлы там.

Плохая часть - я не знаю, почему она работает

Я надеялся, что с ответом на этот вопрос у меня будет много новой уверенности в масштабировании UIScrollView. Я этого не делаю.

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

A UIScrollView не должен включать прокрутку, пока вы не дадите ему делегат, который реализует viewForZoomingInScrollView:, который я еще не сделал.