看到这张图你一定会点进来
一, 巧用透明 button
在背景 view 上放一个比背景 view 更小的透明 button, 这样给人的感觉就是响应区域变小了;
在透明 button 上放一个比透明 button 更小的 view, 这样给人的感觉就是响应区域变大了.
骚的一批
二, 设置 imageEdgeInsets
按钮的背景颜色为 clear 的时候这招很好用. 其实就是在不改变图片大小的情况下将按钮调大.
以下两种情况图片的 size 都是 20x20, 但是响应区域却不一样:
- button.setImage(UIImage.init(named: "ic_middle_cicle_notice"), for: .normal)
- button.frame = CGRect.init(x: 0, y: 0, width: 40, height: 40)
- button.imageEdgeInsets = UIEdgeInsets.init(top: 10, left: 10, bottom: 10, right: 10)
- button.frame = CGRect.init(x: 0, y: 0, width: 20, height: 20)
同方案一, 也是间接修改按钮响应区域.
三, 重写 pointInside
上面两种方案遇到圆形按钮就比较无力了, 如图:
你看这按钮它又大又圆, 需要特别注意的是按钮图片的四周留白非常大.
你要是按照 UI 图来做弄一个 204x204 的 button 然后赋值图片, 看起来肯定是没有任何问题的, 问题是整个 204x204 的区域都会响应点击事件, 由于图片留白很大, 就会出现明明就没有点击按钮 (绿色区域), 按钮却响应了这种体验不太好的情况.
最好的体验是只有点击绿色区域才响应.
用方案一, imageView 作为背景 view, 然后放一个跟绿色区域一样大一样圆的透明圆 button. 听起来貌似完美, 然而如果只是设置 button 的 cornerRadius, 它看起来是圆了, 但是它的响应区域依旧是方的.
方案二同样无解.
开始方案三:
我们想要的效果是以绿色圆圆心为中心方圆 70pt 的区域能够响应用户交互. 因此可以计算用户 touch 的点距离圆心的距离, 如果这个距离低于或等于 70, 才允许响应:
- override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
- // 按钮中心
- let centerX = self.bounds.size.width / 2
- let centerY = self.bounds.size.height / 2
- // 点击位置
- let pointX = point.x
- let pointY = point.y
- // delta
- let dltX = pointX - centerX
- let dltY = pointY - centerY
- // 点击点距离按钮中心的距离
- let length = sqrt(pow(dltX, 2) + pow(dltY, 2))
- return length <= 70
- }
或者定义一个圈, 判断 touch 点是否在这个圈里面:
- override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
- let originX: CGFloat = (204 - 140) / 2
- // 画一个圈, 看 touch 点是否在圈里面
- let circle = UIBezierPath.init(ovalIn: CGRect.init(x: originX, y: originX, width: 140, height: 140))
- return circle.contains(point)
- }
注: 按钮左上角的点是 (0, 0)
这样, UI 展现与设计图是一模一样的, 用户交互也是我们所期望的.
补充: 子 view 超出父 view 的情况
如图:
还是那个又大又圆的按钮, 背景色给它设置成了黄色, 然后这个按钮是被 add 到红色 view 上的, 但是只有一半在上面, 也就是说点击按钮上半部分不会有反应.
问: 如何才能让点击按钮上半部分也会有反应?
老司机估计沉不住气了:
这是傻逼吧? 为什么非得放在红色 view 上? 放在 controller 的 view 上会怀孕?
不懂变通确实略显呆滞, 但我们今天的游戏规则就是按钮必须 add 到红色 view 上.
根据响应者链可知, 当用户点击按钮上半部分的时候, 事件传递从 application 到 Windows 再到 controller 的 view, 然后, 由于没有处于点击范围内的 subview 了, 所以最终响应的是 controller 的 view.
如何让事件传递从 controller 的 view 到红色 view?
一种做法是扩大红色 view 的响应区域, 比如说认定所有点击都在红色 view 的范围内:
- // 方法一, 重写 redView 的 `pointInside`
- override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
- // 直接返回 true
- // 响应区域全开
- return true
- }
当然这种做法比较极端.
另一种做法是重写 hitTest 方法, 根据 touch 点来设置响应的 view:
- // 方法二
- // 重新设定响应 view
- override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
- // 坐标转换
- let tempPoint = self.scanButton.convert(point, from: self)
- // 判断点击点是否在 button 上
- if self.scanButton.point(inside: tempPoint, with: event) {
- return self.scanButton
- }
- return super.hitTest(point, with: event)
- }
两种操作都是围绕事件传递的两个方法展开的, 如果对响应者链不熟悉, 也许会轻度懵逼.
- demo
- https://github.com/CaiWanFeng/iOS_Storage
结束了
多种姿势, 你学会了吗?
如果还有其它姿势, 欢迎共享.
头发日渐稀少. gif
来源: http://www.jianshu.com/p/4a606d6780ae