ios - 向 UICollectionView 添加双指缩放

标签 ios uicollectionview pinchzoom uicollectionviewlayout uipinchgesturerecognizer

介绍

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

最相关的代码内嵌在问题的底部,以便快速访问。您可以 Download a zip of the source 或以 的形式获取项目Mercurial Repository 在 BitBucket。该项目现在合并了以下答案中的修复。如果你想要最初提供的损坏版本,它会被标记为“initial-buggy-version”

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

预期效果

该应用程序将显示形成垂直表格的大量离散信息行。该表格将由用户垂直滚动。这是带有 UITableView 的标准行为,您可以使用 UICollectionView也。但是,该应用程序还必须支持捏合缩放。当你在 table 上捏缩放时,全部 的线应该挤压在一起。当你拉伸(stretch)时,全部 的线应该拉开。

在我的概念证明中,单个单元格没有调整大小,它们只是重新定位得更近或更远。这是有意为之:我认为验证想法的可行性并不重要。

以下是屏幕截图,显示了当前应用程序的缩小和放大方式:

Zoomed in image
Zoomed out image

当前实现

我正在使用 UICollectionView带定制 UICollectionViewLayout子类。布局定位UICollectionViewCells在屏幕中间的一个很好的摆动正弦波中。每个UICollectionViewCell只是一个 UILabel 的容器拿着indexPath排。
UICollectionViewLayout子类有一个参数来设置它描述的每个单元格之间的垂直间距 UICollectionView并调整它允许根据需要垂直挤压或拉伸(stretch) table 。

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

无需进一步考虑,缩放将从内容的顶部发生,而不是围绕触摸手势的中心。 UICollectionViewcontentOffset在捏合期间调整属性以提供此功能。

手势识别器还需要适应捏合时发生的拖动。这也可以通过更改 UICollectionView 来处理。的 contentOffset .一些额外的代码允许触摸手势的中心点随着手指添加到手势中/从手势中移除而改变。

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

我添加了代码来禁用 UICollectionView在我的捏合手势期间内置滚动,但这似乎没有太大区别。我尝试使用 gestureRecognizer:shouldRequireFailureOfGestureRecognizer:做我的 UIPinchGestureRecognizer使内置失败 UIPanGestureRecognizer ,但这似乎完全阻止了我的捏合识别器工作。我不知道这是我愚蠢,还是 iOS 中的错误。

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

什么有效

工作位工作得很好。您可以上下拖动表格。在拖动过程中,您可以添加一个手指并开始捏合,然后松开手指并继续拖动,然后添加并捏合,等等。一切都非常流畅。在原版 iPhone 5 上,它可以流畅地支持捏合和平移,在屏幕上查看 > 200 次。

什么不起作用 1

如果您在 View 的顶部或底部出现在屏幕上时尝试捏合和捏合,这一切都会变得有点疯狂。

  • 在滚动时,允许拖动 View 以使其超出可见内容(这是我想要的,因为它是 iOS 上数据列表的标准行为)。
  • 然而,在规模变化时, View 会被收回,以便内容被夹在屏幕上(我不希望这种情况发生)。

  • 这两个在捏合手势期间互相打架,这使得内容剧烈地上下闪烁(我绝对不想要!)。

    什么不起作用 2
    UICollectionView如果您在滚动时放手,默认滚动会减速,并且当您滚动到内容之外时也会平滑地将内容弹回。目前根本没有处理这些。
  • 如果您在滚动时松开捏合手势,它就会停止。
  • 如果您使用捏合手势滚动到内容之外然后松开,它会保持原样并且不会弹回。当您再次开始滚动时,它会将内容跳回。

  • 我尝试过但无法工作的事情
    UICollectionView , 是 UIScrollView应该有一个内置的 UIPinchGestureRecogniser如果它设置正确以支持缩放。我想知道我是否可以利用它而不是拥有自己的 UIPinchGestureRecogniser .我尝试通过设置最小和最大比例并添加 Controller 的捏合处理程序来设置它。但是,我真的不明白我应该从 viewForZoomingInScrollView: 的实现中返回什么。 ,所以我只是用 [[UIView alloc] initWithFrame: [[self collectionView] bounds]] 创建了一个虚拟 View .它使 ScrollView “折叠”为一行,这不是我所追求的!

    最后(在代码之前)

    这是一个很长的问题,所以感谢您的阅读。如果您能帮忙解答,则更加感谢。如果我所说或添加的很多内容无关紧要,我很抱歉!

    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
    

    最佳答案

    好的一点——如何让它发挥作用

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

    我在 viewDidLoad 中添加了以下几行我的方法UICollectionViewController :

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

    我还更新了示例项目,以便代替文本标签, View 由小圆圈组成。当您放大和缩小时,这些会被调整大小。这是现在的样子(缩小和放大):

    Image zoomed out Image zoomed in

    在缩放过程中,圆的 View 不会重新绘制,而只是从它们的预缩放大小进行插值。重绘被推迟到缩放完成。这是放大几次后的外观截图:

    During zoom

    在后台线程中进行缩放期间的重绘会很棒,这样人工制品就不那么明显了,但这远远超出了这个问题的范围,我也还没有研究过。

    您可以在 Bit Bucket 上找到整个项目以及修复程序,以便您可以 grab the files那里。

    不好的部分——我不知道它为什么有效

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

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

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

    关于ios - 向 UICollectionView 添加双指缩放,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/20895469/

    相关文章:

    ios - .noDataText 未在图表内更新。 swift 2

    javascript - 从作为 zip 下载的包中加载 UIWebview 中的 .js 文件

    ios - UICollectionView 神秘崩溃

    iphone - ios - UICollectionView 在水平分页时更改默认偏移量

    android - 如何从捏合中心缩放列表项

    ios - UILabel 调整大小不起作用

    ios - 如何使用 Alamofire 将用户个人资料图像保存到 MYSQL 数据库,然后在以下屏幕上检索

    swift - 单个 UICollectionView 的多个单元格

    android - 如何在android中实现图片 ScrollView ?

    android - Android 中的可滚动/可缩放 View