【原】iOS触摸事件深度解析

概述

本文主要解析从我们的手指触摸苹果设备到最终响应事件的整个处理机制。本质上讲,整个过程可以分为两个步骤:

步骤1:找目标。在iOS视图层次结构中找到触摸事件的最终接受者;

步骤2:事件响应。基于iOS响应者链(Responder Chain)处理触摸事件

找目标

在找目标阶段所使用到的两大利器是UIView的 hitTest:withEvent: 以及 pointInside:withEvent: 方法。找目标的过程也称为hit-Testing。先来看一张图(注: 图来自MJ)比较直观:

下面解释一下处理原理:

1、手指触摸屏幕,这个动作被包装成一个UIEvent对象发送给当前活跃的UIApplication (Active Application),Application将该Event对象插到任务队列的末尾等待处理(先进先出,先来的先处理);

2、UIApplication单例将事件发送给APP的主Window(所有显示的view都添加在Window上);

3、主Window调用视图层次结构上逐级使用hit-Testing确认最终的响应目标,这个目标也称为hitTesting view。

在没有做任何重载操作的前提下,系统默认的hit-Testing的处理机制如下:

当前view调用自身的pointInside: withEvent:方法判断触摸点是否在自己范围内:

  • 若pointInside: withEvent:方法返回NO,则说明触摸点不在自己范围内,则 当前view的hitTest: withEvent:方法返回nil,当前view上的所有subview都不做判断。有点领导的意见一票否决的味道。
  • 若pointInside: withEvent:方法返回YES,则说明触摸点在自己的范围内。但无法判断是否在自己身上还是在subview的身上。此时,遍历所有的subviews,对每个subview调用hitTest方法。这里要注意,遍历的顺序是从当前view的subviews数组的尾部开始遍历。因此离用户最近的上层的subview会优先被调用hitTest方法。
  • 一旦hitTest方法返回非空的view,则被返回的view就是最终相应触摸事件的view,寻找hitTesting view的阶段到此结束,不再遍历。
  • 若当前view的所有subviews的hitTest方法都返回nil,则当前view的hitTest方法返回self作为最终的hitTesting view,处理结束。

以上就是第一阶段寻找响应view的机制。这里我们结合一个具体的例子再过一遍(图片引自技术哥的博客):

当用户点击ViewD所在的区域时会进行以下hit-Testing:

  • ViewA的pointInside返回YES,因为触摸点在其bounds内。遍历ViewA的两个subview;
  • ViewB的pointInside返回NO,因为触摸点不在其bounds内,ViewB的hitTest方法返回nil。而且发生一票否决,在ViewB上的所有subviews受到牵连将不再进行hit-Testing处理。ViewC的pointInside返回YES,因为触摸点在其bounds范围内,ViewC的hitTest方法返回默认处理,也就是 return [super hitTest:point withEvent:event]; 遍历ViewC的两个subview;
  • ViewD的pointInside返回YES,因为触摸点在其bounds范围内,且ViewD没有subview,因此hitTest方法返回其自己。hitTesting view找到,结束处理。

这里有几点需要强调:

1、hitTest方法调用pointInside方法;

2、hit-Testing过程是从superView向subView逐级传递,也就是从层次树的根节点向叶子节点传递;

3、遇到以下设置时,view的pointInside将返回NO,hitTest方法返回nil:

  • view.isHidden=YES;
  • view.alpah<=0.01;
  • view.userInterfaceEnable=NO;
  • control.enable=NO;(UIControl的属性)

hit-Testing过程用代码可以描述如下:

- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
    if (self.alpha <= 0.01 || !self.userInteractionEnabled || self.hidden) {
        return nil;
    }
    BOOL inside = [self pointInside:point withEvent:event];
    UIView *hitView = nil;
    if (inside) {
        NSEnumerator *enumerator = [self.subviews reverseObjectEnumerator];
        for (UIView *subview in enumerator) {
            hitView = [subview hitTest:point withEvent:event];
            if (hitView) {
                break;
            }
        }
        if (!hitView) {
            hitView = self;
        }
        return hitView;
    } else {
        return nil;
    }
}

  

事件响应

上一部分我们通过hit-Testing机制找到了hitTesting View,这个hitTesting View就是触摸事件的响应者Responder。在iOS系统中,能够响应并处理事件的对象称之为Responder Object,而UIResponder是所有responder的最顶层基类。当hitTesting view做完自己该做的动作后,可以根据需要将消息传给下一级响应者。那下一级响应者会是什么呢?这取决于iOS中的响应者链Responder Chain,如下图所示:

  • UIView的nextResponder属性,如果有管理此view的UIViewController对象,则为此UIViewController对象;否则nextResponder即为其superview
  • UIViewController的nextResponder属性为其管理view的superview.
  • UIWindow的nextResponder属性为UIApplication对象。
  • UIApplication的nextResponder属性为nil。

更具体的:

  1. 如果hit-test view或first responder不处理此事件,则将事件传递给其nextResponder处理,若有UIViewController对象则传递给UIViewController,传递给其superView。
  2. 如果view的viewController也不处理事件,则viewController将事件传递给其管理view的superView。
  3. 视图层级结构的顶级为UIWindow对象,如果window仍不处理此事件,传递给UIApplication.
  4. 若UIApplication对象不处理此事件,则事件被丢弃。

了解响应者链有时候可以帮我解决一些实际问题。我举个例子,我们知道,当提供给你一个ViewController你可以很容易得到它的view,一句代码的事情:

viewWanted = someViewController.view;

但如果反过来呢?当给你一个view,让你找到其所在的ViewController呢?这时候响应者链可以帮上忙了,代码如下:

@implementation UIView (FindController)
-(UIViewController*)parentController{
    UIResponder *responder = [self nextResponder];
    while (responder) {
	if ([responder isKindOfClass:[UIViewController class]]) {
		return (UIViewController*)responder;
	}
	responder = [responder nextResponder];
    }
    return nil;
}
@end

写在最后

这篇文章解析了iOS响应触摸事件的机制。或许你现在找不到这个知识的应用点,但是一旦你理解了,可以帮助你实现一些特别的需求,比如点击某个按钮,响应的却是另一个按钮;穿透某个view点击到view下面的view...  更有甚者,你可以用上面的知识解决不规则区域触摸问题(看我之前的文章)、不添加任何view就能扩大控件的可触摸区域等。天马行空,任我翱翔!

=======================================================

原创文章,转载请注明 编程小翁@博客园,邮件[email protected],欢迎各位与我在C/C++/Objective-C/机器视觉等领域展开交流!

欢迎跳转我的GitHub主页,关注我的开源代码。也欢迎你Star/Fork/Watch我的项目。

=======================================================

时间: 2024-11-03 05:29:57

【原】iOS触摸事件深度解析的相关文章

iOS触摸事件深度解析-备用

概述 本文主要解析从我们的手指触摸苹果设备到最终响应事件的整个处理机制.本质上讲,整个过程可以分为两个步骤: 步骤1:找目标.在iOS视图层次结构中找到触摸事件的最终接受者: 步骤2:事件响应.基于iOS响应者链(Responder Chain)处理触摸事件 找目标 在找目标阶段所使用到的两大利器是UIView的 hitTest:withEvent: 以及 pointInside:withEvent: 方法.找目标的过程也称为hit-Testing.先来看一张图(注: 图来自MJ)比较直观: 下

IOS触摸事件和手势识别

IOS触摸事件和手势识别 目录 概述 触摸事件 手势识别 概述 为了实现一些新的需求,我们常常需要给IOS添加触摸事件和手势识别 触摸事件 触摸事件的四种方法 -(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event 开始触摸所触发的方法 -(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event 移动时触发的方法 -(void)touchesEnded:(N

IOS 触摸事件分发机制详解

欢迎大家前往云+社区,获取更多腾讯海量技术实践干货哦~ 作者:MelonTeam 前言 很多时候大家都不关心IOS触摸事件的分发机制的实现原理,当遇到以下几种情形的时候你很可能抓破头皮都找不到解决方案: 某个点击消息由父视图来处理,子视图怎么把消息传递给父视图 这个按钮不灵敏,怎么扩大点击响应区域 怎么在一个页面处理手绘.表情拖动放缩.文本编辑三种消息 阅读本文,你会明白两个问题:IOS如何找到响应者.响应者是如何做出响应,明白这两个问题你就能解决类似上述的疑难杂症.通过控制Hit-test v

iOS触摸事件深入

转载自:http://www.cnblogs.com/wengzilin/p/4720550.html 概述 本文主要解析从我们的手指触摸苹果设备到最终响应事件的整个处理机制.本质上讲,整个过程可以分为两个步骤: 步骤1:找目标.在iOS视图层次结构中找到触摸事件的最终接受者: 步骤2:事件响应.基于iOS响应者链(Responder Chain)处理触摸事件 找目标 在找目标阶段所使用到的两大利器是UIView的 hitTest:withEvent: 以及 pointInside:withEv

iOS触摸事件

步骤1:找目标.在iOS视图层次结构中找到触摸事件的最终接受者: 步骤2:事件响应.基于iOS响应者链(Responder Chain)处理触摸事件 找目标 在找目标阶段所使用到的两大利器是UIView的 hitTest:withEvent: 以及 pointInside:withEvent: 方法.找目标的过程也称为hit-Testing.先来看一张图(注: 图来自MJ)比较直观: 下面解释一下处理原理: 1.手指触摸屏幕,这个动作被包装成一个UIEvent对象发送给当前活跃的UIApplic

iOS触摸事件应用举例

1. 触摸事件的类型 触摸事件的类型一共有四个,一次完整的触摸,至少包括开始和结束两个事件 1> 触摸开始,用手指(一根或者多根)按在屏幕上 2> 触摸移动,手指在屏幕上发生移动(有可能会发生) 3> 触摸结束,手指从屏幕上离开 4> 触摸被取消,因为系统事件(例如电话呼叫)一次触摸事件被取消 #pragma mark ***触摸开始 - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event #pragma m

IOS 触摸事件的处理

触摸事件的处理1.判断触摸点在谁身上: 调用所有UI控件的- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event 2.pointInside返回YES的控件就是触摸点所在的UI控件 3.由触摸点所在的UI控件选出处理事件的UI控件: 调用- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event

iOS 触摸事件与UIResponder(内容根据iOS编程编写)

触摸事件 因为 UIView 是 UIResponder 的子类,所以覆盖以下四个方法就可以处理四种不同的触摸事件: 1.  一根手指或多根手指触摸屏幕 - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event; 2.  一根手指或多根手指在屏幕上移动(随着手指的移动,相关的对象会持续发送该消息) - (void)touchesMoved:(NSSet<UITouch *>

iOS触摸事件总结

一.关于事件传递一些基础知识 1.UIView类是UIResponder的一个子类,因此能够接收用户和视图内容交互 时产生的触摸事件.触摸事件从发生触摸的视图开始,沿着响应者链进行传 递,直到最后被处理. 视图本身就是响应者,是响应者链的参与者,因此可以 收到所有关联子视图派发给它们的触摸事件. 2.在缺省情况下,视图每次只响应一个触摸动作.如果用户将第二个手 指放在屏幕上,系统会忽略该触摸事件,而不会将它报告给视图对象.如果您 希望在视图的事件处理器方法中跟踪多点触摸手势,则需要重新激活多点触