简介
我将描述我想要实现的效果,然后详细说明我目前正在尝试如何实现它以及当前行为存在什么问题。我还会提到另一种我看过但无法完全实现的方法。
最相关的代码是内联在问题底部以便快速访问。您可以下载源代码zip文件或者在BitBucket上获取作为一个Mercurial Repository的项目。该项目现已整合了下面回答中的修复。如果您想要最初提供的错误版本,请使用标记"initial-buggy-version"。
该项目是一个相对简单的概念验证/示例,以评估效果是否可行!
期望效果
应用程序将显示大量离散的信息行,形成垂直表格。用户可以通过垂直滚动来滚动表格。这是UITableView
的标准行为,您也可以使用UICollectionView
。但是,该应用程序还必须支持捏合缩放。当您对表格进行捏合缩放时,所有行将挤在一起。随着您的拉伸,所有的行都会拉开。
在我的概念验证中,单个单元格不会被调整大小,只会靠近或远离重新定位。这是有意为之的:我认为这并不是验证该想法可行性的关键。
下面是屏幕截图,显示当前应用程序缩小和放大的情况:
目前的实现
我正在使用自定义的UICollectionViewLayout
子类和UICollectionView
。布局将UICollectionViewCells
以一个漂亮且弯曲的正弦波形式放置在屏幕中央。每个UICollectionViewCell
只是包含一个持有indexPath
行的UILabel
的容器。
UICollectionViewLayout
子类具有设置垂直间距的参数,该参数描述了其所述的每个单元格与UICollectionView
之间的垂直间距,并调整此间距可以根据需要垂直压缩或拉伸表格。
我的UICollectionViewController
子类具有一个UIPinchGestureRecognizer
。当识别器检测到比例变化时,更改UICollectionView
布局中的垂直单元格间距。
未经进一步考虑,缩放将发生在内容顶部而非触摸手势中心。为了提供此功能,在收缩期间会调整UICollectionView
的contentOffset
属性。
手势识别器还需要适应在收缩时发生的拖动。这也通过更改UICollectionView
的contentOffset
来处理。一些额外的代码允许随着手指添加/移除而更改触摸手势的中心点。
请注意,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
UIPinchGestureRecognizer
减少集合视图项间距时,集合视图在视觉上被夹紧,以至于内容被拉回到屏幕顶部,而不是像拖动时那样“弹跳”(愚蠢的名字)。我还不理解它,但我认为它与UICollectionView
继承的缩放机制有关。如果我提供一个viewForZoomingInScrollView
并设置最小和最大缩放比例,问题就会消失。 - Benjohn