はじめに (対象読者・この記事でわかること)

この記事は、SwiftUIで「Textをタップして画面遷移したいのに遷移しない!」という壁にぶち当たったiOS開発者の方を対象にしています。特にUIKitのUIButtonaddTarget感覚でSwiftUIを書き始めた方がハマりやすいポイントです。

読み進めることで、 - なぜText.onTapGestureを付けても画面遷移できないのか - NavigationLinkが期待通り動作する条件 - 見た目はTextだけどタップ可能な「透明なボタン」の作り方 を実装レベルで習得できます。サンプルコードはXcode 15以降で動作確認済みです。

前提知識

この記事を読み進める上で、以下の知識があるとスムーズです。 - Swiftの基本的な文法(クラス、構造体、プロトコル) - SwiftUIのViewプロトコルとbodyプロパティの動作 - NavigationStack(iOS 16以降)またはNavigationView(iOS 15以前)の基礎

NavigationLinkの仕組みとTextタップが効かない理由

SwiftUIでは「画面遷移=状態変化」と教わりますが、Text.onTapGestureを付けてisActiveフラグを書き換えても遷移しない理由はシンプルです:NavigationLinkNavigationStackの中でのみ動作し、かつ「リンクとして認識できるView」でなければならないからです。

.onTapGestureTextのタップを検知しても、それがNavigationLinkdestinationをトリガーするわけではありません。UIKitで言えば「UILabelUITapGestureRecognizerを付けてもUINavigationControllerpushViewControllerは呼ばれない」に近い状況です。

見た目はText、振る舞いはボタンにする3つの実装パターン

ここでは「Textをタップして画面遷移したい」という要望を満たす3つのパターンを解説します。いずれもiOS 15/16/17で動作し、ダークモード対応も考慮しています。

ステップ1:NavigationLinkにTextをラベルとして渡す(推奨)

最もシンプルで推奨される方法は、NavigationLinklabelTextを直接渡すことです。これにより、Text全体がタップ可能なリンクになります。

Swift
import SwiftUI struct ContentView: View { var body: some View { NavigationStack { // iOS 16以降 List { // 1. テキストのみのリンク NavigationLink("詳細を見る") { DetailView() } // 2. 複数行テキストもOK NavigationLink { DetailView() } label: { Text("タイトル\nサブタイトル") .lineLimit(2) } } .navigationTitle("記事一覧") } } }

ポイント:NavigationLinklabelクロージャ内でTextを使えば、見た目はTextのままタップ可能になります。.font.foregroundColorも自由にカスタマイズできます。

ステップ2:ボタン風の見た目をTextにする(カスタムラベル)

「角丸のボタンにしたいけど中身はテキスト」というケースでは、ButtonStyleを使わずにNavigationLinklabelTextを修飾して実現できます。

Swift
struct ArticleRow: View { let title: String var body: some View { NavigationLink { DetailView(title: title) } label: { Text(title) .font(.headline) .padding(.vertical, 12) .padding(.horizontal, 20) .background(Color.accentColor) .foregroundColor(.white) .clipShape(RoundedRectangle(cornerRadius: 8)) } .buttonStyle(PlainButtonStyle()) // 行のハイライトを無効化 } }

.buttonStyle(PlainButtonStyle())を付けることで、リスト行のグレーハイライトを無効にして「テキストそのもの」に見せられます。

ステップ3:Textに見えて実は透明なボタン(absolutePosition活用)

「複雑なレイアウトの上にTextを置いてタップしたい」という要件では、.backgroundNavigationLinkを仕込むテクニックが有効です。

Swift
struct OverlayLinkView: View { var body: some View { ZStack { // 背景の複雑なView Image("hero") .resizable() .aspectRatio(contentMode: .fill) .frame(height: 200) .clipped() // 透明なNavigationLinkを重ねる NavigationLink { DetailView() } label: { Color.clear } .buttonStyle(PlainButtonStyle()) // 見た目のText(タップは透過リンクが拾う) Text("Tap Me!") .font(.largeTitle) .bold() .foregroundColor(.white) .shadow(radius: 2) } .frame(height: 200) } }

このパターンではColor.clearで透明な矩形を作り、その上にTextを重ねることで「Textをタップした感」を演出できます。Color.clear部分のサイズを.frameで調整すれば、タップ領域も自在に変えられます。

ハマった点やエラー解決

ハマりポイント1:NavigationViewの中でNavigationLinkを使っていない

SwiftUI 2.0(iOS 14)時代のコードをコピペしてきた際、古いNavigationViewのまま使っているとNavigationLinkが反応しないケースがあります。iOS 16以降ではNavigationStackへの移行をおすすめします。

ハマりポイント2:Listの外でScrollView内にNavigationLinkを置いた

ScrollViewの中だけでNavigationLinkを並べると、タップが明確に検知されないことがあります。LazyVStackを挟むか、余白を.paddingで調整してタップ領域を確保してください。

ハマりポイント3:NavigationLinkのisActiveバインディングが即座にfalseに戻る

状態変数を@Stateで持ち、onTapGesturetrueにしても、画面描画タイミングでfalseに戻って遷移しないという現象が起きます。これはNavigationLinkisActiveBinding<Bool>であるため、値が即座にリセットされてしまうためです。回避策はNavigationLinkdestinationを直接呼ぶか、NavigationPathを使うことです。

解決策

iOS 16以降ではNavigationStackNavigationPathの組み合わせが最も安定動作します。以下は汎用的なナビゲーション構造です。

Swift
@main struct MyApp: App { var body: some Scene { WindowGroup { RouterView() } } } @Observable // iOS 17以降 class Router { var path = NavigationPath() func to(_ page: Page) { path.append(page) } func back() { path.removeLast() } enum Page: Hashable { case detail(id: Int) case settings } } struct RouterView: View { @State private var router = Router() var body: some View { NavigationStack(path: $router.path) { List { NavigationLink("通常リンク") { DetailView() } Button("プログラム遷移") { router.to(.detail(id: 123)) } } .navigationDestination(for: Router.Page.self) { page in switch page { case .detail(let id): DetailView(id: id) case .settings: SettingsView() } } } .environment(router) } }

このようにNavigationPathで状態を一元管理すれば、「Textをタップして画面遷移」もシンプルにrouter.to(.detail(id: 1))の一行で実現できます。

まとめ

本記事では、SwiftUIで「Textをタップして画面遷移したい」という要望を満たす3パターンと、ハマりやすいポイントを解説しました。

  • NavigationLinkのlabelにTextを渡すだけで、見た目はTextのままタップ可能
  • ボタン風の装飾もNavigationLinkのlabel内で完結
  • 複雑なレイアウトでは透明なNavigationLinkを重ねることで自由度UP
  • iOS 16以降はNavigationStack+NavigationPathが安定&拡張しやすい

この記事を通して、SwiftUIの「宣言的UI」の思想を少しだけ掴んでいただければ幸いです。 次回は、SwiftUIでカスタムな戻るボタンを作る方法と、ジェスチャー遷移アニメーションをカスタマイズするテクニックをお届けします。

参考資料