iOS事件传递机制详解

1. 响应者 UIResponder

iOS事件传递机制详解_第1张图片
image-20200910100042036.png

Declaration

class UIResponder : NSObject

只有继承自UIResponder的类才可以接受和处理

作用

处理事件响应,更改inputview等

UIResponder 的几个属性

// 下一个响应者
var next: UIResponder? { get }
// 是否为第一响应者 比如我们处理键盘事件会用到 becomeFirstResponder,
// 收起键盘调用 resignFirstResponder
var isFirstResponder: Bool
// 是否能成为第一响应者
var canBecomeFirstResponder: Bool
// ...
var canResignFirstResponder: Bool

为什么Responder 可以处理事件

becomeFirstResponder()
resignFirstResponder()

// 告诉对象在view或window中发生了一个 或 多个 触摸
func touchesBegan(Set, with: UIEvent?)
// 一个或者多个触摸何时更改
func touchesMoved(Set, with: UIEvent?)
// 一个或者多个触摸何时结束
func touchesEnded(Set, with: UIEvent?)

// 一个或者多个触摸何时取消
// 当遇到系统事件而取消触摸时通知开发者
// 如 begin 后 做一个uiview 动画,系统alert 等。
func touchesCancelled(Set, with: UIEvent?)

下面是加速计事件的响应方法

func motionBegan(UIEvent.EventSubtype, with: UIEvent?)
func motionEnded(UIEvent.EventSubtype, with: UIEvent?)
func motionCancelled(UIEvent.EventSubtype, with: UIEvent?)

还有一堆事件就不写了

2. iOS中的事件分类

iOS事件传递机制详解_第2张图片
image-20200910102139165.png

3. 事件分发机制

注意:运动事件相关的加速度计、陀螺仪、磁强计都不属于响应者链。而是由CoreMotion传递事件给你指定的对象。Core Motion

分发

由视图层级的底层向上分发

UIApplication -> window -> viewcontroller -> view ....

iOS事件传递机制详解_第3张图片
1306128-ec979c527e4f9252.png

响应

找到合适的view后进行处理,如果这个view不进行处理,就按照分发顺序回传

Responder chains in an app 来自官网文档

iOS事件传递机制详解_第4张图片
f17df5bc-d80b-4e17-81cf-4277b1e0f6e4.png

小结

  1. 发生了触摸或其他事件后,系统将事件发送到UIApplication管理的事件队列中,UIApplication从队列中取出最前面的事件分发下去。通常先发送给keywindow

  2. 按照视图层级,从下层向上层发送(由window到view )

  3. 如果找到了合适处理事件的控件,调用touchbengin等方法

    合适的控件 如果调用了super touch...等方法事件会沿着响应链向下传递,传递给下一个响应者,这个响应者来调用touch begin...

    注意: 如果父视图不接收触摸事件,那么子视图也不能接收到

  4. 如果没有找到合适的控件来处理事件,就回传给window,如果window也不进行处理,传给UIApplication,如果UIApplication不能处理,就抛弃这个事件

4. 如何找到适合的控件来处理事件

利用hitTest 找到控件

override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? 

在hitTest内部会判断这个控件是否能响应事件

那么控件是否能响应事件呢

  • isUserInteractionEnabledfalse

  • isHiddentrue

  • alpha 值为0.0 ~ 0.01

上述三种情况都不能响应事件

如果可以响应事件,那么判断事件是否发生在控件的内部

可以通过point inside方法来判断

open func point(inside point: CGPoint, with event: UIEvent?) -> Bool

返回false ,那么hitTest 就返回nil

返回true就遍历子控件,从后往前(先取subviews.last),某个子控件返回了truehitTest 就返回这个子控件,如果子控件没有适合响应(都返回了false),那么就返回自己

上代码表述一下hitTest 内部实现 看文字可能不清晰

func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
    // 先要满足上述三种情况
    if self.isUserInteractionEnabled == false || self.isHidden == true || self.alpha <= 0.01{
        return nil
    }
    // 如果不在自己的坐标范围内
    if self.point(inside: point, with: event) == false{
        return nil
    }
    // 没有关闭交互,没有隐藏,也没有透明到不行
    // 寻找子视图看看有没有更合适的,如果没有就返回自己
    for subView in self.subviews.reversed() {
        let subPoint = self.convert(point, to: subView)
        let fitView = subView.hitTest(subPoint, with: event)
        if fitView != nil{
            return fitView
        }
    }
    return self
}

5. UITouch的一些属性

// 触摸时所处的window
var window
// 触摸时所处的视图
var view 
// 短时间内点击屏幕的次数
var tapCount
//记录触摸时间产生或变化的时间,单位是秒
var timeStamp
// 触摸事件的状态
var phase

6. UIEvent

事件对象 用来记录事件的产生的时刻 和 类型

每产生一个事件,就会有一个UIEvent与之对应

class UIEvent : NSObject
UIEvent.EventType

case touches
case motion
case remoteControl
case presses
case hover 
case scroll
case transform

UIEvent还提供了可以获得某个view上的UITouch对象的属性

var allTouches: Set?
- (void)pushViewController:(UIViewController *)viewController animated:(BOOL)animated{
    
    if (self.viewControllers.count >= 1){
        viewController.hidesBottomBarWhenPushed = YES;
    }else{
        viewController.hidesBottomBarWhenPushed = NO;
    }
    
    [super pushViewController:viewController animated:animated];
}

你可能感兴趣的:(iOS事件传递机制详解)