需求三: 点击橙色 view 的任意地方, 蓝色 view(既父视图)响应事件.
难点在于点击非重叠区时, 蓝色 view 不能接收到事件. 为什么会出现这种情况呢? 回顾一下 "原理篇 - 如何寻找最适合的控件来处理事件" 就会发现, 一个控件想要接收事件需要满足两个条件:
判断自己能否触发事件;
判断触摸点是否在自己身上(
- point(inside:with:)
- ).
根据第二点, 我们在点击非重叠区时, 触摸点不在自己 (蓝色 view) 身上, 因此不能够接收事件.
再回顾一下这一节的要点: 触摸事件传递的过程是从父控件传递到子控件的, 如果父控件也不能接收事件, 那么子控件就不可能接收事件.
那应该怎么做呢? 关键还是在第二点上(判断触摸点是否在自己身上), 这个方法返回的是一个 Bool 类型的值, 换句话说, 无论点是否在自己身上, 只要让这个方法返回 true, 就可以让蓝色 view 接收事件.
- /// BlueView.swift
- override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
- // 首先正常返回,
- // 如果点不在自己身上, 则判断点是否在橙色 view 身上.
- // 注: 此时的 subviews.first 代表橙色 view.
- return super.point(inside: point, with: event) || subviews.first!.frame.contains(point)
- }
这样做是可以的, 也最简单. 但有一个问题, 那就是如果橙色 view 也实现了 touches(_:with:) , 这时候是橙色 view 触发事件而不是蓝色 view. 为什么呢?
因为只要判断符合了条件, 事件就会传递到橙色 view, 而触摸点正好在橙色 view 身上, 因此是橙色 view 触发了事件.
不过一般来说, 有这种需求的子控件 (橙色 view) 都不会自己实现事件而是交给父控件 (蓝色 view) 去处理. 所以如果不想考虑这么多的话, 可以直接用上面的方法. 但是如果想屏蔽掉子控件事件的触发的话, 还是有办法解决的.
解决的办法就是拦截橙色 view 接收事件, 只要在 BlueView.swift 中重写 hitTest(_:with) 方法, 返回指定的 view 来做为最适合处理事件的控件就可以了.
- /// BlueView.swift
- override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
- let hitView = super.hitTest(point, with: event)
- // 如果点在橙色 view 的身上, 返回自己(蓝色 view), 不在则正常返回.
- // 注: 此时的 subviews.first 代表橙色 view.
- return subviews.first!.frame.contains(point) ? self : hitView
- }
这样一来, 事件就不会传递到橙色 view 了, 只要点在橙色 view 身上, 我就返回它的父视图(蓝色 view); 如果不在, 就正常返回(点击了蓝色 view 还是蓝色 view 触发事件; 点击了白色 view 则触摸点不在蓝色 view 身上, 此时白色 view 接收事件.)
来源: https://juejin.im/post/5c43154a6fb9a049f5717259