当用户点击到页面上的视图:
1.系统(OS)检测到点击事件,将点击信息包装成UIEvent事件对象
2.系统将该事件对象放到当前活跃APP的事件队列之中
3.单例UIApplication从事件队列中拿到该事件对象,并将其传给key window
4.Key window调用hit-test去找出第一响应者.
a.hit-test调用pointInSide方法,如果pointInSide判断点击的点落在该视图之内,返回true,否则返回false
b.当返回true时,将该响应链入栈,hit-test去递归调用其子视图的hit-test方法
c.当返回false后,hit-test方法在该级返回nil,然后去调用该view的兄弟视图的hit-test方法
依此方法,直到找到最小的叶子节点(这并不代表页面里的所有的view的hit-test都会被执行)
5.通过这样的查找,会形成一个响应链条,即所有hit-test方法返回view(非nil)的对象都在该链中。然后,系统按后进先出的顺序测试响应链中的元素(即从找出的最小的叶子节点开始测试),看看哪一个能处理该点击事件。如果能处理,则该点击事件交由它处理,如果不能,继续去查看该链的下一个节点是否能响应,直到UIApplication对象,如果都不能响应,则该事件被放弃处理。
在自定义hit-test方法时需要注意:
1.hit-test方法最好不要返回self.(如果点击的是该视图上的子视图时,正常情况下会返回其子视图的view而不是自己)
2.pointInSide与hit-test的关系。(正常情况下,如果pointInSide返回false,则hit-test会返回nil。但如果你直接在hit-test里写死返回的东西,那其实pointInSide返回值就无意义,所以最终系统会看hit-test返回的view是否为nil,而不关心pointInSide的返回值)
3.在hit-test里最好不要把返回值写死(只要你点击,它几乎都会响应)
其hitTest方法更像是这样的:
func hitTest(_ point:CGPoint, with event:UIEvent?) ->UIView? {
var touchedView:UIView?
let result = self.point(inside: point, with: event)
if result == true {
for view in self.subviews{
touchedView = view.hitTest(point, with: event)
if touchedView != nil{
break
}
}
}
/*
用户override重写该方法,如果用户为touchedView赋非nil值,则该view仍将出现在响应链中
*/
return touchedView
}