DEBUG.WIKI
京东云;让 AI 进入真实业务;零售 · 物流 · 客服 · 硬件;查看方案 →
ZgoCloud:多地区部署,按需选节点;日本、美国、香港、德国
Evoxt;最高 6.0GHz CPU;高频云服务器 · 免费每周备份;查看配置 →

NavigationStack 点链接没反应:排查 no matching navigationDestination

NavigationLink 点了不跳转,按注册位置、值类型、懒容器和导航栈上下文排查 no matching navigationDestination,并统一类型化路径。

点击 NavigationLink(value:) 后没有跳转,控制台提示 no matching navigationDestination declaration visible from the location of the link,意味着当前链接所在的导航上下文找不到对应值类型的目的地。即使项目里已经写了 navigationDestination,它的位置、注册类型或所属导航栈仍可能不对。

本文适用于 iOS 16 及以后的值驱动 NavigationStack。旧式 NavigationLink(destination:) 直接提供目标视图,不按本文的类型注册方式匹配。

目的地修饰器要放在栈内部

先检查大括号的位置。下面的修饰器附在整个 NavigationStack 的外侧,并没有放到其内容视图上:

NavigationStack {
    NavigationLink("登录", value: "login")
}
.navigationDestination(for: String.self) { value in
    Text(value)
}

应把注册移到栈内的内容视图。下面给出可以作为独立页面使用的示例,同时用枚举避免字符串拼写不一致:

import SwiftUI

enum AccountRoute: Hashable {
    case login
    case register
}

struct AccountScreen: View {
    @State private var path: [AccountRoute] = []

    var body: some View {
        NavigationStack(path: $path) {
            VStack {
                NavigationLink("登录", value: AccountRoute.login)
                NavigationLink("注册", value: AccountRoute.register)
                Button("用路径打开登录") {
                    path.append(.login)
                }
            }
            .navigationDestination(for: AccountRoute.self) { route in
                switch route {
                case .login:
                    Text("登录表单")
                case .register:
                    Text("注册表单")
                }
            }
        }
    }
}

这里两个链接、路径数组和目的地都使用 AccountRoute。示例里的 Text 是目标页面占位视图,接入项目时再换成实际的登录、注册界面。

检查匹配的是类型,不是显示文字

NavigationLink("登录", value: "login") 传入的是 String;navigationDestination(for: AccountRoute.self) 注册的是 AccountRoute。即使它们表达相同业务含义,也不会相互匹配。

程序化导航也一样。若使用 NavigationPath 存放多种类型,需要为其中实际压入的值注册目的地;若整个流程只有一种路由类型,示例中的 [AccountRoute] 更容易通过编译器约束误传类型。不要为了绕过类型错误把所有值都转换成字符串,却保留原模型类型的目的地注册。

排查时先记录点击位置实际传入的值类型,再核对 for: 后面的类型,并确认压入的是业务值,而不是包装了该值的另一个结构体。这一步能区分“没有找到注册”和“找到了注册,但目的地内部条件没有显示内容”。

List 的外侧注册,仍然要在 NavigationStack 内侧

List、LazyVStack 等容器按需创建子视图。不要把公共目的地注册藏在某一行内部,否则注册可能随该行的创建和移除而变化。

NavigationStack {
    List {
        NavigationLink("登录", value: AccountRoute.login)
        NavigationLink("注册", value: AccountRoute.register)
    }
    .navigationDestination(for: AccountRoute.self) { route in
        Text(route == .login ? "登录表单" : "注册表单")
    }
}

这段修饰器附在 List 上,位置在列表子行的外侧,同时仍处于导航栈内容中。它与放在每个 NavigationLink 后面或移到整个栈外面不是同一个作用域。

最小示例能跳转,实际页面仍失败时

继续从出问题的链接向上检查实际视图层级。额外嵌套的 NavigationStack、通过 sheet 打开的另一套页面,以及 NavigationSplitView 的不同列,都可能让链接处于另一个导航上下文。不要仅因某个祖先文件中存在同名注册,就认定当前链接能够看到它。

先把链接和一个简单目的地放进同一个最小导航栈,确认匹配成立,再逐层恢复实际结构,找到从哪一层开始失效。若点击已进入目的地构建闭包,却显示空白,就检查闭包里的条件分支、数据是否存在和目标视图本身;此时修饰器作用域已经不是首要问题。

修复后的检查应包括点击链接、程序化追加路径、返回上一级以及重新进入。目标页面应与压入值对应,返回时路径也应缩短,控制台不再出现类型或目的地不可见的提示。不要通过反复新增导航栈解决这条错误,新栈会增加状态和作用域的分歧。