介绍
我将描述我想要达到的效果,然后我将详细说明我目前如何尝试实现它以及它的行为有什么问题。我还会提到另一种我看过但根本无法工作的方法。
最相关的代码位于问题底部,以便快速访问。您可以在 BitBucket下载源代码的 zip文件或将项目作为Mercurial 存储库获取。该项目现在包含以下答案中的修复。如果您想要最初提供的损坏版本,则将其标记为“initial-buggy-version”
该项目是一个极简的概念证明/尖峰,用于评估效果是否可行,所以它相当轻巧!
所需效果
该应用程序将显示大量离散的信息行,形成一个垂直表格。该表格将由用户垂直滚动。这是 a 的标准行为UITableView
,您也可以使用 a UICollectionView
。但是,应用程序还必须支持捏缩放。当你在桌子上捏缩放时,所有的线都应该挤在一起。当你伸展时,所有的线都应该分开。
在我的概念证明中,单个单元格没有调整大小,它们只是重新定位得更近或更远。这是故意的:我认为验证这个想法的可行性并不重要。
这是屏幕截图,显示了当前 App 的缩小和放大效果:
当前实施
我正在使用UICollectionView
带有自定义UICollectionViewLayout
子类的 a 。布局UICollectionViewCells
在屏幕中间以一个漂亮的摆动正弦波定位。每个UICollectionViewCell
都只是一个容纳UILabel
行的容器indexPath
。
子类有一个参数来设置它所描述的每个单元格之间的UICollectionViewLayout
垂直间距,UICollectionView
并且调整它可以让表格根据需要被垂直压缩或拉伸。
我的UICollectionViewController
子类有一个UIPinchGestureRecognizer
. 当识别器检测到比例变化时,UICollectionView
布局中的垂直单元格间距会相应更改。
如果没有进一步考虑,缩放将从内容的顶部开始,而不是触摸手势的中心。在捏合期间调整UICollectionView
'contentOffset
属性以提供此功能。
手势识别器还需要适应捏合时发生的拖动。这也可以通过更改UICollectionView
's来处理contentOffset
。一些额外的代码允许触摸手势的中心点随着手指添加到手势/从手势中移除而改变。
请注意UICollectionView
,作为一个UIScrollView
子类,它有自己的UIPanGestureRecognizer
与我添加的交互UIPinchGestureRecogniser
。我不确定这是否会导致问题。
我添加了代码以UICollectionView
在捏手势期间禁用 ' 的内置滚动,但这似乎没有太大区别。我试图用内置的gestureRecognizer:shouldRequireFailureOfGestureRecognizer:
来使我的失败,但这似乎完全停止了我的捏识别器的工作。我不知道这是我的愚蠢,还是iOS中的错误。UIPinchGestureRecognizer
UIPanGestureRecognizer
如前所述,当前UICollectionViewCell
s 没有调整大小。他们只是重新定位。这是故意的。我认为验证这个概念并不重要。
什么有效
工作位工作得很好。您可以上下拖动表格。在拖动过程中,您可以添加手指并开始捏合,然后松开手指并继续拖动,然后添加并捏合等。这一切都非常流畅。在最初的 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