objective-c 使用 UIScrollView 用两根手指滚动
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/787212/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Scrolling with two fingers with a UIScrollView
提问by Craig
I have an app where my main view accepts both touchesBeganand touchesMoved, and therefore takes in single finger touches, and drags. I want to implement a UIScrollView, and I have it working, but it overrides the drags, and therefore my contentView never receives them. I'd like to implement a UIScrollview, where a two finger drag indicates a scroll, and a one finger drag event gets passed to my content view, so it performs normally. Do I need create my own subclass of UIScrollView?
我有一个应用程序,我的主视图同时接受touchesBegan和touchesMoved,因此接受单指触摸和拖动。我想实现一个UIScrollView,我让它工作,但它覆盖了拖动,因此我的 contentView 永远不会收到它们。我想实现一个UIScrollview,其中两指拖动表示滚动,单指拖动事件传递到我的内容视图,因此它正常执行。我需要创建自己的子类UIScrollView吗?
Here's my code from my appDelegatewhere I implement the UIScrollView.
这是我appDelegate实现UIScrollView.
@implementation MusicGridAppDelegate
@synthesize window;
@synthesize viewController;
@synthesize scrollView;
- (void)applicationDidFinishLaunching:(UIApplication *)application {
// Override point for customization after app launch
//[application setStatusBarHidden:YES animated:NO];
//[window addSubview:viewController.view];
scrollView.contentSize = CGSizeMake(720, 480);
scrollView.showsHorizontalScrollIndicator = YES;
scrollView.showsVerticalScrollIndicator = YES;
scrollView.delegate = self;
[scrollView addSubview:viewController.view];
[window makeKeyAndVisible];
}
- (void)dealloc {
[viewController release];
[scrollView release];
[window release];
[super dealloc];
}
采纳答案by Andrey Tarantsov
You need to subclass UIScrollView (of course!). Then you need to:
您需要继承 UIScrollView(当然!)。那么你需要:
make single-finger events to go to your content view (easy), and
make two-finger events scroll the scroll view (may be easy, may be hard, may be impossible).
使单指事件进入您的内容视图(简单),以及
使两指事件滚动滚动视图(可能很容易,可能很难,可能不可能)。
Patrick's suggestion is generally fine: let your UIScrollView subclass know about your content view, then in touch event handlers check the number of fingers and forward the event accordingly. Just be sure that (1) the events you send to content view don't bubble back to UIScrollView through the responder chain (i.e. make sure to handle them all), (2) respect the usual flow of touch events (i.e. touchesBegan, than some number of {touchesBegan, touchesMoved, touchesEnded}, finished with touchesEnded or touchesCancelled), especially when dealing with UIScrollView. #2 can be tricky.
帕特里克的建议通常很好:让您的 UIScrollView 子类知道您的内容视图,然后在触摸事件处理程序中检查手指的数量并相应地转发事件。只要确保 (1) 您发送到内容视图的事件不会通过响应者链冒泡回 UIScrollView(即确保处理它们),(2) 尊重通常的触摸事件流(即 touchesBegan,而不是一些 {touchesBegan, touchesMoved, touchesEnded},以 touchesEnded 或 touchesCancelled 结束),尤其是在处理 UIScrollView 时。#2 可能很棘手。
If you decide the event is for UIScrollView, another trick is to make UIScrollView believe your two-finger gesture is actually a one-finger gesture (because UIScrollView cannot be scrolled with two fingers). Try passing only the data for one finger to super (by filtering the (NSSet *)touchesargument — note that it only contains the changed touches — and ignoring events for the wrong finger altogether).
如果你决定事件是针对 UIScrollView 的,另一个技巧是让 UIScrollView 相信你的两指手势实际上是一个单指手势(因为 UIScrollView 不能用两根手指滚动)。尝试只将一根手指的数据传递给 super(通过过滤(NSSet *)touches参数——注意它只包含更改的触摸——并完全忽略错误手指的事件)。
If that does not work, you are in trouble. Theoretically you can try to create artificial touches to feed to UIScrollView by creating a class that looks similar to UITouch. Underlying C code does not check types, so maybe casting (YourTouch *) into (UITouch *) will work, and you will be able to trick UIScrollView into handling the touches that did not really happen.
如果这不起作用,你就有麻烦了。从理论上讲,您可以尝试通过创建一个看起来类似于 UITouch 的类来创建人工触摸来提供给 UIScrollView。底层的 C 代码不检查类型,因此也许将 (YourTouch *) 转换为 (UITouch *) 会起作用,并且您将能够欺骗 UIScrollView 处理实际上没有发生的触摸。
You probably want to read my article on advanced UIScrollView tricks(and see some totally unrelated UIScrollView sample code there).
您可能想阅读我关于高级 UIScrollView 技巧的文章(并在那里查看一些完全不相关的 UIScrollView 示例代码)。
Of course, if you can't get it to work, there's always an option of either controlling UIScrollView's movement manually, or use an entirely custom-written scroll view. There's TTScrollView class in Three20 library; it does not feel good to the user, but does feel good to programmer.
当然,如果你不能让它工作,总有一个选项,要么手动控制 UIScrollView 的移动,要么使用完全自定义编写的滚动视图。Three20 库中有 TTScrollView 类;用户感觉不好,但程序员感觉很好。
回答by Kenshi
In SDK 3.2 the touch handling for UIScrollView is handled using Gesture Recognizers.
在 SDK 3.2 中,UIScrollView 的触摸处理是使用手势识别器处理的。
If you want to do two-finger panning instead of the default one-finger panning, you can use the following code:
如果要进行双指平移而不是默认的单指平移,可以使用以下代码:
for (UIGestureRecognizer *gestureRecognizer in scrollView.gestureRecognizers) {
if ([gestureRecognizer isKindOfClass:[UIPanGestureRecognizer class]]) {
UIPanGestureRecognizer *panGR = (UIPanGestureRecognizer *) gestureRecognizer;
panGR.minimumNumberOfTouches = 2;
}
}
回答by Guto Araujo
For iOS 5+, setting this property has the same effect as the answer by Mike Laurence:
对于 iOS 5+,设置此属性与 Mike Laurence 的回答具有相同的效果:
self.scrollView.panGestureRecognizer.minimumNumberOfTouches = 2;
One finger dragging is ignored by panGestureRecognizer and so the one finger drag event gets passed to the content view.
panGestureRecognizer 会忽略单指拖动,因此单指拖动事件会传递给内容视图。
回答by Mike Laurence
In iOS 3.2+ you can now achieve two-finger scrolling quite easily. Just add a pan gesture recognizer to the scroll view and set its maximumNumberOfTouches to 1. It will claim all single-finger scrolls, but allow 2+ finger scrolls to pass up the chain to the scroll view's built-in pan gesture recognizer (and thus allow normal scrolling behavior).
在 iOS 3.2+ 中,您现在可以非常轻松地实现两指滚动。只需在滚动视图中添加一个平移手势识别器并将其 maximumNumberOfTouches 设置为 1。它将要求所有单指滚动,但允许 2+ 手指滚动向上传递到滚动视图的内置平移手势识别器(因此允许正常的滚动行为)。
UIPanGestureRecognizer *panGestureRecognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(recognizePan:)];
panGestureRecognizer.maximumNumberOfTouches = 1;
[scrollView addGestureRecognizer:panGestureRecognizer];
[panGestureRecognizer release];
回答by Dan Rosenstark
This answers are a mess since you can only find the correct answer by reading all the other answers and the comments (closest answer got the question backwards). The accepted answer is too vague to be useful, and suggests a different method.
这个答案一团糟,因为您只能通过阅读所有其他答案和评论来找到正确答案(最接近的答案将问题倒退)。接受的答案太模糊而无用,并提出了一种不同的方法。
Synthesizing, this works
合成,这有效
// makes it so that only two finger scrolls go
for (id gestureRecognizer in self.gestureRecognizers) {
if ([gestureRecognizer isKindOfClass:[UIPanGestureRecognizer class]])
{
UIPanGestureRecognizer *panGR = gestureRecognizer;
panGR.minimumNumberOfTouches = 2;
panGR.maximumNumberOfTouches = 2;
}
}
This requires two fingers for a scroll. I've done this in a subclass, but if not, just replace self.gestureRecognizerswith myScrollView.gestureRecognizersand you're good to go.
这需要两个手指才能滚动。我已经在一个子类中完成了这个,但如果没有,只需替换self.gestureRecognizers为myScrollView.gestureRecognizers,你就可以开始了。
The only thing that I added is using idto avoid an ugly cast :)
我添加的唯一一件事是id用来避免丑陋的演员阵容:)
This works but can get quite messy if you want your UIScrollView to do zoom too... the gestures don't work correctly, since pinch-to-zoom and scroll fight it out. I'll update this if I find a suitable answer.
这有效,但如果您希望 UIScrollView 也进行缩放,则可能会变得非常混乱……手势无法正常工作,因为双指缩放和滚动无法解决问题。如果我找到合适的答案,我会更新这个。
回答by IPv6
we managed to implement similar functionality in our iPhone drawing app by subclassing UIScrollView and filtering events depending on number of touches in simple and rude way:
我们通过继承 UIScrollView 并根据触摸次数以简单粗暴的方式过滤事件,设法在我们的 iPhone 绘图应用程序中实现了类似的功能:
//OCRScroller.h
@interface OCRUIScrollView: UIScrollView
{
double pass2scroller;
}
@end
//OCRScroller.mm
@implementation OCRUIScrollView
- (id)initWithFrame:(CGRect)aRect {
pass2scroller = 0;
UIScrollView* newv = [super initWithFrame:aRect];
return newv;
}
- (void)setupPassOnEvent:(UIEvent *)event {
int touch_cnt = [[event allTouches] count];
if(touch_cnt<=1){
pass2scroller = 0;
}else{
double timems = double(CACurrentMediaTime()*1000);
pass2scroller = timems+200;
}
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
[self setupPassOnEvent:event];
[super touchesBegan:touches withEvent:event];
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
[self setupPassOnEvent:event];
[super touchesMoved:touches withEvent:event];
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
pass2scroller = 0;
[super touchesEnded:touches withEvent:event];
}
- (BOOL)touchesShouldBegin:(NSSet *)touches withEvent:(UIEvent *)event inContentView:(UIView *)view
{
return YES;
}
- (BOOL)touchesShouldCancelInContentView:(UIView *)view
{
double timems = double(CACurrentMediaTime()*1000);
if (pass2scroller == 0 || timems> pass2scroller){
return NO;
}
return YES;
}
@end
ScrollView setuped as follows:
ScrollView 设置如下:
scroll_view = [[OCRUIScrollView alloc] initWithFrame:rect];
scroll_view.contentSize = img_size;
scroll_view.contentOffset = CGPointMake(0,0);
scroll_view.canCancelContentTouches = YES;
scroll_view.delaysContentTouches = NO;
scroll_view.scrollEnabled = YES;
scroll_view.bounces = NO;
scroll_view.bouncesZoom = YES;
scroll_view.maximumZoomScale = 10.0f;
scroll_view.minimumZoomScale = 0.1f;
scroll_view.delegate = self;
self.view = scroll_view;
simple tap does nothing (you can handle it in the way you need), tap with two fingers scrolls/zooms view as expected. no GestureRecognizer is used, so works from iOS 3.1
简单的点击什么都不做(你可以按照你需要的方式处理它),用两根手指点击滚动/缩放视图。不使用 GestureRecognizer,因此适用于 iOS 3.1
回答by Jan
I've got a further improvement to the code above. The problem was, that even after we set setCanCancelContentTouches:NO We have the problem, that a zoom gesture will interrupt with the content. It won't cancel the content touch but allow zooming in the meantime. TO prevent this i lock the zooming by setting the minimumZoomScale and maximumZoomScale to the same values everytime, the timer fires.
我对上面的代码有了进一步的改进。问题是,即使在我们设置之后,setCanCancelContentTouches:NO 我们也有问题,缩放手势会中断内容。它不会取消内容触摸,但同时允许缩放。为了防止这种情况,我通过每次将 minimumZoomScale 和 maximumZoomScale 设置为相同的值来锁定缩放,计时器触发。
A quite strange behavior is that when a one finger event gets canceled by a two finger gesture within the allowed time period, the timer will be delayed. It gets fired after the touchCanceled Event gets called. So we have the problem, that we try to lock the zooming although the event is already canceled and therefore disable zooming for the next event.
To handle this behavior the timer callback method checks against if touchesCanceled was called before.
@implementation JWTwoFingerScrollView
一个非常奇怪的行为是,当一个手指事件在允许的时间段内被两个手指手势取消时,计时器将被延迟。它在 touchCanceled 事件被调用后被触发。所以我们遇到了问题,尽管事件已经被取消,但我们尝试锁定缩放,因此禁用下一个事件的缩放。为了处理此行为,计时器回调方法会检查之前是否调用过 touchesCanceled。
@implementation JWTwoFingerScrollView
#pragma mark -
#pragma mark Event Passing
- (id)initWithCoder:(NSCoder *)coder {
self = [super initWithCoder:coder];
if (self) {
for (UIGestureRecognizer* r in self.gestureRecognizers) {
if ([r isKindOfClass:[UIPanGestureRecognizer class]]) {
[((UIPanGestureRecognizer*)r) setMaximumNumberOfTouches:2];
[((UIPanGestureRecognizer*)r) setMinimumNumberOfTouches:2];
zoomScale[0] = -1.0;
zoomScale[1] = -1.0;
}
timerWasDelayed = NO;
}
}
return self;
}
-(void)lockZoomScale {
zoomScale[0] = self.minimumZoomScale;
zoomScale[1] = self.maximumZoomScale;
[self setMinimumZoomScale:self.zoomScale];
[self setMaximumZoomScale:self.zoomScale];
NSLog(@"locked %.2f %.2f",self.minimumZoomScale,self.maximumZoomScale);
}
-(void)unlockZoomScale {
if (zoomScale[0] != -1 && zoomScale[1] != -1) {
[self setMinimumZoomScale:zoomScale[0]];
[self setMaximumZoomScale:zoomScale[1]];
zoomScale[0] = -1.0;
zoomScale[1] = -1.0;
NSLog(@"unlocked %.2f %.2f",self.minimumZoomScale,self.maximumZoomScale);
}
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
NSLog(@"began %i",[event allTouches].count);
[self setCanCancelContentTouches:YES];
if ([event allTouches].count == 1){
touchesBeganTimer = [NSTimer scheduledTimerWithTimeInterval:0.1 target:self selector:@selector(firstTouchTimerFired:) userInfo:nil repeats:NO];
[touchesBeganTimer retain];
[touchFilter touchesBegan:touches withEvent:event];
}
}
//if one finger touch gets canceled by two finger touch, this timer gets delayed
// so we can! use this method to disable zooming, because it doesnt get called when two finger touch events are wanted; otherwise we would disable zooming while zooming
-(void)firstTouchTimerFired:(NSTimer*)timer {
NSLog(@"fired");
[self setCanCancelContentTouches:NO];
//if already locked: unlock
//this happens because two finger gesture delays timer until touch event finishes.. then we dont want to lock!
if (timerWasDelayed) {
[self unlockZoomScale];
}
else {
[self lockZoomScale];
}
timerWasDelayed = NO;
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
// NSLog(@"moved %i",[event allTouches].count);
[touchFilter touchesMoved:touches withEvent:event];
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
NSLog(@"ended %i",[event allTouches].count);
[touchFilter touchesEnded:touches withEvent:event];
[self unlockZoomScale];
}
//[self setCanCancelContentTouches:NO];
-(void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event {
NSLog(@"canceled %i",[event allTouches].count);
[touchFilter touchesCancelled:touches withEvent:event];
[self unlockZoomScale];
timerWasDelayed = YES;
}
@end
#pragma mark -
#pragma mark Event Passing
- (id)initWithCoder:(NSCoder *)coder {
self = [super initWithCoder:coder];
if (self) {
for (UIGestureRecognizer* r in self.gestureRecognizers) {
if ([r isKindOfClass:[UIPanGestureRecognizer class]]) {
[((UIPanGestureRecognizer*)r) setMaximumNumberOfTouches:2];
[((UIPanGestureRecognizer*)r) setMinimumNumberOfTouches:2];
zoomScale[0] = -1.0;
zoomScale[1] = -1.0;
}
timerWasDelayed = NO;
}
}
return self;
}
-(void)lockZoomScale {
zoomScale[0] = self.minimumZoomScale;
zoomScale[1] = self.maximumZoomScale;
[self setMinimumZoomScale:self.zoomScale];
[self setMaximumZoomScale:self.zoomScale];
NSLog(@"locked %.2f %.2f",self.minimumZoomScale,self.maximumZoomScale);
}
-(void)unlockZoomScale {
if (zoomScale[0] != -1 && zoomScale[1] != -1) {
[self setMinimumZoomScale:zoomScale[0]];
[self setMaximumZoomScale:zoomScale[1]];
zoomScale[0] = -1.0;
zoomScale[1] = -1.0;
NSLog(@"unlocked %.2f %.2f",self.minimumZoomScale,self.maximumZoomScale);
}
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
NSLog(@"began %i",[event allTouches].count);
[self setCanCancelContentTouches:YES];
if ([event allTouches].count == 1){
touchesBeganTimer = [NSTimer scheduledTimerWithTimeInterval:0.1 target:self selector:@selector(firstTouchTimerFired:) userInfo:nil repeats:NO];
[touchesBeganTimer retain];
[touchFilter touchesBegan:touches withEvent:event];
}
}
//if one finger touch gets canceled by two finger touch, this timer gets delayed
// so we can! use this method to disable zooming, because it doesnt get called when two finger touch events are wanted; otherwise we would disable zooming while zooming
-(void)firstTouchTimerFired:(NSTimer*)timer {
NSLog(@"fired");
[self setCanCancelContentTouches:NO];
//if already locked: unlock
//this happens because two finger gesture delays timer until touch event finishes.. then we dont want to lock!
if (timerWasDelayed) {
[self unlockZoomScale];
}
else {
[self lockZoomScale];
}
timerWasDelayed = NO;
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
// NSLog(@"moved %i",[event allTouches].count);
[touchFilter touchesMoved:touches withEvent:event];
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
NSLog(@"ended %i",[event allTouches].count);
[touchFilter touchesEnded:touches withEvent:event];
[self unlockZoomScale];
}
//[self setCanCancelContentTouches:NO];
-(void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event {
NSLog(@"canceled %i",[event allTouches].count);
[touchFilter touchesCancelled:touches withEvent:event];
[self unlockZoomScale];
timerWasDelayed = YES;
}
@end
回答by Adam Freeman
I put this in the viewDidLoad method and this accomplishes the scroll view handling the two touch pan behavior and another pan gesture handler handling the one touch pan behavior -->
我把它放在 viewDidLoad 方法中,这完成了处理两个触摸平移行为的滚动视图和另一个处理一个触摸平移行为的平移手势处理程序 -->
scrollView.panGestureRecognizer.minimumNumberOfTouches = 2
let panGR = UIPanGestureRecognizer(target: self, action: #selector(ViewController.handlePan(_:)))
panGR.minimumNumberOfTouches = 1
panGR.maximumNumberOfTouches = 1
scrollView.gestureRecognizers?.append(panGR)
and in the handlePan method which is a function attached to the ViewController there is simply a print statement to verify that the method is being entered -->
在 handlePan 方法中,它是一个附加到 ViewController 的函数,只有一个打印语句来验证是否正在输入该方法 -->
@IBAction func handlePan(_ sender: UIPanGestureRecognizer) {
print("Entered handlePan numberOfTuoches: \(sender.numberOfTouches)")
}
HTH
HTH
回答by SEQOY Development Team
Bad news: iPhone SDK 3.0 and up, don't pass touches to -touchesBegan:and -touchesEnded:**UIScrollview**subclass methods anymore. You can use the touchesShouldBeginand touchesShouldCancelInContentViewmethods that is not the same.
坏消息:iPhone SDK 3.0 及更高版本,不要再将触摸传递给-touchesBegan:和 - touchesEnded:** UIScrollview** 子类方法。您可以使用不同的touchesShouldBegin和touchesShouldCancelInContentView方法。
If you really want to get this touches, have one hackthat allow this.
如果你真的想得到这个接触,有一个允许这样做的黑客。
In your subclass of UIScrollViewoverride the hitTestmethod like this:
在您的UIScrollView覆盖子类中,hitTest方法如下:
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
UIView *result = nil;
for (UIView *child in self.subviews)
if ([child pointInside:point withEvent:event])
if ((result = [child hitTest:point withEvent:event]) != nil)
break;
return result;
}
This will pass to you subclass this touches, however you can't cancel the touches to UIScrollViewsuper class.
这将传递给您这个触摸的子类,但是您不能取消对UIScrollView超类的触摸。
回答by lucius
What I do is have my view controller set up the scroll view:
我要做的是让我的视图控制器设置滚动视图:
[scrollView setCanCancelContentTouches:NO];
[scrollView setDelaysContentTouches:NO];
And in my child view I have a timer because two-finger touches usually start out as one finger followed quickly by two fingers.:
在我的孩子看来,我有一个计时器,因为两指触摸通常从一根手指开始,然后是两根手指。:
- (void) touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
// Hand tool or two or more touches means a pan or zoom gesture.
if ((selectedTool == kHandToolIndex) || (event.allTouches.count > 1)) {
[[self parentScrollView] setCanCancelContentTouches:YES];
[firstTouchTimer invalidate];
firstTouchTimer = nil;
return;
}
// Use a timer to delay first touch because two-finger touches usually start with one touch followed by a second touch.
[[self parentScrollView] setCanCancelContentTouches:NO];
anchorPoint = [[touches anyObject] locationInView:self];
firstTouchTimer = [NSTimer scheduledTimerWithTimeInterval:kFirstTouchTimeInterval target:self selector:@selector(firstTouchTimerFired:) userInfo:nil repeats:NO];
firstTouchTimeStamp = event.timestamp;
}
If a second touchesBegan: event comes in with more than one finger, the scroll view is allowed to cancel touches. So if the user pans using two fingers, this view would get a touchesCanceled:message.
如果第二个 touchesBegan: 事件用一个以上的手指进入,则允许滚动视图取消触摸。因此,如果用户使用两根手指平移,则此视图将收到一条touchesCanceled:消息。

