Token导航 LogoToken导航

Agent Skill · 研究检索

axiom-swiftui-nav-diag(Axiom Swiftui 导航诊断)

axiom-swiftui-nav-diag 用于查找、检索和筛选相关信息,适合在 Codex、Claude、Cursor、Gemini CLI 中需要根据关键词、任务场景或来源线索快速定位候选结果时使用。可结合来源仓库、安装命令和原始 README 继续核验具体用法。安装前建议确认权限范围、维护状态,以及是否会触发联网、命令执行或文件读写。

axiom-swiftui-nav-diag 是研究检索类 Agent Skill,可用宿主为 Codex、Claude、Cursor、Gemini CLI,通过GitHub 仓库获取,权限为需要联网;许可证为 MIT,第三方扫描已通过,按安装量在研究检索类的 73,675 个 Skill 里排第 22,697。

总安装 4,480周安装 183GitHub Stars 873下载 1,435核验 2026-05-01

安装说明

本站只整理中文说明,不托管安装包,也不代用户安装。
GitHub
许可证
MIT

安装方式

通过对话安装

复制提示词发给支持本地命令或 Skills 的 AI 助手,先确认命令和权限,再让它执行。

请帮我安装这个 Agent Skill:axiom-swiftui-nav-diag(Axiom Swiftui 导航诊断)
来源仓库:https://github.com/charleswiltgen/axiom
仓库路径:skills/axiom-swiftui-nav-diag
安装命令:
npx skills add https://github.com/charleswiltgen/axiom --skill axiom-swiftui-nav-diag
安装前请先检查当前环境是否支持对应 CLI,并向我确认将要执行的命令、安装目录、联网范围和文件读写权限;确认后再执行。

命令行安装

复制命令到本机终端执行。不同来源提供的安装方式可能略有差异;本站展示可直接复制的安装命令,安装前请核对来源页面。

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

这个 Skill 是做什么的

研究检索类 Skill 主要解决「找资料、核对事实、把来源整理清楚」的问题,通常会让助手先检索再总结。

axiom-swiftui-nav-diag 针对导航失败问题提供系统性诊断流程,减少盲目调试时间。适用于路径状态错误、视图标识冲突或侧边栏适配异常等典型导航故障排查。基于 85% 导航问题源于状态管理错误的行业经验,聚焦常见问题模式识别。

使用前需明确当前使用的导航容器类型(如 NavigationStack),以便精准匹配检测项。不建议用于非 SwiftUI 原生导航系统(如 UIKit UINavigationController)相关问题。

来源仓库内路径:skills/axiom-swiftui-nav-diag

上手步骤

4 步
  1. 1

    确认自己的宿主工具

    公开资料标注支持 Codex、Claude、Cursor、Gemini CLI;先确认自己用的是其中之一,再按对应工具的 Skills 目录放置。

  2. 2

    拿到安装命令

    可以直接复制的命令是 npx skills add https://github.com/charleswiltgen/axiom --skill axiom-swiftui-nav…;命令里的占位符换成自己的路径或密钥再执行。

  3. 3

    安装后重启宿主

    命令执行完要完全退出宿主工具再打开,让新装的 Skill 被重新扫描到,否则列表里可能看不到。

  4. 4

    按最小权限试用

    它需要需要联网,先在测试目录或测试账号里跑,确认没问题再放到正式项目。

常见问题

6 条

axiom-swiftui-nav-diag 怎么安装?

在终端执行 npx skills add https://github.com/charleswiltgen/axiom --skill axiom-swiftui-nav-diag。装完完全退出宿主工具再打开,让它重新扫描本地 Skill。

axiom-swiftui-nav-diag 支持哪些 AI 工具?

公开资料标注支持 Codex、Claude、Cursor、Gemini CLI。其他工具只要能读取本地 Skill 目录,一般也能手动放进去,但要自己确认触发方式是否一致。

axiom-swiftui-nav-diag 需要什么权限?

权限级别是需要联网。该 Skill 可能需要联网访问来源站点、仓库或外部 API;具体网络访问范围需要结合源码和 README 复核。

axiom-swiftui-nav-diag 是免费的吗?

来源标注的许可证是 MIT。本站不代收任何费用,也不托管安装包;能否商用、要不要署名,以许可证原文为准。

axiom-swiftui-nav-diag 用的人多吗?

公开数据是累计安装 4,480 次,近一周安装 183 次,下载 1,435 次,GitHub 873 星。这些数字来自第三方目录统计,只能当作热度参考,不代表质量或安全性。

axiom-swiftui-nav-diag 安全吗?

第三方扫描结果为通过,但这只覆盖已知的静态检查项,不能替代自己读一遍源码和安装命令。

值得选的地方和要先想清楚的地方

值得选的地方

  • 同时适配 4 种工具,换客户端不用重新找替代品。
  • 3 个目录同时收录,说明这个 Skill 流传比较广。
  • 许可证明确为 MIT,商用和分发边界好判断。
  • 第三方扫描已通过,常见的高风险行为没有命中。

先想清楚的地方

  • 第三方目录整理,版本更新和行为细节以来源仓库为准。

适不适合你

更适合

  • 已经在用 Codex,想直接扩展它能力的人。
  • 需要处理研究检索类任务、又不想每次都手写提示词的人。
  • 需要许可证清楚、并且有第三方扫描结果的团队项目。

不太适合

  • 需要厂商级技术支持和 SLA 的生产系统。

研究检索类 Skill 对比

6 个同类
Skill总安装GitHub 星权限许可证扫描
find-skills

寻找技能 · Codex、Claude

31,199,81316,486操作浏览器MIT有提醒
self-improving-agent

自我改进 Agent · OpenClaw

16,464,0023,396接触敏感数据MIT-0通过
soultrace

灵魂追踪 · Codex、Claude

7,588,94412需要联网未明确有提醒
self-improving

自我完善 · OpenClaw

6,829,2841,046需要联网MIT-0通过
microsoft-foundry

微软代工厂 · Codex、Claude

6,614,144764调用外部服务未明确有提醒
axiom-swiftui-nav-diag

Axiom Swiftui 导航诊断 · 本页

4,480873需要联网MIT通过

按安装量取同分类前几名;数字来自第三方目录统计,只作热度参考,不代表质量结论。

SKILL.md 原文

SwiftUI Navigation Diagnostics

Overview

Core principle 85% of navigation problems stem from path state management errors, view identity issues, or placement mistakes—not SwiftUI defects.

SwiftUI's navigation system is used by millions of apps and handles complex navigation patterns reliably. If your navigation is failing, not responding, or behaving unexpectedly, the issue is almost always in how you're managing navigation state, not the framework itself.

This skill provides systematic diagnostics to identify root causes in minutes, not hours.

Red Flags — Suspect Navigation Issue

If you see ANY of these, suspect a code issue, not framework breakage:

  • Navigation tap does nothing (link present but doesn't push)
  • Back button pops to wrong screen or root
  • Deep link opens app but shows wrong screen
  • Navigation state lost when switching tabs
  • Navigation state lost when app backgrounds
  • Same NavigationLink pushes twice
  • Navigation animation stuck or janky
  • Crash with navigationDestination in stack trace
  • ❌ FORBIDDEN "SwiftUI navigation is broken, let's wrap UINavigationController"

- NavigationStack is used by Apple's own apps - Wrapping UIKit adds complexity and loses SwiftUI state management benefits - UIKit interop has its own edge cases you'll spend weeks discovering - Your issue is almost certainly path management, not framework defect

Critical distinction NavigationStack behavior is deterministic. If it's not working, you're modifying state incorrectly, have view identity issues, or navigationDestination is misplaced.

Mandatory First Steps

ALWAYS run these checks FIRST (before changing code):

// 1. Add NavigationPath logging
NavigationStack(path: $path) {
    RootView()
        .onChange(of: path.count) { oldCount, newCount in
            print("📍 Path changed: \(oldCount) → \(newCount)")
            // If this never fires, link isn't modifying path
            // If it fires unexpectedly, something else modifies path
        }
}

// 2. Check navigationDestination is visible
// Put temporary print in destination closure
.navigationDestination(for: Recipe.self) { recipe in
    let _ = print("🔗 Destination for Recipe: \(recipe.name)")
    RecipeDetail(recipe: recipe)
}
// If this never prints, destination isn't being evaluated

// 3. Check NavigationLink is inside NavigationStack
// Visual inspection: Trace from NavigationLink up view hierarchy
// Must hit NavigationStack, not another container first

// 4. Check path state location
// @State must be in stable view (not recreated each render)
// Must be @State, @StateObject, or @Observable — not local variable

// 5. Test basic case in isolation
// Create minimal reproduction
NavigationStack {
    NavigationLink("Test", value: "test")
        .navigationDestination(for: String.self) { str in
            Text("Pushed: \(str)")
        }
}
// If this works, problem is in your specific setup
What this tells you
ObservationDiagnosisNext Step
onChange never fires on tapNavigationLink not in NavigationStack hierarchyPattern 1a
onChange fires but view doesn't pushnavigationDestination not found/loadedPattern 1b
onChange fires, view pushes, then immediate popView identity issue or path modificationPattern 2a
Path changes unexpectedly (not from tap)External code modifying pathPattern 2b
Deep link path.append() doesn't navigateTiming issue or wrong threadPattern 3b
State lost on tab switchNavigationStack shared across tabsPattern 4a
Works first time, fails on returnView recreation issuePattern 5a
MANDATORY INTERPRETATION

Before changing ANY code, identify ONE of these:

  1. If link tap does nothing AND no onChange → Link outside NavigationStack (check hierarchy)
  2. If onChange fires but nothing pushes → navigationDestination not in scope (check placement)
  3. If pushes then immediately pops → View identity change or path reset (check @State location)
  4. If deep link fails → Timing or MainActor issue (check thread)
  5. If crash → Force unwrap on path decode or missing type registration
If diagnostics are contradictory or unclear
  • STOP. Do NOT proceed to patterns yet
  • Add print statements at every path modification point
  • Create minimal reproduction case
  • Test with String values first (simplest case)

Decision Tree

Use this to reach the correct diagnostic pattern in 2 minutes:

Navigation problem?
├─ Navigation tap does nothing?
│  ├─ NavigationLink inside NavigationStack?
│  │  ├─ No → Pattern 1a (Link outside Stack)
│  │  └─ Yes → Check navigationDestination
│  │
│  ├─ navigationDestination registered?
│  │  ├─ Inside lazy container? → Pattern 1b (Lazy Loading)
│  │  ├─ Type mismatch? → Pattern 1c (Type Registration)
│  │  └─ Blocked by sheet/popover? → Pattern 1d (Modal Blocking)
│  │
│  └─ Using view-based link?
│     └─ → Pattern 1e (Deprecated API)
│
├─ Unexpected pop back?
│  ├─ Immediate pop after push?
│  │  ├─ View body recreating path? → Pattern 2a (Path Recreation)
│  │  ├─ @State in wrong view? → Pattern 2a (State Location)
│  │  └─ ForEach id changing? → Pattern 2c (Identity Change)
│  │
│  ├─ Pop when shouldn't?
│  │  ├─ External code calling removeLast? → Pattern 2b (Unexpected Modification)
│  │  ├─ Task cancelled? → Pattern 2b (Async Cancellation)
│  │  └─ MainActor issue? → Pattern 2d (Threading)
│  │
│  └─ Back button behavior wrong?
│     └─ → Pattern 2e (Stack Corruption)
│
├─ Deep link not working?
│  ├─ URL not received?
│  │  ├─ onOpenURL not called? → Check URL scheme in Info.plist
│  │  └─ Universal Links issue? → Check apple-app-site-association
│  │
│  ├─ URL received, path not updated?
│  │  ├─ path.append not on MainActor? → Pattern 3a (Threading)
│  │  ├─ Timing issue (app not ready)? → Pattern 3b (Initialization)
│  │  └─ NavigationStack not created yet? → Pattern 3b (Lifecycle)
│  │
│  └─ Path updated, wrong screen shown?
│     ├─ Wrong path order? → Pattern 3c (Path Construction)
│     ├─ Wrong type appended? → Pattern 3c (Type Mismatch)
│     └─ Item not found? → Pattern 3d (Data Resolution)
│
├─ State lost?
│  ├─ Lost on tab switch?
│  │  ├─ Shared NavigationStack? → Pattern 4a (Shared State)
│  │  └─ Tab recreation? → Pattern 4a (Tab Identity)
│  │
│  ├─ Lost on background/foreground?
│  │  ├─ No SceneStorage? → Pattern 4b (No Persistence)
│  │  └─ Decode failure? → Pattern 4c (Decode Error)
│  │
│  └─ Lost on rotation/size change?
│     └─ → Pattern 4d (Layout Recreation)
│
├─ NavigationSplitView issue?
│  ├─ Sidebar not visible on iPad?
│  │  ├─ columnVisibility not set? → Pattern 6a (Column Visibility)
│  │  └─ Compact size class? → Pattern 6a (Automatic Adaptation)
│  │
│  ├─ Detail shows blank on iPad?
│  │  ├─ No default detail view? → Pattern 6b (Missing Detail)
│  │  └─ Selection binding nil? → Pattern 6b (Selection State)
│  │
│  └─ Works on iPhone, broken on iPad?
│     └─ → Pattern 6c (Platform Adaptation)
│
└─ Crash?
   ├─ EXC_BAD_ACCESS in navigation code?
   │  └─ → Pattern 5a (Memory Issue)
   │
   ├─ Fatal error: type not registered?
   │  └─ → Pattern 5b (Missing Destination)
   │
   └─ Decode failure on restore?
      └─ → Pattern 5c (Restoration Crash)

Pattern Selection Rules (MANDATORY)

Before proceeding to a pattern:

  1. Navigation tap does nothing → Add onChange logging FIRST, then Pattern 1
  2. Unexpected pop → Find WHAT is modifying path (logging), then Pattern 2
  3. Deep link fails → Verify URL received (print in onOpenURL), then Pattern 3
  4. State lost → Identify WHEN lost (tab switch vs background), then Pattern 4
  5. Crash → Get full stack trace, then Pattern 5
Apply ONE pattern at a time
  • Implement the fix from one pattern
  • Test thoroughly
  • Only if issue persists, try next pattern
  • DO NOT apply multiple patterns simultaneously (can't isolate cause)
FORBIDDEN
  • Guessing at solutions without diagnostics
  • Changing multiple things at once
  • Wrapping with UINavigationController "because SwiftUI is broken"
  • Adding delays/DispatchQueue.main.async without understanding why
  • Switching to view-based NavigationLink "to avoid path issues"

Diagnostic Patterns

Pattern 1a: NavigationLink Outside NavigationStack

Time cost 5-10 minutes

Symptom
  • Tapping NavigationLink does nothing
  • No navigation occurs, no errors
  • onChange(of: path) never fires
Diagnosis
// Check view hierarchy — NavigationLink must be INSIDE NavigationStack

// ❌ WRONG — Link outside stack
struct ContentView: View {
    var body: some View {
        VStack {
            NavigationLink("Go", value: "test")  // Outside stack!
            NavigationStack {
                Text("Root")
            }
        }
    }
}

// Check: Add background color to NavigationStack
NavigationStack {
    Color.red  // If link is on red, it's inside
}
Fix
// ✅ CORRECT — Link inside stack
struct ContentView: View {
    var body: some View {
        NavigationStack {
            VStack {
                NavigationLink("Go", value: "test")  // Inside stack
                Text("Root")
            }
            .navigationDestination(for: String.self) { str in
                Text("Pushed: \(str)")
            }
        }
    }
}
Verification
  • Tap link, navigation occurs
  • onChange(of: path) fires when tapped

Pattern 1b: navigationDestination in Lazy Container

Time cost 10-15 minutes

Symptom
  • NavigationLink tap does nothing OR works intermittently
  • onChange fires (path updated) but view doesn't push
  • Console may show: "A navigationDestination for [Type] was not found"
Diagnosis
// ❌ WRONG — Destination inside lazy container (may not be loaded)
ScrollView {
    LazyVStack {
        ForEach(items) { item in
            NavigationLink(item.name, value: item)
                .navigationDestination(for: Item.self) { item in
                    ItemDetail(item: item)  // May not be evaluated!
                }
        }
    }
}
Fix
// ✅ CORRECT — Destination outside lazy container
ScrollView {
    LazyVStack {
        ForEach(items) { item in
            NavigationLink(item.name, value: item)
        }
    }
}
.navigationDestination(for: Item.self) { item in
    ItemDetail(item: item)  // Always available
}
Verification
  • Add print in destination closure — should always print on navigation
  • Works regardless of scroll position

Pattern 1c: Type Registration Mismatch

Time cost 10 minutes

Symptom
  • Navigation tap does nothing
  • No matching navigationDestination for the value type
  • May work for some links, not others
Diagnosis
// Check: Value type must EXACTLY match destination type

// Link uses Recipe
NavigationLink(recipe.name, value: recipe)  // value is Recipe

// Destination registered for... Recipe.ID?
.navigationDestination(for: Recipe.ID.self) { id in  // ❌ Wrong type!
    RecipeDetail(id: id)
}
Fix
// Match types exactly
NavigationLink(recipe.name, value: recipe)  // Recipe

.navigationDestination(for: Recipe.self) { recipe in  // ✅ Recipe
    RecipeDetail(recipe: recipe)
}

// OR change link to use ID
NavigationLink(recipe.name, value: recipe.id)  // Recipe.ID

.navigationDestination(for: Recipe.ID.self) { id in  // ✅ Recipe.ID
    RecipeDetail(id: id)
}
Verification
  • Print type in destination: print(type(of: value))
  • Types must match exactly (no inheritance)

Pattern 2a: NavigationPath Recreated Every Render

Time cost 15-20 minutes

Symptom
  • Navigation pushes then immediately pops back
  • Appears to "flash" the destination view
  • Works once, then fails, or fails immediately
Diagnosis
// ❌ WRONG — Path created in view body (reset every render)
struct ContentView: View {
    var body: some View {
        let path = NavigationPath()  // Recreated every time!
        NavigationStack(path: .constant(path)) {
            // ...
        }
    }
}

// ❌ WRONG — @State in child view that gets recreated
struct ParentView: View {
    @State var showChild = true
    var body: some View {
        if showChild {
            ChildView()  // Recreated when showChild toggles
        }
    }
}

struct ChildView: View {
    @State var path = NavigationPath()  // Lost when ChildView recreated
    // ...
}
Fix
// ✅ CORRECT — @State at stable level
struct ContentView: View {
    @State private var path = NavigationPath()  // Persists across renders

    var body: some View {
        NavigationStack(path: $path) {
            RootView()
        }
    }
}

// ✅ CORRECT — @StateObject for ObservableObject
struct ContentView: View {
    @StateObject private var navModel = NavigationModel()

    var body: some View {
        NavigationStack(path: $navModel.path) {
            RootView()
        }
    }
}
Verification
  • Add onChange logging — path should not reset unexpectedly
  • Navigate, wait, path.count stays stable

Pattern 2d: Path Modified Off MainActor

Time cost 10-15 minutes

Symptom
  • Navigation works sometimes, fails others
  • Swift 6 warnings about MainActor isolation
  • Unexpected pops or state corruption
Diagnosis
// ❌ WRONG — Modifying path from background task
func loadAndNavigate() async {
    let recipe = await fetchRecipe()
    path.append(recipe)  // ⚠️ Not on MainActor!
}

// Check: Search for path.append, path.removeLast outside @MainActor context
Fix
// ✅ CORRECT — Ensure MainActor
@MainActor
func loadAndNavigate() async {
    let recipe = await fetchRecipe()
    path.append(recipe)  // ✅ MainActor isolated
}

// OR explicitly dispatch
func loadAndNavigate() async {
    let recipe = await fetchRecipe()
    await MainActor.run {
        path.append(recipe)
    }
}

// ✅ BEST — Use @Observable with @MainActor
@Observable
@MainActor
class Router {
    var path = NavigationPath()

    func navigate(to value: any Hashable) {
        path.append(value)
    }
}
Verification
  • No Swift 6 concurrency warnings
  • Navigation consistent regardless of timing

Pattern 3a: Deep Link Threading Issue

Time cost 15-20 minutes

Symptom
  • Deep link URL received (onOpenURL fires)
  • path.append called but navigation doesn't happen
  • Works when app is in foreground, fails from cold start
Diagnosis
// ❌ WRONG — May be called before NavigationStack exists
.onOpenURL { url in
    handleDeepLink(url)  // NavigationStack may not be rendered yet
}

func handleDeepLink(_ url: URL) {
    path.append(parsedValue)  // Modifies path that doesn't exist yet
}
Fix
// ✅ CORRECT — Defer deep link handling
@State private var pendingDeepLink: URL?
@State private var isReady = false

var body: some View {
    NavigationStack(path: $path) {
        RootView()
            .onAppear {
                isReady = true
                if let url = pendingDeepLink {
                    handleDeepLink(url)
                    pendingDeepLink = nil
                }
            }
    }
    .onOpenURL { url in
        if isReady {
            handleDeepLink(url)
        } else {
            pendingDeepLink = url  // Queue for later
        }
    }
}
Verification
  • Test deep link from cold start (app killed)
  • Test deep link when app in background
  • Test deep link when app in foreground

Pattern 3c: Deep Link Path Construction Order

Time cost 10-15 minutes

Symptom
  • Deep link navigates but to wrong screen
  • Shows intermediate screen instead of final destination
  • Path appears correct but wrong view displayed
Diagnosis
// ❌ WRONG — Wrong order (child before parent)
// URL: myapp://category/desserts/recipe/apple-pie
func handleDeepLink(_ url: URL) {
    path.append(recipe)    // Recipe pushed first
    path.append(category)  // Category pushed second — WRONG ORDER
}
// User sees Category screen, not Recipe screen
Fix
// ✅ CORRECT — Parent before child
func handleDeepLink(_ url: URL) {
    path.removeLast(path.count)  // Clear existing

    // Build hierarchy: parent → child
    path.append(category)  // First: Category
    path.append(recipe)    // Second: Recipe (shows this screen)
}

// For complex paths, build array first
var newPath: [any Hashable] = []
// Parse URL segments...
newPath.append(category)
newPath.append(subcategory)
newPath.append(item)

// Then apply
path = NavigationPath(newPath)
Verification
  • Print path after construction
  • Final item in path should be the destination screen

Pattern 4a: Shared NavigationStack Across Tabs

Time cost 15-20 minutes

Symptom
  • Navigate in Tab A, switch to Tab B
  • Return to Tab A — navigation state lost (back at root)
  • Or: Navigation from Tab A appears in Tab B
Diagnosis
// ❌ WRONG — Single NavigationStack wrapping TabView
NavigationStack(path: $path) {
    TabView {
        Tab("Home") { HomeView() }
        Tab("Settings") { SettingsView() }
    }
}
// All tabs share same navigation — state mixed/lost

// ❌ WRONG — Same @State used across tabs
@State var path = NavigationPath()  // Shared
TabView {
    Tab("Home") {
        NavigationStack(path: $path) { ... }  // Uses shared path
    }
    Tab("Settings") {
        NavigationStack(path: $path) { ... }  // Same path!
    }
}
Fix
// ✅ CORRECT — Each tab has own NavigationStack
TabView {
    Tab("Home", systemImage: "house") {
        NavigationStack {  // Own stack
            HomeView()
                .navigationDestination(for: HomeItem.self) { ... }
        }
    }
    Tab("Settings", systemImage: "gear") {
        NavigationStack {  // Own stack
            SettingsView()
                .navigationDestination(for: SettingItem.self) { ... }
        }
    }
}

// For per-tab path tracking:
struct HomeTab: View {
    @State private var path = NavigationPath()  // Tab-specific

    var body: some View {
        NavigationStack(path: $path) {
            HomeView()
        }
    }
}
Verification
  • Navigate in Tab A, switch tabs, return — state preserved
  • Each tab maintains independent navigation history

Pattern 4b: No State Persistence on Background

Time cost 15-20 minutes

Symptom
  • Navigate to screen, background app
  • Kill app or wait for system to terminate
  • Relaunch — navigation state lost (back at root)
Diagnosis
// ❌ WRONG — No persistence mechanism
@State private var path = NavigationPath()
// Path lost when app terminates
Fix
// ✅ CORRECT — Use SceneStorage + Codable
struct ContentView: View {
    @StateObject private var navModel = NavigationModel()
    @SceneStorage("navigation") private var savedData: Data?

    var body: some View {
        NavigationStack(path: $navModel.path) {
            RootView()
        }
        .task {
            // Restore on appear
            if let data = savedData {
                navModel.restore(from: data)
            }
            // Save on changes
            for await _ in navModel.objectWillChange.values {
                savedData = navModel.encoded()
            }
        }
    }
}

@MainActor
class NavigationModel: ObservableObject {
    @Published var path = NavigationPath()

    func encoded() -> Data? {
        guard let codable = path.codable else { return nil }
        return try? JSONEncoder().encode(codable)
    }

    func restore(from data: Data) {
        guard let codable = try? JSONDecoder().decode(
            NavigationPath.CodableRepresentation.self,
            from: data
        ) else { return }
        path = NavigationPath(codable)
    }
}
Verification
  • Navigate deep, background app
  • Kill app via Xcode
  • Relaunch — state restored

Pattern 5b: Missing navigationDestination Registration

Time cost 10-15 minutes

Symptom
  • Crash: "No destination found for [Type]"
  • Or navigation silently fails
  • Happens when pushing certain types
Diagnosis
// Every type pushed on path needs a destination

// You push Recipe
path.append(recipe)  // Recipe type

// But only registered Category
.navigationDestination(for: Category.self) { ... }
// No destination for Recipe!
Fix
// Register ALL types you might push
NavigationStack(path: $path) {
    RootView()
        .navigationDestination(for: Category.self) { category in
            CategoryView(category: category)
        }
        .navigationDestination(for: Recipe.self) { recipe in
            RecipeDetail(recipe: recipe)
        }
        .navigationDestination(for: Chef.self) { chef in
            ChefProfile(chef: chef)
        }
}

// Or use enum route type for single registration
enum AppRoute: Hashable {
    case category(Category)
    case recipe(Recipe)
    case chef(Chef)
}

.navigationDestination(for: AppRoute.self) { route in
    switch route {
    case .category(let cat): CategoryView(category: cat)
    case .recipe(let recipe): RecipeDetail(recipe: recipe)
    case .chef(let chef): ChefProfile(chef: chef)
    }
}
Verification
  • List all types you push on path
  • Verify each has matching navigationDestination

Pattern 5c: State Restoration Decode Crash

Time cost 15-20 minutes

Symptom
  • Crash on app launch
  • Stack trace shows JSON decode failure
  • Happens after app update or data model change
Diagnosis
// ❌ WRONG — Force unwrap decode
func restore(from data: Data) {
    let codable = try! JSONDecoder().decode(  // 💥 Crashes!
        NavigationPath.CodableRepresentation.self,
        from: data
    )
    path = NavigationPath(codable)
}

// Crash reasons:
// - Saved path contains type that no longer exists
// - Codable encoding changed between versions
// - Saved item was deleted
Fix
// ✅ CORRECT — Graceful decode with fallback
func restore(from data: Data) {
    do {
        let codable = try JSONDecoder().decode(
            NavigationPath.CodableRepresentation.self,
            from: data
        )
        path = NavigationPath(codable)
    } catch {
        print("Navigation restore failed: \(error)")
        path = NavigationPath()  // Start fresh
        // Optionally clear bad saved data
    }
}

// ✅ BETTER — Store IDs, resolve to objects
class NavigationModel: ObservableObject, Codable {
    var selectedIds: [String] = []  // Store IDs

    func resolvedPath(dataModel: DataModel) -> NavigationPath {
        var path = NavigationPath()
        for id in selectedIds {
            if let item = dataModel.item(withId: id) {
                path.append(item)
            }
            // Missing items silently skipped
        }
        return path
    }
}
Verification
  • Delete saved state, launch app — no crash
  • Simulate bad data — graceful fallback
  • Change data model, launch — handles mismatch

Production Crisis Scenario

Context: Navigation Randomly Breaks After iOS Update

Situation
  • iOS 18 ships on Tuesday
  • By Wednesday, support tickets surge: "navigation broken"
  • 20% of users report tapping links does nothing
  • Some users report navigation "resets randomly"
  • CTO asks: "What's the ETA on a fix?"
Pressure signals
  • 🚨 Production issue 20% of users affected
  • ⏰ Time pressure "Users are leaving bad reviews"
  • 👔 Executive visibility CTO personally tracking
  • 📱 Platform change New iOS version
Rationalization traps (DO NOT fall into these)
  1. *"It's an iOS 18 bug, wait for Apple to fix"*

- If 80% of users work fine, it's not iOS - Apple's apps use same NavigationStack - Your code has an edge case exposed by iOS changes

  1. *"Let's wrap UINavigationController"*

- 2-3 week rewrite - Lose SwiftUI state management - UIKit has its own iOS 18 changes - Doesn't address root cause

  1. *"Add retry logic for navigation"*

- Navigation is synchronous — retries don't help - Masks symptom, doesn't fix cause - Makes debugging harder

  1. *"Roll back to pre-iOS 18 version"*

- Can't control user iOS version - App Store version must support iOS 18 - Doesn't fix the issue

MANDATORY Diagnostic Protocol

You have 2 hours to provide CTO with:

  1. Root cause
  2. Fix timeline
  3. Workaround for affected users
Step 1: Identify Pattern (30 minutes)
// Release build with diagnostic logging
#if DEBUG || DIAGNOSTIC
NavigationStack(path: $path) {
    // ...
}
.onChange(of: path.count) { old, new in
    Analytics.log("nav_path_change", ["old": old, "new": new])
}
#endif

// Check analytics for:
// - path.count going to 0 unexpectedly → Path recreation
// - path.count increasing but no push → Missing destination
// - No path changes at all → Link not firing
Step 2: Cross-Reference with iOS 18 Changes (15 minutes)
// iOS 18 changes that affect navigation:
// 1. Stricter MainActor enforcement
// 2. Changes to view identity in TabView
// 3. New navigation lifecycle timing

// Most common iOS 18 issue:
// Code that worked by accident now fails

// Check: Any path modifications in async contexts without @MainActor?
Task {
    let result = await fetch()
    path.append(result)  // ⚠️ iOS 18 stricter about this
}
Step 3: Apply Targeted Fix (30 minutes)
// Root cause found: NavigationPath modified from async context
// iOS 17 was lenient, iOS 18 enforces MainActor properly

// ❌ Old code (worked on iOS 17, breaks on iOS 18)
func loadAndNavigate() async {
    let recipe = await fetchRecipe()
    path.append(recipe)  // Race condition
}

// ✅ Fix: Explicit MainActor isolation
@MainActor
func loadAndNavigate() async {
    let recipe = await fetchRecipe()
    path.append(recipe)  // ✅ Safe
}

// OR: Annotate entire class
@Observable
@MainActor
class Router {
    var path = NavigationPath()

    func navigate(to value: any Hashable) {
        path.append(value)
    }
}
Step 4: Validate and Deploy (45 minutes)
// 1. Test on iOS 17 device — still works
// 2. Test on iOS 18 device — now works
// 3. Test all navigation paths
// 4. Submit expedited review

// Expedited review justification:
// "Critical bug fix for iOS 18 compatibility affecting 20% of users"
Professional Communication Templates
To CTO (45 minutes after starting)
Root cause identified: Navigation code wasn't properly isolated
to the main thread. iOS 18 enforces this more strictly than iOS 17.

Fix: Add @MainActor annotation to navigation code.
Already tested on iOS 17 (no regression) and iOS 18 (fixes issue).

Timeline:
- Fix ready: Now
- QA validation: 1 hour
- App Store submission: Today
- Available to users: 24-48 hours (expedited review)

Workaround for affected users: Force quit and relaunch app
often clears the issue temporarily.
To Engineering Team
iOS 18 Navigation Fix

Root cause: NavigationPath modifications in async contexts
without @MainActor isolation. iOS 17 was permissive, iOS 18 enforces.

Fix applied:
- Added @MainActor to Router class
- Updated all path.append/removeLast calls to be MainActor-isolated
- Added Swift 6 concurrency checking to catch future issues

Files changed: Router.swift, ContentView.swift, DeepLinkHandler.swift

Testing needed:
- All navigation flows
- Deep links from cold start
- Tab switching with navigation state
- Background/foreground with navigation state

Pattern 6: NavigationSplitView Platform Issues

Time cost 10-20 minutes

NavigationSplitView adapts automatically between compact (iPhone) and regular (iPad) size classes. Most issues arise because developers test only on iPhone, where it collapses to a NavigationStack.

Pattern 6a: Sidebar/Column Not Visible

Symptom
  • Sidebar doesn't appear on iPad
  • App shows detail view only, no way to navigate back
  • Works fine on iPhone (collapses to single column)
Diagnosis
// Check 1: Is columnVisibility controlling visibility?
@State private var columnVisibility: NavigationSplitViewVisibility = .all

NavigationSplitView(columnVisibility: $columnVisibility) {
    // sidebar
} detail: {
    // detail
}

// Check 2: Are you in compact size class? (iPhone or iPad slide-over)
// In compact, NavigationSplitView collapses to NavigationStack automatically
// The sidebar becomes the root of the stack
Fix
  • Bind columnVisibility to control initial state (.all, .doubleColumn, .detailOnly)
  • Test on iPad in full screen AND slide-over (compact)
  • Use navigationSplitViewStyle(.balanced) or .prominentDetail to control column proportions

Pattern 6b: Blank Detail View on iPad

Symptom
  • iPad launches to blank right panel
  • Sidebar shows list but detail area is empty
  • iPhone works fine (no detail visible until selection)
Fix — Provide Default Detail
NavigationSplitView {
    List(items, selection: $selectedItem) { item in
        Text(item.name)
    }
} detail: {
    if let selectedItem {
        ItemDetailView(item: selectedItem)
    } else {
        ContentUnavailableView("Select an Item",
            systemImage: "sidebar.left",
            description: Text("Choose an item from the sidebar"))
    }
}

Key insight: iPad shows the detail column immediately on launch. Without a default view, it's blank. iPhone doesn't show this because it starts on the sidebar.

Pattern 6c: Platform Adaptation Issues

Symptom
  • Navigation works on one platform, broken on another
  • List selection behaves differently on iPhone vs iPad
Diagnosis

NavigationSplitView uses different navigation models per size class:

  • Regular (iPad full screen): Side-by-side columns, selection drives detail
  • Compact (iPhone, iPad slide-over): Collapses to NavigationStack, selection pushes
// Common mistake: using NavigationLink inside NavigationSplitView sidebar
// This creates DOUBLE navigation on iPad (link push + selection)
// Fix: Use List(selection:) binding, not NavigationLink
NavigationSplitView {
    List(items, selection: $selectedID) { item in  // ✅ selection binding
        Text(item.name)
    }
} detail: {
    // driven by selectedID
}

Test on both iPhone AND iPad before shipping. Most NavigationSplitView bugs are platform-specific.


Quick Reference Table

SymptomLikely CauseFirst CheckPatternFix Time
Link tap does nothingLink outside stackView hierarchy1a5-10 min
Intermittent navigation failureDestination in lazy containerDestination placement1b10-15 min
Works for some types, not othersType mismatchPrint type(of:)1c10 min
Push then immediate popPath recreated@State location2a15-20 min
Random unexpected popsExternal path modificationAdd logging2b15-20 min
Works on MainActor, fails in TaskThreading issueCheck @MainActor2d10-15 min
Deep link doesn't navigateNot on MainActorThread check3a15-20 min
Deep link from cold start failsTiming/lifecycleAdd pendingDeepLink3b15-20 min
Deep link shows wrong screenPath order wrongPrint path contents3c10-15 min
State lost on tab switchShared NavigationStackCheck Tab structure4a15-20 min
State lost on backgroundNo persistenceAdd SceneStorage4b20-25 min
Crash on launch (decode)Force unwrap decodeError handling5c15-20 min
"No destination found" crashMissing registrationList all types5b10-15 min
Sidebar missing on iPadcolumnVisibilityCheck binding6a10-15 min
Blank detail on iPadNo default detailAdd ContentUnavailableView6b10 min
Works iPhone, broken iPadPlatform adaptationTest both size classes6c15-20 min

Common Mistakes

Mistake 1: Putting navigationDestination Inside ForEach

Problem Destination not loaded when needed (lazy evaluation).

Why it fails LazyVStack/ForEach don't evaluate all children. Destination may not exist when link is tapped.

Fix
// Move destination OUTSIDE lazy container
List {
    ForEach(items) { item in
        NavigationLink(item.name, value: item)
    }
}
.navigationDestination(for: Item.self) { item in
    ItemDetail(item: item)
}

Mistake 2: Using NavigationView on iOS 16+

Problem NavigationView deprecated, different behavior across versions.

Why it fails No NavigationPath support, can't programmatically navigate or deep link reliably.

Fix
  • Replace NavigationView with NavigationStack or NavigationSplitView
  • Use value-based NavigationLink(title, value:) instead of view-based

Mistake 3: Creating NavigationPath in computed property

Problem Path reset every access.

Why it fails var body is called repeatedly. Creating path there means it's reset constantly.

Fix
// Use @State, not computed
@State private var path = NavigationPath()  // ✅ Persists

// NOT
var path: NavigationPath { NavigationPath() }  // ❌ Reset every time

Mistake 4: Not Handling Decode Errors in Restoration

Problem Crash when saved navigation data is invalid.

Why it fails Data model changes, items deleted, encoding format changes between app versions.

Fix
  • Always use try? or do/catch for decode
  • Provide fallback (empty path)
  • Consider storing IDs and resolving to objects

Mistake 5: Assuming Deep Links Work Immediately

Problem Deep link on cold start fails.

Why it fails onOpenURL may fire before NavigationStack is rendered.

Fix
  • Queue deep link URL
  • Process after onAppear of NavigationStack
  • Use isReady flag pattern

Cross-References

For Preventive Patterns

swiftui-nav skill — Discipline-enforcing anti-patterns:

  • Red Flags: NavigationView, view-based links, path in body
  • Pattern 1a-7: Correct implementation patterns
  • Pressure Scenarios: How to handle architecture pressure

For API Reference

swiftui-nav-ref skill — Complete API documentation:

  • NavigationStack, NavigationSplitView, NavigationPath full API
  • All WWDC code examples with timestamps
  • Router/Coordinator patterns with testing
  • iOS 26+ features (Liquid Glass, bottom search)

For Related Issues

swift-concurrency skill — If MainActor issues:

  • Pattern 3: @MainActor isolation patterns
  • Async/await with UI updates
  • Task cancellation handling

Last Updated 2025-12-05 Status Production-ready diagnostics Tested Diagnostic patterns validated against common navigation issues

平台分布

Claude Code

27.62%
按下载量换算396

OpenCode

22.37%
按下载量换算321

Codex

16.73%
按下载量换算240

Antigravity

13.48%
按下载量换算193

Cursor

7.39%
按下载量换算106

Gemini CLI

3.32%
按下载量换算48

安全审计

Gen Agent Trust Hub

通过

Socket

通过

Snyk

通过

权限和风险

需要联网

该 Skill 可能需要联网访问来源站点、仓库或外部 API;具体网络访问范围需要结合源码和 README 复核。

安装前确认

本站仅展示第三方公开信息,不托管安装包,不提供自动安装或运行环境。安装前应自行审查源码、依赖和命令行为。

继续浏览同类 Skills