iOS:pointInsideでボタンのタップ範囲を拡大|操作性向上の具体手法
iOSアプリのボタン操作ミスを減らすため、pointInsideメソッドでタップ感知範囲を拡大。ユーザーの利便性を高め、操作率アップを実現する具体的なコード解説。
本記事は AI による翻訳をもとに作成されています。表現が不自然な箇所がありましたら、ぜひコメントでお知らせください。
記事一覧
iOS ボタンのタップ範囲を拡大する方法
pointInside をオーバーライドして感知領域を拡大する
日常の開発では、デザイン通りに UI を配置して画面は美しく仕上がっても、実際の操作でボタンの感知範囲が狭く、正確にタップしにくいことがよくあります。特に指の太い人には非常に使いづらいです。
完成サンプル画像
以前は…
この問題について当初は特に深く調べず、元のボタンの上に範囲を広げた透明な UIButton を無理やり重ねて、その透明ボタンでイベントを受け取る方法を使っていましたが、とても面倒でコンポーネントが増えると管理もしづらくなりました。
後でレイアウトの方法で解決しました。ボタンの上下左右を0(またはそれ以下)に揃え、imageEdgeInsets、titleEdgeInsets、contentEdgeInsets の3つの内側余白パラメータを調整して、アイコンやボタンのタイトルをUIデザインの正しい位置に配置します。しかし、この方法はStoryboardやxibを使ったプロジェクトに向いています。なぜならInterface Builderで直接レイアウト調整ができるからです。もう一つは、デザインしたアイコンは余白がないものが望ましく、そうでないと位置合わせが難しくなります。時には0.5ポイントの距離でうまく揃わないこともあります。
その後…
いわゆる「見聞を広げる」ということで、最近新しいプロジェクトに触れて、一つ小技を学びました。それは 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 プロパティを追加して 拡張したい範囲を保持 できるようにします。次に、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 のクラスを自作の 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 日常使用。(詳細原理請參考 貓大的文章 )
Extension は Property を含めることができず、「Extensions must not contain stored properties」というコンパイルエラーが発生します。ここでは 使用 Property 配合 Associated Object を参考に、外部変数 buttonTouchEdgeInsets を Extension に関連付けることで、Property のように日常的に使用できるようにしています。(詳細な仕組みは 貓大的文章 をご参照ください)
UIImageView (UITapGestureRecognizer) は?
画像のタップに対して、私たちが View に追加した Tap ジェスチャーも、UIImageView の pointInside をオーバーライドすることで同じ効果を得ることができます。
完了!継続的な改善により、この問題の解決がより簡単で便利になりました!
参考資料:
付記
去年同じ時期に「 小さなことを大きなことに 」という小カテゴリを作って、日常の開発の細かいことを記録しようと思いました。しかし、これらの小さなことがひそかに積み重なって、アプリ全体の体験やプログラム面で大きな成果になります。結果として 1年も遅れて やっと記事を追加しました <( _ _ )>。小さなことは本当に記録し忘れやすいですね!
ご質問やご意見がありましたら、こちらからご連絡ください 。
Post MediumからZMediumToMarkdownを使って変換しました。
本記事は Medium にて初公開されました(こちらからオリジナル版を確認)。ZMediumToMarkdown による自動変換・同期技術を使用しています。

