iOS开发之UIResponder

UIResponder 的官方文档

一、介绍

在iOS中UIResponder类是专门用来响应用户的操作处理各种事件的,包括触摸事件(Touch Events)、运动事件(Motion Events)、远程控制事件(Remote Control Events,如插入耳机调节音量触发的事件)。
UIApplication、UIView、UIViewController这几个类是直接继承自UIResponder;
UIWindow是直接继承自UIView的一个特殊的View,所以这些类都可以响应事件。
当然我们自定义的继承自UIView的View以及自定义的继承自UIViewController的控制器都可以响应事件。
iOS里面通常将这些能响应事件的对象称之为响应者。

二、UIResponder基本交互接口

1. 触摸事件:

// 当用户触摸到屏幕时调用方法:
func touchesBegan(Set<UITouch>, with: UIEvent?)
// 当用户触摸到屏幕并移动时调用此方法:
func touchesMoved(Set<UITouch>, with: UIEvent?)
// 当触摸离开屏幕时调用此方法:
func touchesEnded(Set<UITouch>, with: UIEvent?)
// 当触摸被取消时调用此方法:
func touchesCancelled(Set<UITouch>, with: UIEvent?)
// Apple Pencil 产生的 touch 事件的部分信息(如 Pencil 的方向等)传递到 iPad 或 iPhone 上会有一定的延时。
func touchesEstimatedPropertiesUpdated(Set<UITouch>)

2. 运动事件:

// 开始运动时调用方法:
func motionBegan(_ motion: UIEvent.EventSubtype, with event: UIEvent?)
// 结束运动时调用方法:
func motionEnded(_ motion: UIEvent.EventSubtype, with event: UIEvent?)
// 取消运动时调用方法:
func motionCancelled(_ motion: UIEvent.EventSubtype, with event: UIEvent?)

3. 远程控制事件:

func remoteControlReceived(with event: UIEvent?)

我们每个 UIKit 的组件,都有自己对于这些方法的默认实现。
为什么要了解它呢,比如你遇到了 UI 上面的一些疑难杂症,知道了这个机制,你就能更有逻辑的去调试问题。

三. Responder Chain机制

1. 概念

请添加图片描述

这里涉及到几个概念, 以触摸事件来说,

  1. 首先 UIKit 通过 hitTest(_:with:) point(inside:with:) 方法的组合,先找到在视图层级中最深的,并且坐标在当前触摸范围内的 View。 这个 View 就被视作 First Responder,这个过程我们这里不详细展开,有机会再和大家聊。
  2. 确定好这个 First Responder 后,就会开始 Responder Chain 的一系列操作。 比如上面图片中,如果我们点击 UITextField, UIKit 先会通过刚说的匹配机制,找到 UITextField 作为 First Responder。 然后给他发送一个触摸事件, 这时候 UITextField 自己来判断能否处理这个触摸,如果它自己能处理就直接处理。 如果处理不了,就会把这个事件抛给上一级。
  3. 这个上一级也就是 UIResponder 的这个属性:
var next: UIResponder? { get }
  1. UIKit 对触摸事件的默认行为是如果它自己处理不了当前事件,它会转发给 parent view, 如果它没有 parent view,也就是说它是所在 View Controller 中的 root view, 它就把事件转发给 View Controller。 前面我们也说过了, UIViewController 也继承了 UIResponder。 包括 UIWindow,甚至 UIApplication 都实现了 UIResponder。

我们可以看到,上面这张图的事件,事件最远能够转发到 UIApplicationDelegate 这个级别。 但前提是我们为 UIApplicationDelegate 实现了 UIResponder 接口。 默认情况下 UIApplicationDelegate 是没有 UIResponder 实现的。

除了这个默认实现的一层层向上转发的流程, 其实我们是可以修改的。 修改方法也简单, 就是我们自己去实现 next 这个属性。 返回我们想转发到的实例就可以了。

2. 应用实例

class MyView: UIView {

    var targetView: UIView?

    override var next: UIResponder? {
        get {

            return targetView
        }
    }

}

上面这个 MyView 重载了 next 属性, 并且返回它自己设置的一个 targetView 属性。 然后我们进行一下设置:

class ViewController: UIViewController {

    override func viewDidLoad() {

        super.viewDidLoad()

        let myView = MyView(frame: CGRect(x: 0, y: 0, width: 200, height: 200))
        myView.backgroundColor = UIColor.red
        self.view.addSubview(myView)

        let targetView = TargetView(frame: CGRect(x: 0, y: 300, width: 200, height: 200))
        targetView.backgroundColor = UIColor.blue
        self.view.addSubview(targetView)

        myView.targetView = targetView

    }

}

首先创建了一个 MyView 实例,设置它为红色背景。 也就是我们上面定义的。 然后又创建了一个 TargetView 实例,设置它为蓝色背景,下面会贴出它的代码。 然后把这个实例设置给 myView.targetView

再来看看 TargetView 的代码:

class TargetView: UIView {

    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        print("touch")
        self.backgroundColor = UIColor.red
    }

}

也很简单,就是实现了 touchesBegan 方法,输出一行字,然后把自己设置成红色背景。

两个关注点, 首先 MyViewTargetView 都是被添加到 ViewControllerself.view 中的, 它们不存在父节点关系,也就是说按照 Responder Chain 触摸事件的默认行为, 谁也不是谁的 Next Responder

但是我们重载了 MyViewnext 属性, 将它强制返回为 targetView 的实例。这样最终运行的效果就是这样。

MyView 中,没有实现任何触摸事件的方法, 所以它自己处理不了。 就会通过 next 属性找到它的 Next Responder,我们强制指定了 Next ResponderTargetView , 并且我们又在 TargetView 中实现了 touchesBegan 方法。

实际的效果就是,我们触摸 MyView 的视图区域, 实际上是 TargetView 在响应这个触摸事件,并且把自己设置成了红色。

四、管理响应链

UIResponder提供了几个方法来管理响应链,包括让响应对象成为第一响应者、放弃第一响应者、检测是否是第一响应者以及传递事件到下一响应者的方法。

// 1、负责传递事件的方法是nextResponder
var next: UIResponder? { get }
// 2、判定一个响应对象是否是第一响应者
var isFirstResponder: Bool { get }
// 3、将一个响应对象作为第一响应者
func becomeFirstResponder() -> Bool
// 4、判定一个响应对象成为第一响应者的一个前提是它可以成为第一响应者
open var canBecomeFirstResponder: Bool { get } // default is NO
// 5、与上面两个方法相对应的是响应者放弃第一响应者的方法
open func resignFirstResponder() -> Bool
open var canResignFirstResponder: Bool { get } // default is YES
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值