ios 允许 UIScrollView 及其子视图都响应触摸
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/11485100/
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
Allow UIScrollView and its subviews to both respond to a touch
提问by William Jockusch
I want both my UIScrollView and its subviews to receive all touch events inside the subview. Each can respond in its own way.
我希望我的 UIScrollView 及其子视图都能接收子视图内的所有触摸事件。每个人都可以以自己的方式做出回应。
Alternatively, if tap gestures were forwarded to subviews, all would be well.
或者,如果将点击手势转发到子视图,一切都会好起来的。
A lot of people are struggling in this general area. Here are a few of the many related questions:
很多人都在这个一般领域苦苦挣扎。以下是许多相关问题中的一些:
How does UIScrollView steal touches from its subviews
How to steal touches from UIScrollView?
How to Cancel Scrolling in UIScrollView
UIScrollView 如何从其子视图
窃取触摸 如何从 UIScrollView 窃取触摸?
如何在 UIScrollView 中取消滚动
Incidentally, if I override hitTest:withEvent: in the scroll view, I do see the touches as long as userInteractionEnabled is YES. But that doesn't really solve my problem, because:
顺便说一句,如果我在滚动视图中覆盖 hitTest:withEvent: ,只要 userInteractionEnabled 为 YES,我就会看到触摸。但这并不能真正解决我的问题,因为:
1) At that point, I don't know if it's a tap or not.
2) Sometimes I need to set userInteractionEnabled to NO.
1) 那个时候,我不知道是不是水龙头。
2) 有时我需要将 userInteractionEnabled 设置为 NO。
EDIT: To clarify, yes, I want to treat taps differently from pans. Taps should be handled by subviews. Pans can be handled by the scroll view in the usual way.
编辑:澄清一下,是的,我想将水龙头与平底锅区别对待。点击应该由子视图处理。平移可以由滚动视图以通常的方式处理。
回答by Aaron Hayman
First, a disclaimer. If you set userInteractionEnabled
to NO
on the UIScrollView
, no touch events will be passed to the subviews. So far as I'm aware, there's no way around that with one exception: intercept touch events on the superview of the UIScrollView
, and specifically pass those events to the subviews of UIScrollView
. To be honest, though, I don't know why you would want to do this. If you're wanting to disable specific UIScrollView functionality (like...well, scrolling) you can do that easily enough without disabling UserInteraction.
首先,免责声明。如果设置userInteractionEnabled
为NO
on UIScrollView
,则不会将触摸事件传递给子视图。到目前为止我所知,还有周围有一个例外没有办法:对的对上海华拦截触摸事件UIScrollView
,具体通过那些事件的子视图UIScrollView
。不过,说实话,我不知道你为什么要这样做。如果你想禁用特定的 UIScrollView 功能(比如……嗯,滚动),你可以很容易地做到这一点,而无需禁用 UserInteraction。
If I understand your question, you need tap events to be processed by the UIScrollView andpassed to the subviews? In any case (whatever the gesture is), I think what you're looking for is the protocol method gestureRecognizer:shouldRecognizeSimultaneouslyWithGestureRecognizer:in the protocol UIGestureRecognizerDelegate
. In your subviews, whatever gesture recognizers you have, set a delegate (probably whatever class is setting the UIGestureReconginzer
in the first place) on the gesture recognizer. Override the above method and return YES
. Now, this gesture will be recognized along with any other recognizers that might have 'stolen' the gesture (in your case, a tap). Using this method you can even fine tune your code to only send certain kinds of gestures to the subviews or send the gesture only in certain situations. It gives you a lot of control. Just be sure to read about the method, especially this part:
如果我理解你的问题,你需要点击事件由 UIScrollView 处理并传递给子视图?在任何情况下(无论手势是什么),我认为您正在寻找的是协议方法gestureRecognizer:shouldRecognizeSimultaneouslyWithGestureRecognizer:在协议中UIGestureRecognizerDelegate
。在您的子视图中,无论您拥有什么手势识别器,请在手势识别器上设置一个委托(可能是首先设置的任何类UIGestureReconginzer
)。覆盖上面的方法并返回YES
. 现在,此手势将与可能“窃取”手势的任何其他识别器一起被识别(在您的情况下,是轻按)。使用这种方法,您甚至可以微调您的代码以仅向子视图发送某些类型的手势或仅在某些情况下发送手势。它给了你很多控制权。请务必阅读该方法,尤其是这部分:
This method is called when recognition of a gesture by either gestureRecognizer or otherGestureRecognizer would block the other gesture recognizer from recognizing its gesture. Note that returning YES is guaranteed to allow simultaneous recognition; returning NO, on the other hand, is not guaranteed to prevent simultaneous recognition because the other gesture recognizer's delegate may return YES.
当gestureRecognizer 或otherGestureRecognizer 识别手势会阻止其他手势识别器识别其手势时,将调用此方法。请注意,返回 YES 保证允许同时识别;另一方面,返回 NO 并不能保证防止同时识别,因为另一个手势识别器的代表可能返回 YES。
Of course, there's a caveat: This only applies to gesture recognizers. So you may still have problems if you're trying to use touchesBegan:
, touchesEnded
, etc to process the touches. You can, of course, use hitTest:
to send raw touch events on to the subviews, but why? Why process the events using those methods in UIView
, when you can attach a UIGestureRecognizer
to a view and get all of that functionality for free? If you need touches processed in a way that no standard UIGestureRecognizer
can provide, subclassUIGestureRecognizer
and process the touches there. That way you get all the the functionality of a UIGestureRecognizer
along with your own custom touch processing. I really think Apple intended for UIGestureRecognizer
to replace most (if not all) of the custom touch processing code that developers use on UIView
. It allows for code-reuse and it's a lot easier to deal with when mitigating what code processes what touch event.
当然,有一个警告:这仅适用于手势识别器。所以,你仍然可能有问题,如果你想使用touchesBegan:
,touchesEnded
等来处理触摸。当然,您可以使用hitTest:
将原始触摸事件发送到子视图,但为什么呢?UIView
当您可以将 a 附加UIGestureRecognizer
到视图并免费获得所有这些功能时,为什么要使用 中的这些方法处理事件?如果您需要以标准UIGestureRecognizer
无法提供的方式处理触摸,请在那里子类化UIGestureRecognizer
并处理触摸。这样您就可以获得 a 的所有功能UIGestureRecognizer
以及您自己的自定义触摸处理。我真的认为 Apple 打算UIGestureRecognizer
替换开发人员使用的大部分(如果不是全部)自定义触摸处理代码UIView
. 它允许代码重用,并且在减轻什么代码处理什么触摸事件时更容易处理。
回答by Oyashiro
I don't know if this can help you, but I had a similar problem, where I wanted the scrollview to handle double-tap, but forward single tap to subviews. Here is the code used in a CustomScrollView
我不知道这是否可以帮助您,但我遇到了类似的问题,我希望滚动视图处理双击,但将单击转发到子视图。这是在a中使用的代码CustomScrollView
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch* touch = [touches anyObject];
// Coordinates
CGPoint point = [touch locationInView:[self.subviews objectAtIndex:0]];
// One tap, forward
if(touch.tapCount == 1){
// for each subview
for(UIView* overlayView in self.subviews){
// Forward to my subclasss only
if([overlayView isKindOfClass:[OverlayView class]]){
// translate coordinate
CGPoint newPoint = [touch locationInView:overlayView];
//NSLog(@"%@",NSStringFromCGPoint(newPoint));
BOOL isInside = [overlayView pointInside:newPoint withEvent:event];
//if subview is hit
if(isInside){
Forwarding
[overlayView touchesEnded:touches withEvent:event];
break;
}
}
}
}
// double tap : handle zoom
else if(touch.tapCount == 2){
if(self.zoomScale == self.maximumZoomScale){
[self setZoomScale:[self minimumZoomScale] animated:YES];
} else {
CGRect zoomRect = [self zoomRectForScrollView:self withScale:self.maximumZoomScale withCenter:point];
[self zoomToRect:zoomRect animated:YES];
}
[self setNeedsDisplay];
}
}
Of course, the effective code should be changed, but at this point you should have all the informations you need to decide if you have to forward the event. You might need to implement this in another method as touchesMoved:withEvent:.
当然,应该更改有效代码,但此时您应该拥有决定是否必须转发事件所需的所有信息。您可能需要在另一个方法中实现它,如 touchesMoved:withEvent:。
Hope this can help.
希望这能有所帮助。
回答by Hunter Monk
I was having this same problem, but with a scrollview that was inside UIPageViewController
, so it had to be handled slightly differently.
我遇到了同样的问题,但是在里面有一个滚动视图UIPageViewController
,所以它的处理方式必须略有不同。
By changing the cancelsTouchesInView
property to false for each recognizer on the UIScrollView
I was able to receives touches to buttons inside the UIPageViewController
.
通过将cancelsTouchesInView
每个识别器的属性更改为 false,UIScrollView
我能够接收对UIPageViewController
.
I did so by adding this code into viewDidLoad
:
我通过将此代码添加到viewDidLoad
:
guard let recognizers = self.pageViewController.view.subviews[0].gestureRecognizers else {
print("No gesture recognizers on scrollview.")
return
}
for recognizer in recognizers {
recognizer.cancelsTouchesInView = false
}
回答by xingzhi.sg
A hackish way to achieve your objective - not 100% exact - is to subclass the UIWindow and override the - (void)sendEvent:(UIEvent *)event;
实现您的目标的一种黑客方法 - 不是 100% 准确 - 是将 UIWindow 子类化并覆盖 - (void)sendEvent:(UIEvent *)event;
A quick example:
一个简单的例子:
in SecondResponderWindow.h header
在 SecondResponderWindow.h 标头中
//SecondResponderWindow.h
@protocol SecondResponderWindowDelegate
- (void)userTouchBegan:(id)tapPoint onView:(UIView*)aView;
- (void)userTouchMoved:(id)tapPoint onView:(UIView*)aView;
- (void)userTouchEnded:(id)tapPoint onView:(UIView*)aView;
@end
@interface SecondResponderWindow : UIWindow
@property (nonatomic, retain) UIView *viewToObserve;
@property (nonatomic, assign) id <SecondResponderWindowDelegate> controllerThatObserves;
@end
in SecondResponderWindow.m
在 SecondResponderWindow.m 中
//SecondResponderWindow.m
- (void)forwardTouchBegan:(id)touch onView:(UIView*)aView {
[controllerThatObserves userTouchBegan:touch onView:aView];
}
- (void)forwardTouchMoved:(id)touch onView:(UIView*)aView {
[controllerThatObserves userTouchMoved:touch onView:aView];
}
- (void)forwardTouchEnded:(id)touch onView:(UIView*)aView {
[controllerThatObserves userTouchEnded:touch onView:aView];
}
- (void)sendEvent:(UIEvent *)event {
[super sendEvent:event];
if (viewToObserve == nil || controllerThatObserves == nil) return;
NSSet *touches = [event allTouches];
UITouch *touch = [touches anyObject];
if ([touch.view isDescendantOfView:viewToObserve] == NO) return;
CGPoint tapPoint = [touch locationInView:viewToObserve];
NSValue *pointValue = [NSValue valueWithCGPoint:tapPoint];
if (touch.phase == UITouchPhaseBegan)
[self forwardTouchBegan:pointValue onView:touch.view];
else if (touch.phase == UITouchPhaseMoved)
[self forwardTouchMoved:pointValue onView:touch.view];
else if (touch.phase == UITouchPhaseEnded)
[self forwardTouchEnded:pointValue onView:touch.view];
else if (touch.phase == UITouchPhaseCancelled)
[self forwardTouchEnded:pointValue onView:touch.view];
}
It's not 100% conforms to what your were expecting - because your second responder view does not handle the touch event natively via -touchDidBegin: or so, and has to implement the SecondResponderWindowDelegate. However this hack does allow you to handle touch events on additional responders.
它不是 100% 符合您的期望 - 因为您的第二个响应者视图不会通过 -touchDidBegin: 本地处理触摸事件,并且必须实现 SecondResponderWindowDelegate。但是,此 hack 确实允许您处理其他响应者的触摸事件。
This method is inspired by and extended from MITHIN KUMAR's TapDetectingWindow
此方法受 MITHIN KUMAR 的TapDetectingWindow启发并扩展
回答by shannoga
If what you need is to differ between a touch and a scroll then you can test if touches has been moved. If this is a tap then touchHasBeenMoved will not be called then you can assume this is a touch.
如果您需要区分触摸和滚动,那么您可以测试触摸是否已移动。如果这是一个点击,那么 touchHasBeenMoved 将不会被调用,那么你可以假设这是一个触摸。
At this point you can set a boolean to indicate if a movnent accoured and set this Boolean as a condition in your other methods.
在这一点上,您可以设置一个布尔值来指示是否有移动,并将此布尔值设置为其他方法中的条件。
I am on the road but if that's what you need I will be able to explain better later.
我在路上,但如果这就是你需要的,我稍后会解释得更好。