容器视图附带的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

查看:79
本文介绍了容器视图附带的Tap Gesture Recognizer不会阻止容器视图中按钮的触摸事件,但会阻止工具栏按钮的触摸事件的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

所以我有一个具有容器视图的视图控制器.容器视图嵌入有导航控制器,该导航控制器也是视图控制器的父控制器.故事板如下:

视图控制器( 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和更高版本中,默认控制动作可防止手势识别器行为重叠.例如,按钮的默认操作是单击.如果您在按钮的父视图上附加了一个点击手势识别器,并且用户点击了该按钮,则该按钮的操作方法将接收触摸事件,而不是手势识别器.

这似乎可以解释为什么 UIButton 能够抢占点击手势识别器的原因,但却没有明确说明工具栏按钮.

如果打印出视图层次结构,您会发现使用 UIToolbarButton 表示工具栏按钮,这是直接从 UIControl 继承的私有类.根据我们的观察,我们假设 UIToolbarButton 不会像公共 UIControl 子类那样抢占手势识别器.当我滑动其 touchesCancelled:withEvent:方法时,我发现它在轻击手势识别器触发后被调用,这似乎是基于《 iOS事件处理指南》所期望的:手势识别器的手势识别器"他们指出:获得第一个识别触摸的机会"部分:

...如果手势识别器识别出触摸手势,则窗口将永远不会将触摸对象传递到视图,并且还会取消先前发送给视图的属于该识别序列的任何触摸对象.

您可以通过几种不同的方式来修改此行为,而选择的方式将取决于最终目标.如果要允许在工具栏上进行触摸,则可以检查发送到手势识别器的代表的 gestureRecognizer:shouldReceiveTouch: UITouch 是否在工具栏的框架内,并返回 NO(如果是).专门阻止对 UIButton 的触摸可能需要子类化,但是如果您想阻止对mainViewController的子视图控制器的所有触摸,则可以在其容器视图上添加透明视图.

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(mainViewController) --> navigation controller --> view controller(contentViewController)

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 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

I added a Tap Gesture Recognizer to the container view in 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:

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:

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")

...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 UIButton is able to preempt the tap gesture recognizer, but it doesn't say anything explicit about the toolbar button.

If you print out the view hierarchy you'll find that the toolbar button is represented using a 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:

...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 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.

这篇关于容器视图附带的Tap Gesture Recognizer不会阻止容器视图中按钮的触摸事件,但会阻止工具栏按钮的触摸事件的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆