iOS 扩大按钮点击范围|Swift 自订 UIButton 扩展触控区域技巧
解决 iOS 按钮点击范围过小导致操作不便的问题,透过重写 UIButton 的 pointInside 方法及 touchEdgeInsets 扩大感应区域,提升用户点击准确度与操作体验,适用 Storyboard 与程式码开发环境。
Click here to view the English version of this article.
點擊這裡查看本文章正體中文版本。
基于 SEO 考量,本文标题与描述经 AI 调整,原始版本请参考内文。
iOS 扩大按钮点击范围
重写 pointInside 扩大感应区域
日常开发上经常遇到版面照著设计 UI 排好之后,画面美美的,但是实际操作上按钮的感应范围太小,不容易准确点击;尤其对手指粗的人极不友善。
完成范例图
Before…
关于这个问题当初没特别深入研究,直接暴力盖一个范围更大的透明 UIButton 在原按钮上,并使用这个透明的按钮响应事件,做起来非常麻烦、元件一多也不好控制。
后来改用排版的方式解决,按钮在排版时设定上下左右都对齐0 (或更低),再控制 imageEdgeInsets
、 titleEdgeInsets
、 contentEdgeInsets
这三个内距参数,将 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 达到同样的效果。
完成!经过不断的改进,在解决这个议题上更简洁方便了不少!
参考资料:
附记
去年同一时间想开个小分类「 顾小事成大事 」纪录一下日常开发琐碎的小事,但这些小事默默累积又能成大事增加整个 APP 的不管是体验或是程式方面;结果 拖了一年 才又增加了一篇文章 <( _ _ )>,小事真的很容易忘了记录啊!
有任何问题及指教欢迎 与我联络 。
本文首次发表于 Medium (点击查看原始版本),由 ZMediumToMarkdown 提供自动转换与同步技术。