容器视图附带的Tap Gesture Recognizer不会阻止容器视图中按钮的触摸事件,但会阻止工具栏按钮的触摸事件 [英] Tap Gesture Recognizer attached with container view does not block touch event of button in container view but blocks toolbar button's touch event
问题描述
所以我有一个具有容器视图的视图控制器.容器视图嵌入有导航控制器,该导航控制器也是视图控制器的父控制器.故事板如下:
视图控制器( mainViewController
)->导航控制器->视图控制器( contentViewController
)
您可以在下面看到情节提要的屏幕截图.
第一个箭头是从容器视图到导航控制器的嵌入序列.第二个箭头是表示 contentViewController
是导航控制器的根视图控制器的关系.
mainViewController
和 contentViewController
是相同类的对象,名为 testViewController
.它是UIViewController的子类.它的实现很简单.它只有三个 IBAction
方法,仅此而已.这是实现代码:
#import"TestViewController.h"@实现TestViewController-(IBAction)buttonTapped:(id)sender {UIAlertView * alert = [[UIAlertView alloc] initWithTitle:nil消息:@点击按钮"委托人:自己cancelButtonTitle:@确定"otherButtonTitles:nil];[警报显示];}-(IBAction)barButtonTapped:(id)发送者{UIAlertView * alert = [[UIAlertView alloc] initWithTitle:nil消息:@点击了条形按钮"委托人:自己cancelButtonTitle:@确定"otherButtonTitles:nil];[警报显示];}-(IBAction)viewTapped:(id)sender {UIAlertView * alert = [[UIAlertView alloc] initWithTitle:nil消息:@点击了视图"委托人:自己cancelButtonTitle:@确定"otherButtonTitles:nil];[警报显示];}@结尾
我在 mainViewController
的容器视图中添加了Tap Gesture Recognizer.轻按容器视图时,它将向 mainViewController
发送 viewTapped:(id)sender
消息.在 contentViewController
的根视图内部,有一个按钮,当点击该按钮时,该按钮会将 buttonTapped:(id)sender
消息发送到 contentViewController
.在 contentViewController
的工具栏中有一个条形按钮,当点击该按钮时,它会向 contentViewController
发送 barButtonTapped:(id)sender
消息.初始场景是 mainViewController
.当应用程序运行时,我发现只有条形按钮的触摸事件被阻止,触摸事件才由按钮正确处理.在Apple文档中,的响应者链遵循特定的传递路径"部分介绍了如何将触摸事件首先传递给被触摸的视图,然后遍历其所有超级视图,再到窗口,最后到应用程序本身.
项目视图层次的简化表示为:
mainViewController的根视图|mainViewController的容器视图(具有Tap Gesture Recognizer)||UINavigationController的根视图|||contentViewController的视图||||UIButton(按钮")|||UINavigationController的工具栏视图||||UIToolbarTextButton(项目")
...因此,当您点击按钮或工具栏按钮时,它们会在mainViewController的容器视图之前收到touch事件.
按钮事件触发而工具栏按钮未触发的原因似乎与 在iOS 6.0和更高版本中,默认控制动作可防止手势识别器行为重叠.例如,按钮的默认操作是单击.如果您在按钮的父视图上附加了一个点击手势识别器,并且用户点击了该按钮,则该按钮的操作方法将接收触摸事件,而不是手势识别器. 这似乎可以解释为什么 如果打印出视图层次结构,您会发现使用 ...如果手势识别器识别出触摸手势,则窗口将永远不会将触摸对象传递到视图,并且还会取消先前发送给视图的属于该识别序列的任何触摸对象. 您可以通过几种不同的方式来修改此行为,而选择的方式将取决于最终目标.如果要允许在工具栏上进行触摸,则可以检查发送到手势识别器的代表的 So I have a view controller which has a container view. The container view is embedded with a navigation controller which is also parent controller of a view controller. The storyboard is like this: view controller( You can see screenshot of storyboard in the below. The first arrow is a embed segue from container view to navigation controller. The second arrow is a relationship represents I added a Tap Gesture Recognizer to the container view in In the simple case, when a touch occurs, the touch object is passed
from the UIApplication object to the UIWindow object. Then, the window
first sends touches to any gesture recognizers attached the view where
the touches occurred (or to that view’s superviews), before it passes
the touch to the view object itself. I thought touch event will not pass to the button. This really confused me. Can someone explain this behavior? Thank you very much. Screenshot of the storyboard:
The Event Handling Guide for iOS: Event Delivery: The Responder Chain's "The Responder Chain Follows a Specific Delivery Path" section describes how touch events are passed first to the view that was touched, then up through all of its superviews, then to the window, and finally to the application itself. A simplified representation of your project's view hierarchy would be: ...so when you tap the button or the toolbar button, they receive the touch event before mainViewController's container view. The reason why the button's event fires and the toolbar button's doesn't appears to be related to Event Handling Guide for iOS: Gesture Recognizers' "Interacting with Other User Interface Controls" section: In iOS 6.0 and later, default control actions prevent overlapping gesture recognizer behavior. For example, the default action for a button is a single tap. If you have a single tap gesture recognizer attached to a button’s parent view, and the user taps the button, then the button’s action method receives the touch event instead of the gesture recognizer. That appears to explain why the If you print out the view hierarchy you'll find that the toolbar button is represented using a ...if the gesture recognizer recognizes a touch gesture, then the window never delivers the touch object to the view, and also cancels any touch objects it previously sent to the view that were part of that recognized sequence. There are a few different ways you could modify this behavior and the one you picked would depend on your end goal. If you wanted to allow touches on the toolbar you could check if the 这篇关于容器视图附带的Tap Gesture Recognizer不会阻止容器视图中按钮的触摸事件,但会阻止工具栏按钮的触摸事件的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋! UIButton
能够抢占点击手势识别器的原因,但却没有明确说明工具栏按钮. UIToolbarButton
表示工具栏按钮,这是直接从 UIControl
继承的私有类.根据我们的观察,我们假设 UIToolbarButton
不会像公共 UIControl
子类那样抢占手势识别器.当我滑动其 touchesCancelled:withEvent:
方法时,我发现它在轻击手势识别器触发后被调用,这似乎是基于《 iOS事件处理指南》所期望的:手势识别器的手势识别器"他们指出:获得第一个识别触摸的机会"部分: gestureRecognizer:shouldReceiveTouch:
的 UITouch
是否在工具栏的框架内,并返回 NO
(如果是).专门阻止对 UIButton
的触摸可能需要子类化,但是如果您想阻止对mainViewController的子视图控制器的所有触摸,则可以在其容器视图上添加透明视图.mainViewController
) --> navigation controller --> view controller(contentViewController
)contentViewController
is root view controller of the navigation controller.mainViewController
and contentViewController
are objects of the same class, named testViewController
. It is the subclass of UIViewController. Its implementation is simple. It only has three IBAction
methods, nothing else. Here is the implementation code:#import "TestViewController.h"
@implementation TestViewController
- (IBAction)buttonTapped:(id)sender {
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:nil
message:@"button is tapped"
delegate:self
cancelButtonTitle:@"OK"
otherButtonTitles:nil];
[alert show];
}
- (IBAction)barButtonTapped:(id)sender
{
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:nil
message:@"bar button is tapped"
delegate:self
cancelButtonTitle:@"OK"
otherButtonTitles:nil];
[alert show];
}
- (IBAction)viewTapped:(id)sender {
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:nil
message:@"view is tapped"
delegate:self
cancelButtonTitle:@"OK"
otherButtonTitles: nil];
[alert show];
}
@end
mainViewController
. It sends viewTapped:(id)sender
message to mainViewController
when the container view is tapped. Inside of the root view of contentViewController
, there is a button which sends buttonTapped:(id)sender
message to contentViewController
when tapped. And there is a bar button in the toolbar of contentViewController
which sends barButtonTapped:(id)sender
message to contentViewController
when tapped. The initial scene is mainViewController
. When the app is running, I found that only touch events of the bar button is blocked, touch event is handled correctly by the button. In Apple's documentation, Regulating the Delivery of Touches to Views, it says:
mainViewController's Root View
| mainViewController's Container View (has Tap Gesture Recognizer)
| | UINavigationController's Root View
| | | contentViewController's View
| | | | UIButton ("Button")
| | | UINavigationController's Toolbar View
| | | | UIToolbarTextButton ("Item")
UIButton
is able to preempt the tap gesture recognizer, but it doesn't say anything explicit about the toolbar button.UIToolbarButton
which is a private class that inherits directly from UIControl
. Based on our observations we would assume that UIToolbarButton
does not preempt gesture recognizers like the public UIControl
subclasses do. When I swizzled its touchesCancelled:withEvent:
method I found that it gets called after the tap gesture recognizer fires, which seems to be what you would expect based on Event Handling Guide for iOS: Gesture Recognizers's "Gesture Recognizers Get the First Opportunity to Recognize a Touch" section where they note:
UITouch
sent to the gesture recognizer's delegate's gestureRecognizer:shouldReceiveTouch:
was inside the toolbar's frame and return NO
if it was. Blocking touches to the UIButton
specifically would probably require subclassing, but if you wanted to block all touches to mainViewController's child view controllers you could add a transparent view over its container view.