mcpField
A
axiom-swiftui-nav

axiom-swiftui-nav

github.com/charleswiltgen開発1.2k183 回インストール今日更新
リポジトリ

axiom-swiftui-nav について

SwiftUI 開発者向けのナビゲーションスキルです。ナビゲーション構造、ディープリンク、状態復元の選択と実装を支援し、判断基準や具体例、失敗につながりやすい設計の注意点を示します。

主な機能

  1. 01スタック、分割ビュー、タブ構成の選択
  2. 02NavigationPath によるプログラム制御
  3. 03ディープリンクと URL ルーティングの設定
  4. 04ナビゲーション状態の保存と復元
  5. 05ルーターパターンの適用とアンチパターン回避

活用シーン

  1. 01アプリのナビゲーション構造を選定
  2. 02ディープリンクから画面へ遷移
  3. 03アプリ再起動後もナビゲーション状態を保持
  4. 04リリース前にナビゲーションコードをレビュー

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/charleswiltgen/axiom --skill axiom-swiftui-nav

インストール後、Claude Code で/axiom-swiftui-navを入力して読み込まれたか確認します。

詳細

SwiftUI ナビゲーション

このスキルを使う場面

次のような場合に使用します:

  • ナビゲーションアーキテクチャを選択する場合(NavigationStack、NavigationSplitView、TabView)
  • NavigationPath を使ったプログラムによるナビゲーションを実装する場合
  • ディープリンクと URL ルーティングを設定する場合
  • ナビゲーションの状態復元を実装する場合
  • タブ/サイドバーのパターンを採用する場合(iOS 18 以降)
  • コーディネーター/ルーターのパターンを実装する場合
  • リリース前にナビゲーションの実装についてコードレビューを依頼する場合
関連スキル
  • ナビゲーションの問題を体系的にトラブルシューティングするには、axiom-swiftui-nav-diag を使用します
  • タブのカスタマイズや iOS 26 以降の機能を含む包括的な API リファレンスと、すべての WWDC の例を参照するには、axiom-swiftui-nav-ref を使用します

プロンプトの例

このスキルが回答するように設計されている、開発者からの実際の質問を紹介します:

1. 「アプリでは NavigationStack と NavigationSplitView のどちらを使うべきですか?」

-> このスキルでは、対象デバイス、コンテンツ階層の深さ、マルチプラットフォームの要件に基づく判断フローを示します

2. 「SwiftUI でプログラムによるナビゲーションを行うにはどうすればよいですか?」

-> このスキルでは、プッシュ、ポップ、ルートへのポップ、ディープリンクに対応する NavigationPath の操作パターンを紹介します

3. 「ディープリンクが機能しません。アプリは開きますが、間違った画面が表示されます。」

-> このスキルでは、URL の解析パターン、パスの構築順序、onOpenURL のタイミングに関する問題を説明します

4. 「アプリがバックグラウンドに移行すると、ナビゲーションの状態が失われます。」

-> このスキルでは、Codable NavigationPath、SceneStorage による永続化、クラッシュに強い状態復元を紹介します

5. 「SwiftUI でコーディネーターパターンを実装するにはどうすればよいですか?」

-> このスキルでは、Router パターンの例に加え、コーディネーターが複雑さよりも価値をもたらす場面について説明します


危険な兆候 — 防止すべきアンチパターン

次のいずれかを行っている場合は、すぐに中止し、このスキルのパターンを使用してください:

❌ 重大 — 絶対に行わないでください
1. iOS 16 以降で非推奨の NavigationView を使用する
// ❌ WRONG — Deprecated, different behavior on iOS 16+
NavigationView {
    List { ... }
}
.navigationViewStyle(.stack)

問題の理由 NavigationView は iOS 16 以降、非推奨です。NavigationPath に対応していないため、プログラムによるナビゲーションやディープリンクの信頼性が低くなります。iOS のバージョンによって動作が異なるため、バグの原因になります。

2. プログラムによるナビゲーションにビュー型の NavigationLink を使用する
// ❌ WRONG — Cannot programmatically control
NavigationLink("Recipe") {
    RecipeDetail(recipe: recipe)  // View destination, no value
}

問題の理由 ビュー型のリンクはプログラムから制御できません。ディープリンクを設定したり、この遷移先に戻ったりする方法がありません。iOS 16 以降、非推奨です。

3. 遅延コンテナ内に navigationDestination を配置する
// ❌ WRONG — May not be loaded when needed
LazyVGrid(columns: columns) {
    ForEach(items) { item in
        NavigationLink(value: item) { ... }
            .navigationDestination(for: Item.self) { item in  // Don't do this
                ItemDetail(item: item)
            }
    }
}

問題の理由 遅延コンテナはすべてのビューをすぐには読み込みません。そのため navigationDestination が NavigationStack から認識されず、ナビゲーションが何も通知されないまま失敗する可能性があります。

4. 状態復元のために、完全なモデルオブジェクトを NavigationPath に保存する
// ❌ WRONG — Duplicates data, stale on restore
class NavigationModel: Codable {
    var path: [Recipe] = []  // Full Recipe objects
}

問題の理由 モデル内にすでに存在するデータが重複します。状態復元時に、Recipe のデータが古くなっている可能性があります(別の場所で編集または削除された場合など)。ID を使用し、現在のデータを取得してください。

5. MainActor の外部で NavigationPath を変更する
// ❌ WRONG — UI update off main thread
Task.detached {
    await viewModel.path.append(recipe)  // Background thread
}

問題の理由 NavigationPath は UI にバインドされています。ナビゲーション状態が破損しないよう、変更は MainActor 上で行う必要があります。クラッシュや、何も通知されないままの失敗を引き起こすことがあります。

6. ナビゲーション状態に @MainActor による分離がない
// ❌ WRONG — Not MainActor isolated
class Router: ObservableObject {
    @Published var path = NavigationPath()  // No @MainActor
}

問題の理由 Swift 6 の厳格な並行処理では、SwiftUI ビューからアクセスされる @Published プロパティに MainActor による分離が必要です。データ競合の警告やクラッシュの原因になる可能性があります。

7. マルチタブアプリでナビゲーション状態を処理しない
// ❌ WRONG — Shared NavigationPath across tabs
TabView {
    Tab("Home") { HomeView() }
    Tab("Settings") { SettingsView() }
}
// All tabs share same NavigationStack — wrong!

問題の理由 タブを切り替えたときにナビゲーション状態を保持できるよう、各タブにそれぞれ独自の NavigationStack を設ける必要があります。状態を共有すると、分かりにくい UX になります。

8. NavigationPath のデコードエラーを無視する
// ❌ WRONG — Crashes on invalid data
let path = NavigationPath(try! decoder.decode(NavigationPath.CodableRepresentation.self, from: data))

問題の理由 パスに含まれていた項目がユーザーによって削除されている可能性があります。また、スキーマが変更されている可能性もあります。強制アンラップすると、状態復元時にクラッシュします。


必須の最初の手順

ナビゲーションを実装する前に、必ず次の手順を完了してください:

// Step 1: Identify your navigation structure
// Ask: Single stack? Multi-column? Tab-based with per-tab navigation?
// Record answer before writing any code

// Step 2: Choose container based on structure
// Single stack (iPhone-primary): NavigationStack
// Multi-column (iPad/Mac-primary): NavigationSplitView
// Tab-based: TabView with NavigationStack per tab

// Step 3: Define your value types for navigation
// All values pushed on NavigationStack must be Hashable
// For deep linking/restoration, also Codable
struct Recipe: Hashable, Codable, Identifiable { ... }

// Step 4: Plan deep link URLs (if needed)
// myapp://recipe/{id}
// myapp://category/{name}/recipe/{id}

// Step 5: Plan state restoration (if needed)
// Will you use SceneStorage? What data must be Codable?

簡単な判断フロー

Need navigation?
├─ Multi-column interface (iPad/Mac primary)?
│  └─ NavigationSplitView
│     ├─ Need drill-down in detail column?
│     │  └─ NavigationStack inside detail (Pattern 3)
│     └─ Selection-only detail?
│        └─ Just selection binding (Pattern 2)
├─ Tab-based app?
│  └─ TabView
│     ├─ Each tab needs drill-down?
│     │  └─ NavigationStack per tab (Pattern 4)
│     └─ iPad sidebar experience?
│        └─ .tabViewStyle(.sidebarAdaptable) (Pattern 5)
└─ Single-column stack?
   └─ NavigationStack
      ├─ Need deep linking?
      │  └─ Use NavigationPath (Pattern 1b)
      └─ Simple push/pop?
         └─ Typed array path (Pattern 1a)

Need state restoration?
└─ SceneStorage + Codable NavigationPath (Pattern 6)

Need coordinator abstraction?
├─ Complex conditional flows?
├─ Navigation logic testing needed?
├─ Sharing navigation across many screens?
└─ YES to any → Router pattern (Pattern 7)
   NO to all → Use NavigationPath directly

パターン 1a: 基本的な NavigationStack

使用場面: シンプルな push/pop ナビゲーション、すべての遷移先が同じ型

所要時間: 5〜10 分

struct RecipeList: View {
    @State private var path: [Recipe] = []

    var body: some View {
        NavigationStack(path: $path) {
            List(recipes) { recipe in
                NavigationLink(recipe.name, value: recipe)
            }
            .navigationTitle("Recipes")
            .navigationDestination(for: Recipe.self) { recipe in
                RecipeDetail(recipe: recipe)
            }
        }
    }

    // Programmatic navigation
    func showRecipe(_ recipe: Recipe) {
        path.append(recipe)
    }

    func popToRoot() {
        path.removeAll()
    }
}

ポイント:

  • すべての値が同じ型の場合は型付き配列 [Recipe]
  • 値ベースの NavigationLink(title, value:)
  • navigationDestination(for:) は遅延コンテナの外に配置する

パターン 1b: ディープリンク対応の NavigationStack

使用場面: 複数の遷移先型、URL ベースのディープリンク

所要時間: 15〜20 分

struct ContentView: View {
    @State private var path = NavigationPath()

    var body: some View {
        NavigationStack(path: $path) {
            HomeView()
                .navigationDestination(for: Category.self) { category in
                    CategoryView(category: category)
                }
                .navigationDestination(for: Recipe.self) { recipe in
                    RecipeDetail(recipe: recipe)
                }
        }
        .onOpenURL { url in
            handleDeepLink(url)
        }
    }

    func handleDeepLink(_ url: URL) {
        // URL: myapp://category/desserts/recipe/apple-pie
        path.removeLast(path.count)  // Pop to root first

        guard let components = URLComponents(url: url, resolvingAgainstBaseURL: false) else { return }
        let segments = components.path.split(separator: "/").map(String.init)

        var index = 0
        while index < segments.count - 1 {
            switch segments[index] {
            case "category":
                if let category = Category(rawValue: segments[index + 1]) {
                    path.append(category)
                }
                index += 2
            case "recipe":
                if let recipe = dataModel.recipe(named: segments[index + 1]) {
                    path.append(recipe)
                }
                index += 2
            default:
                index += 1
            }
        }
    }
}

ポイント:

  • 異種の型には NavigationPath
  • ディープリンクのパスを構築する前にルートまでポップする
  • 正しい順序でパスを構築する (親 → 子)

パターン 2: 選択ベースの NavigationSplitView

使用場面: 詳細カラムが選択項目を表示するマルチカラムレイアウト

所要時間: 10〜15 分

struct MultiColumnView: View {
    @State private var selectedCategory: Category?
    @State private var selectedRecipe: Recipe?

    var body: some View {
        NavigationSplitView {
            List(Category.allCases, selection: $selectedCategory) { category in
                NavigationLink(category.name, value: category)
            }
            .navigationTitle("Categories")
        } content: {
            if let category = selectedCategory {
                List(recipes(in: category), selection: $selectedRecipe) { recipe in
                    NavigationLink(recipe.name, value: recipe)
                }
                .navigationTitle(category.name)
            } else {
                Text("Select a category")
            }
        } detail: {
            if let recipe = selectedRecipe {
                RecipeDetail(recipe: recipe)
            } else {
                Text("Select a recipe")
            }
        }
    }
}

ポイント:

  • List の selection: $binding がカラムの選択に接続される
  • 値を提示するリンクは選択を自動的に更新する
  • iPhone では単一スタックに適応する

パターン 3: 詳細にスタックを持つ NavigationSplitView

使用場面: 詳細カラムでドリルダウン可能なマルチカラム

所要時間: 20〜25 分

struct GridWithDrillDown: View {
    @State private var selectedCategory: Category?
    @State private var path: [Recipe] = []

    var body: some View {
        NavigationSplitView {
            List(Category.allCases, selection: $selectedCategory) { category in
                NavigationLink(category.name, value: category)
            }
            .navigationTitle("Categories")
        } detail: {
            NavigationStack(path: $path) {
                if let category = selectedCategory {
                    RecipeGrid(category: category)
                        .navigationDestination(for: Recipe.self) { recipe in
                            RecipeDetail(recipe: recipe)
                        }
                } else {
                    Text("Select a category")
                }
            }
        }
    }
}

ポイント:

  • 詳細カラム内の NavigationStack
  • サイドバーを維持したまま Grid → Detail のドリルダウン
  • ドリルダウン用の個別パス、サイドバー用の選択

パターン 4: タブごとの NavigationStack を持つ TabView

使用場面: 各タブが独自のナビゲーションを持つタブベースのアプリ

所要時間: 15〜20 分

struct TabBasedApp: View {
    var body: some View {
        TabView {
            Tab("Home", systemImage: "house") {
                NavigationStack {
                    HomeView()
                        .navigationDestination(for: Item.self) { item in
                            ItemDetail(item: item)
                        }
                }
            }

            Tab("Search", systemImage: "magnifyingglass") {
                NavigationStack {
                    SearchView()
                }
            }

            Tab("Settings", systemImage: "gear") {
                NavigationStack {
                    SettingsView()
                }
            }
        }
    }
}

ポイント:

  • 各タブが独自の NavigationStack を持つ
  • タブを切り替えてもナビゲーション状態が保持される
  • systemImage を使った iOS 18 以降の Tab 構文

パターン 5: サイドバー適応型 TabView (iOS 18 以降)

使用場面: iPhone ではタブバー、iPad ではサイドバー

所要時間: 20〜25 分

struct AdaptableApp: View {
    var body: some View {
        TabView {
            Tab("Watch Now", systemImage: "play") {
                WatchNowView()
            }
            Tab("Library", systemImage: "books.vertical") {
                LibraryView()
            }

            TabSection("Collections") {
                Tab("Favorites", systemImage: "star") {
                    FavoritesView()
                }
                Tab("Recently Added", systemImage: "clock") {
                    RecentView()
                }
            }

            Tab(role: .search) {
                SearchView()
            }
        }
        .tabViewStyle(.sidebarAdaptable)
    }
}

ポイント:

  • .tabViewStyle(.sidebarAdaptable) で iPad のサイドバーが有効になる
  • TabSection でサイドバーに折りたたみ可能なグループを作成する
  • Tab(role: .search) は特別な配置になる

パターン 6: 状態の復元

使用場面: アプリ起動をまたいでナビゲーション状態を保持する

所要時間: 25〜30 分

@MainActor
class NavigationModel: ObservableObject, Codable {
    @Published var selectedCategory: Category?
    @Published var recipePath: [Recipe.ID] = []  // Store IDs, not objects

    enum CodingKeys: String, CodingKey {
        case selectedCategory, recipePath
    }

    func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)
        try container.encodeIfPresent(selectedCategory, forKey: .selectedCategory)
        try container.encode(recipePath, forKey: .recipePath)
    }

    required init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        selectedCategory = try container.decodeIfPresent(Category.self, forKey: .selectedCategory)
        recipePath = try container.decode([Recipe.ID].self, forKey: .recipePath)
    }

    init() {}

    var jsonData: Data? {
        get { try? JSONEncoder().encode(self) }
        set {
            guard let data = newValue,
                  let model = try? JSONDecoder().decode(NavigationModel.self, from: data)
            else { return }
            selectedCategory = model.selectedCategory
            recipePath = model.recipePath
        }
    }
}

struct ContentView: View {
    @StateObject private var navModel = NavigationModel()
    @SceneStorage("navigation") private var data: Data?

    var body: some View {
        NavigationStack(path: $navModel.recipePath) {
            // Content
        }
        .task {
            if let data { navModel.jsonData = data }
            for await _ in navModel.objectWillChange.values {
                data = navModel.jsonData
            }
        }
    }
}

ポイント:

  • ID を保存し、現在のオブジェクトに解決する
  • Swift 6 の並行処理安全性のために @MainActor
  • シーンスコープの自動永続化には SceneStorage
  • 削除された項目を処理するため、ID の解決には compactMap を使う

パターン 7: Router/Coordinator

使用場面: 複雑なナビゲーションロジック、テスト容易性が必要

所要時間: 30〜45 分

enum AppRoute: Hashable {
    case home
    case category(Category)
    case recipe(Recipe)
    case settings
}

@Observable
@MainActor
class Router {
    var path = NavigationPath()

    func navigate(to route: AppRoute) {
        path.append(route)
    }

    func pop() {
        guard !path.isEmpty else { return }
        path.removeLast()
    }

    func popToRoot() {
        path.removeLast(path.count)
    }

    func showRecipeOfTheDay() {
        popToRoot()
        if let recipe = DataModel.shared.recipeOfTheDay {
            path.append(AppRoute.recipe(recipe))
        }
    }
}

struct ContentView: View {
    @State private var router = Router()

    var body: some View {
        NavigationStack(path: $router.path) {
            HomeView()
                .navigationDestination(for: AppRoute.self) { route in
                    switch route {
                    case .home: HomeView()
                    case .category(let cat): CategoryView(category: cat)
                    case .recipe(let recipe): RecipeDetail(recipe: recipe)
                    case .settings: SettingsView()
                    }
                }
        }
        .environment(router)
    }
}

Coordinator が価値を加える場面:

  • 複雑な条件分岐のあるナビゲーションフロー
  • ナビゲーションロジックに単体テストが必要
  • 複数のビューが同じナビゲーションをトリガーする
  • カスタムトランジションを伴う UIKit との相互運用

Coordinator が価値を加えず複雑さだけを生む場面:

  • シンプルな直線的ナビゲーション
  • 遷移先が 5 個未満
  • ナビゲーションのテストが不要
  • NavigationPath がすでにディープリンクを処理している

アンチパターン (これはやらないでください)

❌ NavigationStack の中に NavigationStack をネストする
// ❌ WRONG — Nested stacks
NavigationStack {
    SomeView()
        .sheet(isPresented: $showSheet) {
            NavigationStack {  // Creates separate stack — confusing
                SheetContent()
            }
        }
}

問題 2 つのナビゲーションスタックが混乱を招く UX を生みます。戻るボタンの動作が不明確になります。 修正 単一の NavigationStack を使い、可能な場合はネストしたナビゲーションなしでシートを提示します。

❌ Button の中で NavigationLink を使う
// ❌ WRONG — Double navigation triggers
Button("Go") {
    // Some action
} label: {
    NavigationLink(value: item) {  // Fires on button AND link
        Text("Item")
    }
}

問題 Button と NavigationLink の両方がタップに反応します。 修正 NavigationLink のみを使い、必要ならアクションを .simultaneousGesture に入れます。

❌ ビューの body 内で NavigationPath を生成する
// ❌ WRONG — Recreated every render
var body: some View {
    let path = NavigationPath()  // Reset on every render!
    NavigationStack(path: .constant(path)) { ... }
}

問題 レンダリングのたびにパスが再生成され、ナビゲーション状態が失われます。 修正 ナビゲーション状態には @State または @StateObject を使います。


プレッシャーシナリオ: 「Instagram のようなナビゲーションにして」

問題

プロダクト/デザインから Instagram のような複雑なナビゲーションを求められます:

  • 「タブごとのナビゲーションスタックを持つタブバー」
  • 「すべてのフローに対するスムーズな coordinator パターン」
  • 「任意の画面へのディープリンク」
  • 「どこからでもアクセスできるプロフィール」
危険信号 — 過剰設計の圧力を見極める

次のいずれかを聞いたら、立ち止まって評価してください。

  • 🚩 「ビューを作る前に、完全なコーディネーター層を構築しましょう」 → たいていは YAGNI です
  • 🚩 「あらゆる状況に対応できるナビゲーションアーキテクチャが必要です」 → スコープクリープです
  • 🚩 「Instagram/TikTok はこの方法でやっています」 → 100 人以上のエンジニアがいます
所要時間の比較
オプション A:過剰設計のコーディネーター
  • コーディネーター層の構築時間:3-5 日
  • 保守とデバッグにかかる時間:継続的
  • 要件変更時にかかる時間:大幅なリファクタリング
オプション B:組み込みナビゲーション + シンプルなルーター
  • Pattern 4(TabView + NavigationStack)の実装時間:2-3 時間
  • 必要に応じて Router を追加する時間:1-2 時間
  • 要件変更時にかかる時間:段階的な追加
プロフェッショナルな反論の方法
ステップ 1:現在のニーズを数値化する
"Let's list our actual navigation flows:
1. Home → Item Detail
2. Search → Results → Item Detail
3. Profile → Settings

That's 6 destinations. NavigationPath handles this natively."
ステップ 2:組み込みの解決策を示す
"Here's our navigation with NavigationStack + NavigationPath:
[Show Pattern 1b code]

This gives us:
- Programmatic navigation ✓
- Deep linking ✓
- State restoration ✓
- Type safety ✓

Without a coordinator layer."
ステップ 3:段階的な進め方を提案する
"If we find NavigationPath insufficient, we can add a Router
(Pattern 7) later. It's 30-45 minutes of work.

But let's start with the simpler solution and add complexity
only when we hit a real limitation."
実例:48 時間での機能リリース

シナリオ:

  • PM:「2 日後のキャンペーン開始に向けて、ディープリンクが必要です」
  • リード:「まず適切なコーディネーターを構築しましょう」
  • 利用可能な時間:16 時間

誤ったアプローチ:

  • 8 時間:コーディネーターのインフラを構築
  • 4 時間:コーディネーターのエッジケースをデバッグ
  • 4 時間:壊れた基盤の上でディープリンクを急いで実装
  • 結果:バグだらけで、期限に間に合わない

正しいアプローチ:

  • 2 時間:Pattern 1b(ディープリンクに対応した NavigationStack)を実装
  • 1 時間:すべてのディープリンク URL をテスト
  • 1 時間:SceneStorage による状態復元(Pattern 6)を追加
  • 結果:4 時間でディープリンクが動作し、仕上げとテストに 12 時間を確保

圧力がかかるシナリオ:「NavigationView の後方互換性」

問題

チームリードの発言:「iOS 15 に対応するため、NavigationView を使いましょう」

危険信号
  • 🚩 NavigationView は iOS 16(2022 年)以降、非推奨です
  • 🚩 iOS のバージョンによって動作が異なり、バグの原因になります
  • 🚩 NavigationPath に対応していないため、ディープリンクを適切に実装できません
共有するデータ
iOS 16+ adoption: 95%+ of active devices (as of 2024)
iOS 15: < 5% and declining

NavigationView limitations:
- No programmatic path manipulation
- No type-safe navigation
- No built-in state restoration
- Behavior varies by iOS version
反論のスクリプト
"NavigationView was deprecated in iOS 16 (2022). Here's the impact:

1. We lose NavigationPath — can't implement deep linking reliably
2. Behavior differs between iOS 15 and 16 — more bugs to maintain
3. iOS 15 is < 5% of users — we're adding complexity for small audience

Recommendation: Set deployment target to iOS 16, use NavigationStack.
If iOS 15 support is required, use NavigationStack with @available
checks and fallback UI for older devices."

コードレビューのチェックリスト

ナビゲーションアーキテクチャ
  • ユースケースに適したコンテナー(Stack、SplitView、TabView)
  • 値ベースの NavigationLink(ビュー形式ではない)
  • navigationDestination が遅延コンテナーの外にある
  • 各タブに個別の NavigationStack がある(タブベースの場合)
状態管理
  • NavigationPath が @State または @StateObject にある(body 内で再作成されない)
  • ナビゲーション状態に @MainActor の分離が適用されている(Swift 6)
  • 復元用に ID が保存されている(完全なオブジェクトではない)
  • デコード失敗時のエラー処理がある
ディープリンク
  • onOpenURL ハンドラーがある
  • パスを構築する前にルートに戻る
  • パスが正しい順序(親 → 子)で構築されている
  • データが見つからない場合も適切に処理される
iOS 26 以降の機能
  • Liquid Glass に干渉するカスタム背景がない
  • iPhone で画面下部に配置された検索が機能する
  • 適切な場合はタブバーの最小化に対応している

トラブルシューティング早見表

症状考えられる原因パターン
ナビゲーションをタップしても反応しないNavigationLink が NavigationStack の外にある階層を確認
タップ時に二重にナビゲーションするNavigationLink を Button で囲んでいるButton のラッパーを削除
タブ切り替え時に状態が失われる複数のタブで NavigationStack を共有しているPattern 4
バックグラウンド移行時に状態が失われるSceneStorage がないPattern 6
ディープリンクで誤った画面が表示されるパスの構築順序が誤っているPattern 1b
復元時にクラッシュするデコード結果を強制アンラップしているエラーを適切に処理

リソース

WWDC:2022-10054、2024-10147、2025-256、2025-323

スキル:axiom-swiftui-nav-diag、axiom-swiftui-nav-ref


最終更新 WWDC 2022-2025 のナビゲーションセッションに基づく プラットフォーム iOS 18 以降、iPadOS 18 以降、macOS 15 以降、watchOS 11 以降、tvOS 18 以降

コメント