iOS开发实用技术程序员iOS学习开发

iOS总结 | 修改button响应区域的各种姿势

2019-08-17  本文已影响23人  Lol刀妹
看到这张图你一定会点进来😆

一、巧用透明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
上一篇下一篇

猜你喜欢

热点阅读