SwiftUIでアプリを開発する際、画面遷移(ナビゲーション)の実装は必須スキルです。この記事では、NavigationView
の基本から実践的な使い方まで、初心者にもわかりやすく解説します。
NavigationViewとは?
NavigationView
は、SwiftUIで画面遷移機能を実装するためのコンテナビューです。iOSアプリでよく見る、以下のような機能を簡単に実装できます。
- 画面上部のタイトルバー
- 画面間の遷移(次の画面へ進む)
- 自動で表示される戻るボタン
- ツールバーのボタン
NavigationViewとNavigationStackの違い
重要なお知らせ: iOS 16以降ではNavigationStack
が推奨されていますが、この記事では両方を解説します。
項目 | NavigationView | NavigationStack |
---|---|---|
対応OS | iOS 13以降 | iOS 16以降 |
状態 | レガシー(古い方式) | 最新の推奨方式 |
使いやすさ | やや複雑 | よりシンプル |
まずは基本となるNavigationView
をマスターしましょう。
NavigationViewの基本的な使い方
最もシンプルな例
まずは、タイトルバーを表示する最小限のコードから始めましょう。
import SwiftUI
struct ContentView: View {
var body: some View {
NavigationView {
Text("これがメイン画面です")
.navigationTitle("ホーム")
}
}
}
このコードを実行すると、画面上部に「ホーム」というタイトルが表示されます。NavigationView
で囲むだけで、ナビゲーションバーが自動的に追加されます。
タイトルの表示スタイルを変更する
タイトルの表示方法は3種類から選べます。
struct ContentView: View {
var body: some View {
NavigationView {
List(1..<30) { index in
Text("アイテム \(index)")
}
.navigationTitle("リスト")
.navigationBarTitleDisplayMode(.large)
}
}
}
3つの表示モード:
.large
– 大きく表示され、スクロールすると自動的に小さくなる(デフォルト).inline
– 常に小さく表示される.automatic
– システムが自動的に判断
実際にスクロールしてみると、.large
の場合はタイトルが縮小するアニメーションが見られます。
画面遷移の実装:NavigationLink
NavigationLink
を使うと、タップで次の画面に遷移できます。
基本的な画面遷移
struct ContentView: View {
var body: some View {
NavigationView {
VStack(spacing: 20) {
// シンプルな遷移
NavigationLink("詳細画面へ移動") {
DetailView()
}
// カスタムデザインの遷移ボタン
NavigationLink {
SettingsView()
} label: {
Text("設定画面へ")
.foregroundColor(.white)
.padding()
.background(Color.blue)
.cornerRadius(10)
}
}
.navigationTitle("メニュー")
}
}
}
struct DetailView: View {
var body: some View {
Text("ここは詳細画面です")
.font(.title)
.navigationTitle("詳細")
}
}
struct SettingsView: View {
var body: some View {
Text("ここは設定画面です")
.font(.title)
.navigationTitle("設定")
}
}
ポイント:
NavigationLink
をタップすると自動的に次の画面に遷移- 戻るボタンも自動で表示される
- 遷移先の画面でも
.navigationTitle()
でタイトルを設定できる
Listと組み合わせた実装
最も一般的な使い方は、リストと組み合わせた画面遷移です。
struct FruitListView: View {
let fruits = ["りんご", "バナナ", "オレンジ", "ぶどう", "メロン", "いちご"]
var body: some View {
NavigationView {
List(fruits, id: \.self) { fruit in
NavigationLink(fruit) {
FruitDetailView(fruitName: fruit)
}
}
.navigationTitle("果物リスト")
}
}
}
struct FruitDetailView: View {
let fruitName: String
var body: some View {
VStack(spacing: 20) {
Image(systemName: "leaf.fill")
.font(.system(size: 80))
.foregroundColor(.green)
Text(fruitName)
.font(.largeTitle)
.bold()
Text("の詳細情報がここに表示されます")
.foregroundColor(.gray)
}
.navigationTitle(fruitName)
}
}
この例では、果物のリストをタップすると、その果物の詳細画面に遷移します。
データを渡して画面遷移
実際のアプリでは、データを次の画面に渡すことがよくあります。
// データモデル
struct Product: Identifiable {
let id = UUID()
let name: String
let price: Int
let description: String
}
struct ProductListView: View {
let products = [
Product(name: "ノートPC", price: 120000, description: "高性能なノートパソコン"),
Product(name: "スマートフォン", price: 80000, description: "最新のスマートフォン"),
Product(name: "タブレット", price: 60000, description: "便利なタブレット"),
Product(name: "スマートウォッチ", price: 45000, description: "健康管理に最適")
]
var body: some View {
NavigationView {
List(products) { product in
NavigationLink {
ProductDetailView(product: product)
} label: {
HStack {
VStack(alignment: .leading) {
Text(product.name)
.font(.headline)
Text(product.description)
.font(.caption)
.foregroundColor(.gray)
}
Spacer()
Text("¥\(product.price)")
.font(.subheadline)
.bold()
.foregroundColor(.blue)
}
.padding(.vertical, 4)
}
}
.navigationTitle("商品一覧")
}
}
}
struct ProductDetailView: View {
let product: Product
var body: some View {
ScrollView {
VStack(alignment: .leading, spacing: 24) {
// 商品画像エリア
RoundedRectangle(cornerRadius: 12)
.fill(Color.gray.opacity(0.2))
.frame(height: 250)
.overlay(
Image(systemName: "photo")
.font(.system(size: 60))
.foregroundColor(.gray)
)
// 商品情報
VStack(alignment: .leading, spacing: 12) {
Text(product.name)
.font(.title)
.bold()
Text("¥\(product.price)")
.font(.title2)
.foregroundColor(.blue)
.bold()
Divider()
Text("商品説明")
.font(.headline)
Text(product.description)
.foregroundColor(.gray)
}
.padding(.horizontal)
// 購入ボタン
Button {
print("\(product.name)をカートに追加")
} label: {
Text("カートに追加")
.font(.headline)
.foregroundColor(.white)
.frame(maxWidth: .infinity)
.padding()
.background(Color.blue)
.cornerRadius(10)
}
.padding(.horizontal)
}
}
.navigationTitle(product.name)
.navigationBarTitleDisplayMode(.inline)
}
}
この例では、商品リストから商品をタップすると、その商品の詳細情報が次の画面に渡されて表示されます。
ツールバーの実装
ナビゲーションバーにボタンを追加する方法を見ていきましょう。
基本的なツールバーボタン
struct ContentView: View {
var body: some View {
NavigationView {
Text("メイン画面")
.navigationTitle("ホーム")
.toolbar {
// 左側にボタンを配置
ToolbarItem(placement: .navigationBarLeading) {
Button("編集") {
print("編集ボタンがタップされました")
}
}
// 右側にボタンを配置
ToolbarItem(placement: .navigationBarTrailing) {
Button {
print("追加ボタンがタップされました")
} label: {
Image(systemName: "plus")
}
}
}
}
}
}
複数のボタンを配置
右側に複数のボタンを並べることもできます。
struct ContentView: View {
@State private var showingSearch = false
var body: some View {
NavigationView {
Text("メイン画面")
.navigationTitle("ホーム")
.toolbar {
ToolbarItemGroup(placement: .navigationBarTrailing) {
Button {
showingSearch = true
print("検索")
} label: {
Image(systemName: "magnifyingglass")
}
Button {
print("フィルター")
} label: {
Image(systemName: "line.3.horizontal.decrease.circle")
}
Button {
print("設定")
} label: {
Image(systemName: "gearshape")
}
}
}
}
}
}
プログラムで画面遷移を制御
ボタン以外の条件で画面遷移をトリガーしたい場合もあります。
struct LoginView: View {
@State private var username = ""
@State private var password = ""
@State private var isLoggedIn = false
var body: some View {
NavigationView {
VStack(spacing: 20) {
TextField("ユーザー名", text: $username)
.textFieldStyle(.roundedBorder)
.autocapitalization(.none)
SecureField("パスワード", text: $password)
.textFieldStyle(.roundedBorder)
Button("ログイン") {
// ログイン処理
if !username.isEmpty && !password.isEmpty {
isLoggedIn = true
}
}
.buttonStyle(.borderedProminent)
.disabled(username.isEmpty || password.isEmpty)
// 非表示のNavigationLink
NavigationLink(isActive: $isLoggedIn) {
HomeView(username: username)
} label: {
EmptyView()
}
}
.padding()
.navigationTitle("ログイン")
}
}
}
struct HomeView: View {
let username: String
var body: some View {
VStack {
Text("ようこそ、\(username)さん!")
.font(.title)
}
.navigationTitle("ホーム")
.navigationBarBackButtonHidden(true) // 戻るボタンを非表示
}
}
このパターンは、ログイン成功後に自動的にホーム画面に遷移させたい場合などに便利です。
実践例:タブ付き設定画面
実際のアプリでよく見る設定画面を作ってみましょう。
struct SettingsView: View {
@State private var notificationsEnabled = true
@State private var darkModeEnabled = false
@State private var fontSize = 16.0
var body: some View {
NavigationView {
Form {
Section("通知設定") {
Toggle("プッシュ通知", isOn: $notificationsEnabled)
if notificationsEnabled {
NavigationLink("通知の詳細設定") {
NotificationSettingsView()
}
}
}
Section("外観") {
Toggle("ダークモード", isOn: $darkModeEnabled)
VStack(alignment: .leading) {
Text("フォントサイズ: \(Int(fontSize))pt")
Slider(value: $fontSize, in: 12...24, step: 1)
}
}
Section("アカウント") {
NavigationLink("プロフィール") {
ProfileView()
}
NavigationLink("プライバシー") {
PrivacyView()
}
NavigationLink("セキュリティ") {
SecurityView()
}
}
Section("サポート") {
NavigationLink("ヘルプ") {
HelpView()
}
NavigationLink("お問い合わせ") {
ContactView()
}
NavigationLink("バージョン情報") {
VersionView()
}
}
Section {
Button("ログアウト") {
print("ログアウト処理")
}
.foregroundColor(.red)
}
}
.navigationTitle("設定")
}
}
}
struct NotificationSettingsView: View {
@State private var emailNotifications = true
@State private var smsNotifications = false
var body: some View {
Form {
Toggle("メール通知", isOn: $emailNotifications)
Toggle("SMS通知", isOn: $smsNotifications)
}
.navigationTitle("通知の詳細")
}
}
struct ProfileView: View {
var body: some View {
VStack(spacing: 20) {
Image(systemName: "person.circle.fill")
.font(.system(size: 100))
.foregroundColor(.blue)
Text("山田太郎")
.font(.title)
Text("taro@example.com")
.foregroundColor(.gray)
Button("プロフィールを編集") {
print("編集画面へ")
}
.buttonStyle(.borderedProminent)
}
.navigationTitle("プロフィール")
}
}
struct PrivacyView: View {
var body: some View {
Form {
Section("データ収集") {
Toggle("使用状況データ", isOn: .constant(true))
Toggle("クラッシュレポート", isOn: .constant(true))
}
Section("位置情報") {
Text("常に許可")
}
}
.navigationTitle("プライバシー")
}
}
struct SecurityView: View {
var body: some View {
Form {
Section("認証") {
Toggle("Face ID", isOn: .constant(true))
Toggle("2段階認証", isOn: .constant(false))
}
Section {
Button("パスワードを変更") {
print("パスワード変更")
}
}
}
.navigationTitle("セキュリティ")
}
}
struct HelpView: View {
var body: some View {
List {
NavigationLink("はじめに") {
Text("はじめにの内容")
}
NavigationLink("よくある質問") {
Text("FAQの内容")
}
NavigationLink("使い方ガイド") {
Text("ガイドの内容")
}
}
.navigationTitle("ヘルプ")
}
}
struct ContactView: View {
@State private var message = ""
var body: some View {
VStack {
TextEditor(text: $message)
.frame(height: 200)
.padding()
.overlay(
RoundedRectangle(cornerRadius: 8)
.stroke(Color.gray.opacity(0.5))
)
.padding()
Button("送信") {
print("お問い合わせ送信: \(message)")
}
.buttonStyle(.borderedProminent)
.disabled(message.isEmpty)
Spacer()
}
.navigationTitle("お問い合わせ")
}
}
struct VersionView: View {
var body: some View {
VStack(spacing: 16) {
Image(systemName: "app.badge")
.font(.system(size: 80))
.foregroundColor(.blue)
Text("MyApp")
.font(.title)
Text("バージョン 1.0.0")
.font(.headline)
.foregroundColor(.gray)
Text("ビルド 100")
.font(.caption)
.foregroundColor(.gray)
}
.navigationTitle("バージョン情報")
}
}
NavigationBarの非表示とカスタマイズ
ナビゲーションバーを非表示にする
struct ContentView: View {
var body: some View {
NavigationView {
Text("ナビゲーションバーなし")
.navigationBarHidden(true)
}
}
}
戻るボタンを非表示にする
struct DetailView: View {
var body: some View {
Text("戻るボタンなし")
.navigationTitle("詳細")
.navigationBarBackButtonHidden(true)
.toolbar {
ToolbarItem(placement: .navigationBarLeading) {
Button("閉じる") {
// カスタムの閉じる処理
}
}
}
}
}
カスタム戻るボタン
struct CustomBackButtonView: View {
@Environment(\.dismiss) var dismiss
var body: some View {
Text("カスタム戻るボタン")
.navigationBarBackButtonHidden(true)
.toolbar {
ToolbarItem(placement: .navigationBarLeading) {
Button {
dismiss()
} label: {
HStack {
Image(systemName: "chevron.left")
Text("戻る")
}
}
}
}
}
}
iPadでの2カラムレイアウト
iPadでは、サイドバーとメインコンテンツの2カラムレイアウトが実装できます。
struct ContentView: View {
var body: some View {
NavigationView {
// サイドバー
List {
NavigationLink("ホーム") {
HomeContentView()
}
NavigationLink("お気に入り") {
FavoritesContentView()
}
NavigationLink("設定") {
SettingsContentView()
}
}
.navigationTitle("メニュー")
// 初期表示される画面(iPadの右側)
Text("左のメニューから項目を選択してください")
.font(.title)
.foregroundColor(.gray)
}
}
}
struct HomeContentView: View {
var body: some View {
Text("ホームの内容")
.navigationTitle("ホーム")
}
}
struct FavoritesContentView: View {
var body: some View {
Text("お気に入りの内容")
.navigationTitle("お気に入り")
}
}
struct SettingsContentView: View {
var body: some View {
Text("設定の内容")
.navigationTitle("設定")
}
}
iOS 16以降:NavigationStackへの移行
iOS 16以降では、NavigationStack
の使用が推奨されています。基本的な使い方はNavigationView
と似ています。
struct ModernNavigationView: View {
var body: some View {
NavigationStack {
List(1..<20) { index in
NavigationLink("アイテム \(index)") {
Text("詳細 \(index)")
.navigationTitle("詳細")
}
}
.navigationTitle("NavigationStack")
}
}
}
NavigationStackの利点
- より効率的なメモリ管理
- パスベースのナビゲーション
- プログラマティックなナビゲーションが簡単
struct PathBasedNavigationView: View {
@State private var path = NavigationPath()
var body: some View {
NavigationStack(path: $path) {
List(1..<10) { index in
Button("アイテム \(index)") {
path.append(index)
}
}
.navigationDestination(for: Int.self) { value in
DetailView(value: value, path: $path)
}
.navigationTitle("ホーム")
}
}
}
struct DetailView: View {
let value: Int
@Binding var path: NavigationPath
var body: some View {
VStack(spacing: 20) {
Text("アイテム \(value)")
.font(.title)
Button("ルートに戻る") {
path = NavigationPath()
}
.buttonStyle(.borderedProminent)
}
.navigationTitle("詳細 \(value)")
}
}
よくある問題と解決方法
問題1:NavigationLinkが反応しない
原因: NavigationView
で囲まれていない
解決方法:
// 悪い例
NavigationLink("次へ") {
Text("詳細")
}
// 良い例
NavigationView {
NavigationLink("次へ") {
Text("詳細")
}
}
問題2:戻るボタンが表示されない
原因: navigationBarBackButtonHidden(true)
が設定されている
解決方法:
// この設定を削除するか、カスタムボタンを追加
.navigationBarBackButtonHidden(false)
問題3:タイトルが表示されない
原因: .navigationTitle()
がNavigationViewの外にある
解決方法:
// 悪い例
NavigationView {
Text("内容")
}
.navigationTitle("タイトル")
// 良い例
NavigationView {
Text("内容")
.navigationTitle("タイトル")
}
ベストプラクティス
1. iOS 16以降ではNavigationStackを使う
新しいプロジェクトではNavigationStack
を優先しましょう。
2. タイトルは各画面で設定する
遷移先の画面でも.navigationTitle()
を設定することで、わかりやすいUIになります。
3. 深い階層は避ける
ユーザーが迷わないよう、ナビゲーション階層は3〜4段階までに抑えましょう。
4. 適切なツールバーボタンを配置
よく使う機能は右上に、破壊的な操作(削除など)は左上に配置するのが一般的です。
まとめ:NavigationViewをマスターしよう
この記事では、SwiftUIのNavigationView
について詳しく解説しました。
重要ポイントの復習
- 基本機能
- 画面遷移とタイトルバーの実装
NavigationLink
で簡単に遷移- 自動で戻るボタンが表示される
- 画面遷移
- シンプルなリンク
- データを渡す遷移
- プログラムでの遷移制御
- ツールバー
- ボタンの追加と配置
- 複数ボタンの実装
- カスタムデザイン
- iOS 16以降
NavigationStack
への移行推奨- よりシンプルで効率的
- パスベースのナビゲーション
- 実践テクニック
- 設定画面の実装
- iPadの2カラムレイアウト
- カスタム戻るボタン
次のステップ
- まずは基本的な画面遷移を実装
- Listとの組み合わせで実用的なUIを作成
- iOS 16以降の場合は
NavigationStack
を試す - ツールバーやカスタマイズを追加
NavigationView
は、SwiftUIアプリ開発の基礎となる重要な機能です。この記事で学んだことを実際のプロジェクトで試して、ナビゲーション機能をマスターしましょう!