将缩放缩放添加到UICollectionView

Ben*_*ohn 31 ios pinchzoom uipinchgesturerecognizer uicollectionview uicollectionviewlayout

介绍

我将描述我想要实现的效果,然后我将详细介绍我目前是如何实现这一点以及它的行为有什么问题.我还会提到我看过的另一种方法,但根本无法完成工作.

最相关的代码内嵌在问题的底部,以便快速访问.您可以下载源代码的zip或在BitBucket上将项目作为Mercurial Repository获取.该项目现在包含以下答案的修复程序.如果你想要最初提供的破碎版本,它会被标记为"initial-buggy-version"

该项目是概念/尖峰的最小证明,用于评估效果是否可行,因此它非常轻巧简单!

期望的效果

该应用程序将显示大量离散的信息行,形成一个垂直表.该表将由用户垂直滚动.这是a的标准行为UITableView,你也可以使用它UICollectionView.但是,应用程序还必须支持缩放缩放.当你在桌子上捏缩放时,所有的线条应该挤压在一起.当你伸展时,所有的线都应该分开.

在我的概念证明中,单个细胞没有调整大小,它们只是重新定位在一起或更远.这是故意的:我认为验证这个想法的可行性并不重要.

这是屏幕抓取,显示当前应用程序看起来如何缩小和放大:

放大图像 缩小图像

目前的实施

我正在使用UICollectionView自定义UICollectionViewLayout子类.布局将UICollectionViewCells一个漂亮的摇摆正弦波放在屏幕中间.每个UICollectionViewCell仅仅是一个容器UILabel持有indexPath行.

UICollectionViewLayout子类具有参数来设置每个单元之间的垂直间距它描述到UICollectionView,调整这允许表被压扁或如所期望垂直拉伸.

我的UICollectionViewController子类有一个UIPinchGestureRecognizer.当识别器检测到比例变化时,相应地UICollectionView改变布局中的垂直单元间距.

在没有进一步考虑的情况下,缩放将从内容的顶部发生,而不是围绕触摸手势的中心发生.该UICollectionViewcontentOffset属性捏提供此功能时调整.

手势识别器还需要适应挤压时发生的拖动.这也是通过改变UICollectionView's 来处理的contentOffset.一些附加代码允许触摸手势的中心点随着手指被添加到手势或从手势移除而改变.

请注意UICollectionView,作为UIScrollView子类,它有自己的UIPanGestureRecognizerUIPinchGestureRecogniser我添加的交互.我不确定这是否会导致问题.

我添加了代码以UICollectionView在我的捏合手势期间禁用内置滚动,但这似乎没有太大区别.我试图gestureRecognizer:shouldRequireFailureOfGestureRecognizer:使我UIPinchGestureRecognizer的内置失败UIPanGestureRecognizer,但这似乎停止我的捏识别器工作.我不知道这是不是我是傻瓜,还是iOS中的错误.

如前所述,当前的UICollectionViewCells未调整大小.他们只是重新定位.这是故意的.我认为验证这个概念并不重要.

什么有用

工作位工作得很好.您可以上下拖动表格.在拖动过程中你可以添加一个手指并开始捏,然后释放一个手指并继续拖动,然后添加和捏等.这一切都很顺利.在原装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
Run Code Online (Sandbox Code Playgroud)

Ben*_*ohn 16

好一点 - 如何让它工作

对上述代码的一些非常小的调整已经解决了问题中的什么不起作用1什么不起作用2.

我已将以下几行添加到viewDidLoad我的方法中UICollectionViewController:

[collectionView setMinimumZoomScale: 0.25];
[collectionView setMaximumZoomScale: 4];
Run Code Online (Sandbox Code Playgroud)

我还更新了示例项目,以便视图由小圆圈组成,而不是文本标签.放大和缩小时,会调整大小.这是现在的样子(缩小和放大):

图像缩小了 图像放大了

在缩放过程中,圆圈的视图不会重新绘制,而只是从它们的缩放前尺寸进行插值.重绘被推迟到缩放完成.这是捕捉几次放大后的样子:

缩放期间

在缩放期间重绘会在后台线程中进行重绘会很好,这样人工制品就不那么引人注目了,但这远远超出了这个问题的范围,而且我还没有完成它.

你可以在Bit Bucket上找到整个项目,带有修复,这样你就可以那里获取文件.

坏部分 - 我不知道它为什么会起作用

我希望通过回答这个问题,我对UIScrollView缩放有很多新的确定性.我不.

从我读到的关于UIScrollView的内容来看,这个"修复"应该没有任何区别,它应该已经在第一时间起作用了.

A UIScrollView不应该启用滚动,直到你给它一个实现的委托,viewForZoomingInScrollView:我没有做过.