通过 UIView 和 UIControl 实现的蒙层,哪种更简单?
在 APP 内,经常需要弹出一个半屏的 UIView 来提示更多信息,比如在底部弹出的分享渠道选项,在顶部弹出的列表筛选选项,在中间弹出的广告消息。
比如点击微信公众号右上角之后,弹出的信息页面:
在显示包含信息的 View 时,通常需要在下面添加一个蒙层,来将弹出的 View 和当前正在显示的内容进行隔离。当不需要用户强制选择某个选项时,点击蒙层也充当了取消的功能。
UIView 实现的蒙层
可以通过 UIView + 手势识别来实现的这个蒙层:
1. 自定义一个 UIView,设置一个带透明度的颜色值;
2. 给这个 View 添加点击手势,实现这个手势的代理;
3. 在手势代理中判断手势点击的坐标,如果包含在显示的内容区域,则不处理,否则执行隐藏方法。
另外,UIView 本身可以处理点击事件,所以可以在 `touchesBegan` 中判断触摸的位置,省去了添加手势的过程:
判断某个位置是否在某个视图上有多种方式,可以查看:[iOS 判断当前点击的位置是否在某个视图上的几种方法](https://www.jianshu.com/p/f4e29487b4cd)
UIControl 实现的蒙层
最近在阅读 SwiftMessages 源码时,看到使用 UIControl 实现的蒙层 PassthroughView
:
使用 UIControl 实现的蒙层 PassthroughView 时,只需要设置相应的蒙层颜色,添加点击回调的 tappedHander
就可以了:
小结
我们将 UIControl 和 UIView 的实现过程进行对比:
1. 基类不同:一个 UIControl, 一个 UIView。UIControl 继承自 UIView,UIView 比较轻量级;
2. 添加点击事件:UIControl 使用的 addTarget(_:action:for:)
,UIView 通过添加点击手势或者使用 touchesBegan
来获取点击事件;
3. 过滤点击事件:UIControl 不需要过滤点击事件(上面的 PassthroughView
的 hitTest
方法中,只是通过判断 tappedHander 是否为空,决定 self 是否响应该事件),UIView 需要在手势回调或者在 touchesBegan
中过滤掉点击在子 view 上的事件。
在阅读源码的过程中,总是能惊奇的发现,还有更多/更好的方案。
其他
根据 UIControl 的实现思路,我尝试能不能更加简化代码,直接在 hitTest
中移除蒙层:
但是发现这样是不行的。至于为什么不行,我请教了一位朋友,觉得他说的挺有道理的:
我的理解是:事件处理的优先级是最高的,并且程序一启动就有一个 RunLoop 去处理 App 的事件队列,当发现有事件需要处理,就开始处理这个事件,而这个事件是主线程的,移除的话也是主线程,所以这里应该会有问题
还有一个问题就是,这个方法是返回一个最合适的 view 去响应事件,你要是把 View 给移除了,如果没有内存泄漏,那么就释放了这个 View,返回的那个 View 就不存在了,不知道是否会存在什么问题
虽然说给 nil 对象发送消息,不会 crash,但是苹果应该会尽量避免这个吧
评论