我希望我的UIScrollView及其子视图能够接收子视图中的所有触摸事件。每个人都可以以自己的方式回应。
或者,如果将点击手势转发到子视图,一切都会很好。
很多人都在这个一般领域挣扎。以下是许多相关问题中的一些:
How does UIScrollView steal touches from its subviews
How to steal touches from UIScrollView?
How to Cancel Scrolling in UIScrollView
顺便说一句,如果我在滚动视图中覆盖hitTest:withEvent:,只要userInteractionEnabled为YES,我就会看到触摸。但这并没有真正解决我的问题,因为:
1)此时,我不知道是否是一个水龙头 2)有时我需要将userInteractionEnabled设置为NO。
编辑:澄清,是的,我想以不同于平底锅的方式对待水龙头。点击应由子视图处理。可以通过滚动视图以通常的方式处理平移。
答案 0 :(得分:38)
首先,免责声明。如果您在userInteractionEnabled
上将NO
设置为UIScrollView
,则不会将触摸事件传递给子视图。据我所知,除了一个例外,没有办法解决这个问题:拦截UIScrollView
超级视图上的触摸事件,并将这些事件专门传递给UIScrollView
的子视图。说实话,我不知道你为什么要这样做。如果您想要禁用特定的UIScrollView功能(例如......好吧,滚动),您可以轻松地做到这一点,而无需禁用UserInteraction。
如果我理解您的问题,您需要通过传递给子视图的UIScrollView 和来处理点击事件?在任何情况下(无论手势是什么),我认为您正在寻找的是协议UIGestureRecognizerDelegate
中的协议方法gestureRecognizer:shouldRecognizeSimultaneouslyWithGestureRecognizer:。在您的子视图中,无论您拥有什么样的手势识别器,都可以在手势识别器上设置一个委托(可能是在第一个位置设置UIGestureReconginzer
的任何类)。覆盖上述方法并返回YES
。现在,这个手势将被识别,以及可能“偷走”手势的任何其他识别器(在您的情况下,轻击)。使用此方法,您甚至可以对代码进行微调,以便仅将某些类型的手势发送到子视图,或仅在某些情况下发送手势。它给你很多控制权。请务必阅读有关该方法的内容,尤其是此部分:
在识别手势时调用此方法 gestureRecognizer或otherGestureRecognizer会阻止 其他手势识别器识别其手势。注意 返回YES保证允许同时识别; 另一方面,返回NO并不能保证会阻止 同时识别,因为其他手势识别器 代表可以返回YES。
当然,有一点需要注意:这仅适用于手势识别器。因此,如果您尝试使用touchesBegan:
,touchesEnded
等来处理触摸,则可能仍会遇到问题。当然,您可以使用hitTest:
将原始触摸事件发送到子视图,但为什么呢?为什么在UIView
中使用这些方法处理事件时,您可以将UIGestureRecognizer
附加到视图并免费获得所有这些功能?如果您需要以没有标准UIGestureRecognizer
可以提供的方式处理的触摸,子类 UIGestureRecognizer
并处理那里的触摸。这样,您就可以获得UIGestureRecognizer
的所有功能以及您自己的自定义触摸处理功能。我真的认为Apple希望UIGestureRecognizer
替换大多数(如果不是全部)开发人员在UIView
上使用的自定义触摸处理代码。它允许代码重用,并且在减轻哪些代码处理触摸事件时处理起来要容易得多。
答案 1 :(得分:3)
我不知道这是否可以帮到你,但我遇到了类似的问题,我想让scrollview处理双击,但是转发单击到子视图。以下是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];
}
}
当然,应该更改有效代码,但此时您应该拥有决定是否必须转发事件所需的所有信息。您可能需要在另一个方法中实现这个,如touchesMoved:withEvent:。
希望这可以提供帮助。
答案 2 :(得分:3)
我遇到了同样的问题,但是在UIPageViewController
内有一个滚动视图,所以必须稍微处理一下。
通过将cancelsTouchesInView
上的每个识别器的UIScrollView
属性更改为false,我能够接收到UIPageViewController
内按钮的触摸。
我是通过将此代码添加到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
}
答案 3 :(得分:0)
如果您需要在触摸和滚动之间有所不同,那么您可以测试触摸是否已被移动。如果这是一个点击,那么将不会调用touchHasBeenMoved然后你可以认为这是一个触摸。
此时,您可以设置一个布尔值来指示是否有一个movnent,并将此布尔值设置为其他方法中的条件。
我在路上,但如果这就是你需要的,我将能够在以后更好地解释。
答案 4 :(得分:0)
实现目标的一种hackish方式 - 不是100%完全 - 是子类化UIWindow并覆盖 - (void)sendEvent:(UIEvent *)事件;
一个简单的例子:
在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
在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];
}
这不是100%符合您的预期 - 因为您的第二个响应者视图不能通过-touchDidBegin本地处理触摸事件:左右,并且必须实现SecondResponderWindowDelegate。但是,这个hack确实允许您处理其他响应者的触摸事件。
这种方法的灵感来自MITHIN KUMAR的TapDetectingWindow