iOS 使用响应者和响应者链处理事件

 

内容概览

  • 综述
  • 确定一个事件的第一响应者(First Responder)
  • 确定哪个响应者包含一个触控事件
  • 改变响应者链
  • 在视图(UIView)中处理触控事件

 
 


综述

 

iOS 应用使用响应者对象接收和处理事件。一个响应者对象是 UIResponder 类的实例,常见的子类包括: UIView, UIViewController, UIApplication。响应者接收原始的事件数据,并且必须对其进行 处理 或者将其 转发给另一个响应者对象。当你的应用接收到一个事件时,UIKit 自动将这个事件传递给最适合处理这个事件的响应者对象 —— 第一响应者(first responder)。

未被处理的事件会在响应链中的响应者之间传递,这是应用中的响应者对象的动态配置。

上图展示了一个应用中的响应者,还显示了事件如何按照响应者链从一个响应者传递到下一个响应者。

如果 text field 不处理事件,则 UIKit 会将事件发送到 text field 的 super view 对象,然后是 window 的 root view。
从 root view 开始,响应链在将事件传递到 window 之前,先传递到 root view 所属的 view controller。
如果 window 无法处理事件,则 UIKit 会将事件传递给 UIApplication 对象,如果该 UIApplication 对象是 UIResponder 实例并且还不是响应者链的一部分,则可能传递给应用程序委托。

 
 


确定一个事件的第一响应者(First Responder)

 

UIKit 根据事件的类型将对象指定为事件的第一响应者。

事件类型 第一响应者
触控事件 发生触控事件的视图
按下设备的物理按键触发的事件 有焦点的对象
摇晃动作事件 由 UIKit 或者开发者指定
远程控制事件 由 UIKit 或者开发者指定
编辑菜单消息 由 UIKit 或者开发者指定

与加速度计、陀螺仪和磁力计有关的运动事件不遵循响应程序链。
相反,Core Motion 将这些事件直接传递到指定的对象。
有关更多信息,请参见 Core Motion Framework

 

定义事件类型的代码:

    public enum EventType : Int {
        // 点击屏幕相关的事件
        case touches
        // 设备移动相关的事件,如:用户摇晃设备
        case motion
        // 来自控制设备多媒体的外部配件,如:耳机
        case remoteControl
        // 按下设备的物理按键
        @available(iOS 9.0, *)
        case presses
    }

控件(UIControl)使用动作(action)消息直接与其关联的目标对象进行通信。

    // 为特定事件添加 target/action。你可以多次调用,也可以为特定事件指定多个 target/action
    // 当 target 为nil时,事件沿着响应者链传递。action 可能会包括发送者和事件的顺序
    // action 不能为NULL。target 不会被强引用
    func addTarget(_ target: Any?, action: Selector, for controlEvents: UIControl.Event)

当用户与控件交互时,控件会将操作消息发送到其目标对象(target)。
动作消息不是事件,但它们仍可以利用响应者链。当控件的目标对象为 nil 时,UIKit 从目标对象开始并遍历响应者链,直到找到实现适当操作方法的对象为止。
例如,UIKit 编辑菜单使用此行为来搜索响应者对象,这些对象实现了诸如 cut(_:), copy(_:), or paste(_:) 之类的方法。

手势识别器(Gesture recognizer) 会在视图处理事件之前接收触摸(touch)和按下(press)事件。
如果视图的手势识别器无法识别一系列触摸,则 UIKit 会将触摸发送给视图。
如果视图无法处理触摸,则 UIKit 会将其向上传递到响应者链。
有关使用手势识别器处理事件的更多信息,请参见处理 Handling UIKit Gestures

 
 


确定哪个响应者包含一个触控事件

 

UIKit 使用基于视图的点击测试来确定触摸事件发生的位置。
具体来说,UIKit 将触摸位置与视图层次结构中视图对象的边界(bounds)进行比较。
UIViewfunc hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? 方法遍历视图层次结构,查找包含指定触摸的最深的子视图,该子视图会成为触摸事件的第一响应者。

 

如果触摸位置在视图范围(bounds)之外,则 func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? 方法将忽略该视图及其所有子视图。因此,如果视图的 clipsToBounds 属性为 false,则即使该视图正好包含触摸,也不会返回该视图范围(bounds)之外的子视图。
有关点击测试行为的更多信息,请参见UIView中关于 hitTest(_:with:) 方法的讨论。

 

func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? 方法的文档:

  • 讨论解析:

此方法通过调用每个子视图的 point(inside:with:) 方法来遍历视图层次结构,以确定哪个子视图应接收触摸事件。

// 检测该点是否在接收者的 bounds 范围内
func point(inside point: CGPoint, with event: UIEvent?) -> Bool

如果 point(inside:with:) 返回 true,则将遍历子视图的视图层次结构,直到找到最前面的包含指定点的视图。如果视图不包含该点,则将忽略其视图层次结构的分支。
你很少需要自己调用此方法,但是你可以重写此方法以在子视图中隐藏触摸事件。

此方法将忽略 隐藏的禁用用户交互的alpha值小于0.01 的视图对象。
确定点击时,此方法不会考虑视图的内容。即使指定点位于该视图内容的透明部分中,该视图仍然可以被返回。

位于接收者范围之外的点,即使实际上位于接收者子视图内,也不会被点击测试匹配。如果当前视图的 clipsToBounds 属性设置为 false,并且受影响的子视图超出了视图的范围,则可能发生这种情况。

 
 

发生触摸时,UIKit 会创建一个 UITouch 对象并将其与视图关联。
随着触摸位置或其他参数的更改,UIKit 会使用新信息更新相同的UITouch对象。但唯一不变的属性是视图。 (即使触摸位置移到原始视图之外,UITouch 对象的 view 属性中的值也不会更改。)
触摸结束时,UIKit 会释放该 UITouch 对象。

 
 


改变响应者链

 

var next: UIResponder?

您可以通过重写响应者对象的 next 属性来更改响应者链。
如果重写,下一个响应者将会是你返回的对象。

许多 UIKit 类已经重写此属性并返回特定的对象,比如:

  • UIView 对象。如果 view 是 view controller 的 root view,则下一个响应者是 view controller;否则,下一个响应者是 view 的 super view。

  • UIViewController 对象

    • 如果 view controller 的 view 是 window 的 root view,则下一个响应者是 window 对象。

    • 如果 view controller B 是由 view controller A present的,则下一个响应者是 view controller A。

  • UIWindow 对象。window 的下一个响应者是 UIApplication 对象。

  • UIApplication 对象。下一个响应者是 app delegate,但仅当 app delegate 是 UIResponder 的实例且不是 view,view controller 或 UIApplication 对象本身时,才是下一个响应者。

 
 


在视图(UIView)中处理触控事件

 

如果你不打算在 UIView 中使用 UIGestureRecognizer 来处理触控事件,你可以使用 UIView 本身来处理。
因为 UIView 继承于 UIResponder,所以它可以处理多点触控事件和其他类型的事件。

UIKit 确定一个事件发生在某个 UIView 上时,它会调用该 view 的 touchesBegan:withEvent:, touchesMoved:withEvent:, 或者 touchesEnded:withEvent: 等方法。

你在 UIView 中重写的这些方法将处理点击事件处理过程中不同的阶段。

点击事件的不同阶段,如下图所示:

当手指(或者苹果笔)点击屏幕时,UIKit 会创建一个 UITouch 对象,然后将其触控位置设置为合适的点,将其 phase 属性设置为 UITouchPhaseBegan
当同一个手指在屏幕上移动时,UIKit 会更新该 UITouch 对象的触控位置并将 phase 属性设置为 UITouchPhaseMoved
当手指抬起并离开屏幕,UIKit 会将 phase 属性设置为 UITouchPhaseEnded,然后点击事件结束。

类似地,系统可能会在任何时候取消正在进行的点击事件。比如,电话呼入可以打断应用的执行过程。
这时,UIKit 会通过调用 touchesCancelled:withEvent: 方法通知你的 view。
你可以通过该方法来清理 view 中的数据结构。

UIKit 会为每个点击屏幕的新手指创建 UITouch 对象。这些 UITouch 对象会和当前的 UIEvent 对象一起被传递。UIKit 可以区分来自手指和苹果笔的触控,所以你可以对它们做不同的处理。

默认情况下,一个 view 只接收第一个 UITouch 和相应的事件,即使不只一个手指点击了屏幕。
如果需要接收多个触控,你需要将 multipleTouchEnabled 设置为 true

 
 


参考内容:
Using Responders and the Responder Chain to Handle Events
Handling Touches in Your View

 
 

转载请注明出处,谢谢~

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,362评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,330评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,247评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,560评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,580评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,569评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,929评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,587评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,840评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,596评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,678评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,366评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,945评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,929评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,165评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,271评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,403评论 2 342

推荐阅读更多精彩内容