SwiftUIでハマった!「見た目」と「タップ領域」は別物だった話
はじめに
はじめまして、新垣です。2025年に新卒バックエンドエンジニアとしてウェルスナビに入社しました。この記事では、Swiftに挑戦して一番最初に躓いた点である、Buttonのタップ判定についてお話ししようと思います。
対象読者
- SwiftUIでButtonの挙動に悩んでいる方
- Swift初心者でUI実装に不安がある方
- 新卒や若手エンジニアで、同じようなつまずきを共有したい方
Swiftに挑戦しようとしたきっかけ
私は普段バックエンドを担当していますが、配属先にiOSエンジニアがいたことや、研修中に作成したゲームアプリ(Spring boot)を「スマホで使えるようにしてほしい」と言われたことがきっかけで、個人開発としてSwiftに挑戦することにしました。
初めてのiOS開発で、一度作成したゲームなら余裕だろう!と思っていました。しかし、意外な落とし穴がありました。
躓いた点
SwiftUIでシンプルにButtonを配置し、タップイベントを処理するだけのつもりでした。UI上では、デザイン通りに大きなボタンが表示されていて「これで完璧」と思ったのですが、実際にシミュレーターや実機で試すと、タップ判定が異常に狭い…。本来はボタン全体(青色の部分)にタップ判定を付与したかったにもかかわらず、タップできるのは赤枠の小さな領域だけでした。

発生した原因
最初は「SwiftUIやシミュレーターのバグ?」と疑いましたが、調べてみると原因はレイアウトの仕組みにありました。SwiftUIのButtonは、見た目とタップ判定の両方をその中身(Label)ビューに委ねる設計になっています。つまり、Buttonにpaddingを追加しても、タップ判定の範囲は広がらず、Labelのサイズに依存したままになってしまいます。以下の画像では、青色の部分にはタップ判定があり、緑色の部分にはタップ判定がありません。見た目では緑の領域もボタンの一部に見えますが、実際にはタップすることはできません。
今回はパターン3のように実装を行なっていました…
そのため、本来の実装したかったタップ判定が全体にあるボタンを実装することができませんでした。

・パターン1:ラベル、ボタンともにpaddingを追加
・パターン2:ラベルにのみpaddingを追加
・パターン3:ボタンにのみpaddingを追加
コード
HStack(spacing: 20) {
VStack(spacing: 20) {
Text("パターン1")
Button{
print("Tap: パターン1")
} label: {
Text("Tap Me")
.padding(20)
.background(.blue)
.foregroundColor(.white)
}
.padding(30)
.background(.green)
.cornerRadius(24)
}
VStack(spacing: 20) {
Text("パターン2")
Button{
print("Tap: パターン2")
} label: {
Text("Tap Me")
.padding(50)
.background(.blue)
.foregroundColor(.white)
}
.background(.green)
.cornerRadius(24)
}
VStack(spacing: 20) {
Text("パターン3")
Button{
print("Tap: パターン3")
} label: {
Text("Tap Me")
.background(.blue)
.foregroundColor(.white)
}
.padding(50)
.background(.green)
.cornerRadius(24)
}
}.padding()
解決策
labelでサイズを変更する
パターン2と同じように、labelにpaddingを行うことで当たり判定と見た目に差異をなくして実装することが可能です。これはSwiftUIの仕様に基づいた基本的な考え方で、SwiftUIではButton自体にスタイルを直接付けることはあまり行いません。
理由は、Buttonは「アクションを持つコンテナ」であり、見た目のカスタマイズは中身(label)に対して行うのが基本だからです。

コード(パターン2)
VStack(spacing: 20) {
Button {
print("Tap: 解決策")
} label: {
Text("Tap Me")
.padding(50)
.background(.blue)
.foregroundColor(.white)
.clipShape(Circle())
.contentShape(Circle())
}
}
buttonStyle()を使う
buttonStyleを使えば、LabelではなくButton全体にスタイルを適用できます。これにより、見た目とヒット領域を統一的に管理でき、複数のボタンに共通のデザインを適用する場合にも便利です。そのため、「宣言的UI, 再利用性」の観点からもbuttonStyle()を用いることが推奨されます。SwiftUIにはDefaultButtonStyle()やPlainButtonStyle()などの既存スタイルもありますが、今回の例ではデザイン要件に合わせて独自のButtonStyleを実装しています。
コード
struct LargeButtonStyle: ButtonStyle {
func makeBody(configuration: Configuration) -> some View {
configuration.label
.foregroundColor(Color.white)
.padding(50)
.background(Color.blue)
.cornerRadius(24)
}
}
VStack(spacing: 20) {
// ButtonStyle
Text("ButtonStyle")
Button(action: {
print("Tapped!")
}) {
Text("Tap Me")
}
.buttonStyle(LargeButtonStyle())
}
まとめ
タップ判定のまとめ
| 項目 | 内容 |
|---|---|
| 原因 | SwiftUIでは、Buttonのタップ判定はラベル(コンテンツ)のレイアウトサイズに依存する。 |
| 失敗例 | frameやbackgroundで見た目を広げる。 |
| 解決策1 | Labelにpaddingを追加して、見た目とタップ判定を一致させる。 |
| 解決策2 | buttonStyle()を活用し、ボタン全体のスタイルとヒット領域を統一的に管理する。 |
SwiftUIで学んだこと:「見た目とヒット領域は別物」
UIを作るとき、見た目だけでなく「どこをタップできるか」を意識することが重要です。
今回の学び
-
慣れた技術の感覚を新しい技術にそのまま適用しない
HTML/CSSではpaddingを追加すればクリック領域が広がります。そのため、Web開発経験者は「同じ仕様だろう」と思い込みがちです。しかしSwiftUIは宣言的UIであり、見た目とヒット領域を必ずしも同一にしない柔軟性を持つ設計をされています。この違いを理解しないと、UIの操作性に悪影響を与え、ユーザー体験を損なう可能性があります。 -
ユーザー体験への影響
タップ判定が狭いと「押せない」「反応しない」というストレスを生みます。UI実装では、見た目だけでなく操作性を考慮することが不可欠です。
今回の経験から、「慣れた技術の感覚を新しい技術にそのまま適用しない」ことの重要性を学びました。今後は、仕様を調べて設計意図を理解し、再利用性や保守性を意識した実装(例:buttonStyleの活用)を心がけたいと思います。
Discussion