点击事件处理方式的关系
3 种处理点击事件的方法:
手势
touchBegin 系列方法
UIControl 的 addTarget... 系列方法
问题: 1. 它们之间是否会互相干扰 2. 谁会屏蔽谁 3. UIControl 的时间处理本质上是不是还是 touch 方法 4. 手势的原理是什么? 它本质是 touch 方法还是其他的处理系统? 5. 如果有互相屏蔽, 该怎么避免
测试以及解释
1. 不在同一条响应链上
如果这 3 种方式作用在不同的 view 上, 而这些 view 之间没有上下级的关系, 那么就不存在互相干扰的问题.
点击操作需要找到响应者, 确立响应链, 不在同一条链上, 那么某一个 view 响应时, 完全不会联系到其他的 view.
2.control 和普通 view 的 touch 同一条响应链上
2 种方式在不同的 view 上, 但这些 view 之间具有上下级关系, 那么它们就处在同一条响应链上.
现在有 3 个事件:
control 自身的 touch 方法
control 的 addTarget 添加的事件
普通 view 的 touch 方法
测试情况:
control 在下时, addTarget 事件不响应, 两个 view 的 touch 方法都响应, 而且 control 的 touch 方法是由上层的 view 转发过去的.
control 在上时, 响应 control 的 touch 和 addTarget 事件; 如果去掉 addTarget 事件, 就只有 control 的 touch 响应
总结:
touch 事件只会传递给 hitview, 而 hit-test view 可能会转发给它的 nextResponder
普通 view 默认转发, 而 control 不会转发
- The hit-test view is given the first opportunity to handle a touch event. If the hit-test view cannot handle an event, the event travels up that view's chain of responders
- The responder chain is a series of linked responder objects. It starts with the first responder
A responder object is an object that can respond to and handle events.
If the first responder cannot handle an event, it forwards the event to the next responder in the responder chain.
注意 responder chain 包含的是 responder objects, 而 responder objects 指的是那些可以处理事件的对象
但最后一段又说 first responder 可能处理不了事件, 真是矛盾.
所以最可能的就是: 在概念上 hit-test view 就是 First Responder, 也就是第一个检测是否可以处理事件的, 而你从代码上, 比如调用 isFirstResponder 是得不到 true 的.
按照响应链的逻辑, 最重要的一点是一个 responder 是否可以处理事件, 可惜这里却没有这个方法. 甚至在测试例子里 canBecomeFirstResponder 和 becomeFirstResponder 都没被调用过.
所以可能的猜测是: 把事件传递给上一个响应者的处理, 不是一个中心系统处理, 而是各个 view 内部自己决定的, 比如 UIView 默认上传, 而 UIControl 默认不上传.
假设有开放是否可以处理事件的方法, 那么会更方便:
想把事件传递给下一个 responder, 只要 return false 就可以, 如果想截住事件, 就 return true.
3. control 和 touch 在同一个 view 上测试
重写 control 的 touch 方法, 导致 control 的的事件无法响应, 这个说明了 control 的事件本质是 touch 方法
重写的 touch 方法如果调用 super,control 的事件就会重新响应.
结论: control 的 addTarget 系列方法的处理本质还是 touch 系列方法, 在这个基础上加工.
2.1 手势和其他两个共存测试
普通 view 和手势, 不管哪个在上面, 都是手势和 touch 方法同时响应, 而且手势在后面 (可能是手势需要判断类型, 需要多次触碰才能知道, 才会响应, 所以延时了)
手势和按钮一起, 手势一定响应, 按钮在上面时会响应 touchDown, 但不会响应 touchUpInside
手势和 touch 方法在同一个 view 上时, 测试情况跟第 1 点一样
手势跟 control 方法在同一个 view 上时, 手势会起作用, control 的 addTarget 方法会响应 touchDown, 但不会响应 touchUpInside
问题: 手势的原理到底是什么 2. 响应链跟它们有什么关系
Gesture Recognizers 与触摸事件分发
手势跟响应链是不同的系统, 对于手势和 touch 方法的影响, 着重理解手势的 3 个属性:
cancelsTouchesInView
, 手势识别成功, 会给 hit-view 调用 touchesCancelled 中断 view 的事件处理
delaysTouchesBegan, 手势识别成功之前, 不会给 hit-view 发送触碰信息, 等到手势识别结束, 如果成功, 则还是不发送, 识别失败, 照常发送.
delaysTouchesEnded 的逻辑和上一个类似, 延迟 end 信息, 手势识别成功不发送, 识别失败发送. 但实际上双击手势符合这个, 但滑动手势不符合, 它识别成功了还是会发送.
再回去分析测试效果:
因为 delaysTouchesBegan 默认为 false, 所以 touchBegin 照常响应, 所以普通 view 的 touch 和手势都响应了
control 在下, 这时手势 view 是 hit-test view, 手势响应, 很正常. control 不是所以 control 的 addTarget 不响应. control 在上时, control 响应这个也很正常, 但手势还是会响应, 而且手势的 view 和 control 隔了多个层级还是响应, 手势会截取和影响它所有的子 view 的 touch 事件, 而且它不是完全按照响应链的逻辑来的, 手势的 view 可以不是第一响应者, 但还是需要在响应链里, 可以是第一响应者的第 N 个父视图. 然后 delaysTouchesBegan 默认 false, 所以 touchDown 会响应, 但
cancelsTouchesInView
默认 true, 所以手势识别成功后, 传送了 touchesCancelled, 导致按钮事件处理取消, 所以 touchUpInside 不响应. touchUpInside 是手指拿起来时判断的, 这时按钮的处理已经取消了.
和第一点相同
唯一的区别是, 现在只有一个 view, 它一定是 hit-test view, 相比第 3 点, 不会出现按钮在下没响应的情况, 其他的都一样.
系统的文档搜索不到了, Cocoa 版在 Cocoa Event Handling Guide
总结
手势是特殊的, 分开考虑
control 的 addTarget 系列方法本质是 touchBegin 系列方法的加工, 都属于响应链体系
响应链体系分两步: 第一步找到 hit-test view, 第二步从 hit-test view 沿着响应者链找到处理事件的响应者. 第一步是没有什么疑问的, 第二步关于谁是 First Responder, 什么时候会转发,"是否可以处理这个事件" 这个决定性问题的判断标准是什么等都有疑问.
对于手势:
它不仅影响它附在的那个 view, 还影响这个 view 图层树里所有的子 view.
影响手段就抓住理解手势的 3 个属性:
cancelsTouchesInView ``delaysTouchesBegan ``delaysTouchesEnded
control 的 addTarget 系列方法, 注意不同 event 的区别, 如 touchDown 在按下时就决定了, 而 touchUpInside 和 touchUpOutside 是放手时才决定
互相影响就只有手势影响其他两个, 通过调节 3 个属性来达到想要的效果. 其他两个就按照响应链逻辑, 哪个在上面哪个起作用.
来源: http://www.jianshu.com/p/fcb816993fe4