1. 响应者 UIResponder
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中的事件分类
3. 事件分发机制
注意:运动事件相关的加速度计、陀螺仪、磁强计都不属于响应者链。而是由CoreMotion传递事件给你指定的对象。Core Motion
分发
由视图层级的底层向上分发
UIApplication -> window -> viewcontroller -> view ....
响应
找到合适的view后进行处理,如果这个view不进行处理,就按照分发顺序回传
Responder chains in an app 来自官网文档
小结
发生了触摸或其他事件后,系统将事件发送到UIApplication管理的事件队列中,UIApplication从队列中取出最前面的事件分发下去。通常先发送给
keywindow
按照视图层级,从下层向上层发送(由window到view )
-
如果找到了合适处理事件的控件,调用
touchbengin
等方法合适的控件 如果调用了
super touch...
等方法事件会沿着响应链向下传递,传递给下一个响应者,这个响应者来调用touch begin...
注意: 如果父视图不接收触摸事件,那么子视图也不能接收到
如果没有找到合适的控件来处理事件,就回传给
window
,如果window
也不进行处理,传给UIApplication
,如果UIApplication
不能处理,就抛弃这个事件
4. 如何找到适合的控件来处理事件
利用hitTest 找到控件
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView?
在hitTest内部会判断这个控件是否能响应事件
那么控件是否能响应事件呢?
isUserInteractionEnabled
为 falseisHidden
为 truealpha
值为0.0 ~ 0.01
上述三种情况都不能响应事件
如果可以响应事件,那么判断事件是否发生在控件的内部
可以通过point inside方法来判断
open func point(inside point: CGPoint, with event: UIEvent?) -> Bool
返回false ,那么hitTest 就返回nil
返回true就遍历子控件,从后往前(先取subviews.last
),某个子控件返回了true
,hitTest
就返回这个子控件,如果子控件没有适合响应(都返回了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];
}