Post

iOS 扩大按钮点击范围|Swift 自订 UIButton 扩展触控区域技巧

解决 iOS 按钮点击范围过小导致操作不便的问题,透过重写 UIButton 的 pointInside 方法及 touchEdgeInsets 扩大感应区域,提升用户点击准确度与操作体验,适用 Storyboard 与程式码开发环境。

iOS 扩大按钮点击范围|Swift 自订 UIButton 扩展触控区域技巧

Click here to view the English version of this article.

點擊這裡查看本文章正體中文版本。

基于 SEO 考量,本文标题与描述经 AI 调整,原始版本请参考内文。


iOS 扩大按钮点击范围

重写 pointInside 扩大感应区域

日常开发上经常遇到版面照著设计 UI 排好之后,画面美美的,但是实际操作上按钮的感应范围太小,不容易准确点击;尤其对手指粗的人极不友善。

完成范例图

完成范例图

Before…

关于这个问题当初没特别深入研究,直接暴力盖一个范围更大的透明 UIButton 在原按钮上,并使用这个透明的按钮响应事件,做起来非常麻烦、元件一多也不好控制。

后来改用排版的方式解决,按钮在排版时设定上下左右都对齐0 (或更低),再控制 imageEdgeInsetstitleEdgeInsetscontentEdgeInsets 这三个内距参数,将 Icon/按钮标题 推到 UI 设计的正确位置;但这个做法比较适合使用 Storyboard/xib 的专案,因为可以直接在 Interface Builder 去推排版;另外一个是设计出的 Icon 最好要是没有刘间距的,不然会不好对位置,有时候可能就卡在那个 0.5 的距离,怎么调都不对齐。

After…

正所谓见多识广,最近接触到新专案之后又学到了一小招;就是可以在 UIButton 的 pointInside 中加大事件响应范围,预设是 UIButton 的 Bounds,我们可以在里面延伸 Bounds 的大小使按钮的可点击区域更大!

经过以上思路…我们可以:

1
2
3
4
5
6
7
8
9
10
11
12
class MyButton: UIButton {
    var touchEdgeInsets:UIEdgeInsets?
    override open func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
        var frame = self.bounds
        
        if let touchEdgeInsets = self.touchEdgeInsets {
            frame = frame.inset(by: touchEdgeInsets)
        }
        
        return frame.contains(point);
    }
}

自订一个 UIButton ,增加 touchEdgeInsets 这个 public property 存放要扩张的范围 方便我们使用;接著复写 pointInside 方法,实作上述的想法。

使用:

1
2
3
4
5
6
7
8
9
10
11
12
13
import UIKit

class MusicViewController: UIViewController {

    @IBOutlet weak var playerButton: MyButton!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        playerButton.touchEdgeInsets = UIEdgeInsets(top: -10, left: -10, bottom: -10, right: -10)
    }
    
}

播放按钮/蓝色为原始点击区域/红色为扩大后的点击范围

播放按钮/蓝色为原始点击区域/红色为扩大后的点击范围

使用时只需记得要将 Button 的 Class 指定为我们自订的 MyButton,然后就能透过设定 touchEdgeInsets 针对个别 Button 扩大点击范围!

️⚠️⚠️⚠️⚠️️️️⚠️️️️

使用 Storyboard/xib 时记得设 Custom Class 为 MyButton

⚠️⚠️⚠️⚠️⚠️

touchEdgeInsets 以(0,0)自身为中心向外,所以上下左右的距离要用 负数 来延伸。

看起来不错…但是:

对于每个 UIButton 都要置换成自订的 MyButton 其实挺繁琐的也增加程式的复杂性、甚至在大型专案中可能会有冲突。

对于这种我们认为应该所有 UIButton 天生都应该要具有的功能,如果可以,我们希望能直接 Extension 扩充原本的 UIButton :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
private var buttonTouchEdgeInsets: UIEdgeInsets?

extension UIButton {
    var touchEdgeInsets:UIEdgeInsets? {
        get {
            return objc_getAssociatedObject(self, &buttonTouchEdgeInsets) as? UIEdgeInsets
        }

        set {
            objc_setAssociatedObject(self,
                &buttonTouchEdgeInsets, newValue,
                .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
        }
    }
    
    override open func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
        var frame = self.bounds
        
        if let touchEdgeInsets = self.touchEdgeInsets {
            frame = frame.inset(by: touchEdgeInsets)
        }
        
        return frame.contains(point);
    }
}

使用上如前述使用范例。

因 Extension 不能包含 Property 否则会报编译错误「Extensions must not contain stored properties」,这边参考了 使用 Property 配合 Associated Object 将外部变数 buttonTouchEdgeInsets 关联到我们的 Extension 上,就能如 Property 日常使用。(详细原理请参考 猫大的文章 )

UIImageView (UITapGestureRecognizer) 呢?

针对图片点击、我们自己在 View 上加的 Tap 手势; ㄧ样能透过复写 UIImageView 的 pointInside 达到同样的效果。

完成!经过不断的改进,在解决这个议题上更简洁方便了不少!

参考资料:

UIView 改变触摸范围 (Objective-C)

附记

去年同一时间想开个小分类「 顾小事成大事 」纪录一下日常开发琐碎的小事,但这些小事默默累积又能成大事增加整个 APP 的不管是体验或是程式方面;结果 拖了一年 才又增加了一篇文章 <( _ _ )>,小事真的很容易忘了记录啊!

有任何问题及指教欢迎 与我联络


Buy me a beer

本文首次发表于 Medium (点击查看原始版本),由 ZMediumToMarkdown 提供自动转换与同步技术。

Improve this page on Github.

This post is licensed under CC BY 4.0 by the author.