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 提供自动转换与同步技术。