一、巧用透明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到window再到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
网友评论