You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Widget跳转至NavigationSplitView详情页的实现问题

问题描述

使用NavigationSplitView时,希望实现主屏幕/锁屏Widget点击后打开App并跳转至对应工具页面,但当前Widget与导航结构的适配存在问题。已验证URL Scheme有效性,尝试过通知、传递工具参数等方法仍无法解决。

当前代码实现

App 入口代码

@main
struct SparkysCircKitApp: App {
    @StateObject private var navManager = NavigationManager.shared
   
    @Namespace var animationNamespace
    
    @State private var selectedTool: Tool?
    
    let deviceType = UIDevice.current.userInterfaceIdiom
    
    var body: some Scene {
        WindowGroup {
            if deviceType == .pad {
                iPadListView(animationNamespace: animationNamespace, selectedTool: selectedTool?.route)
                        .environmentObject(accentColorManager)
                        .environmentObject(calculatorViewModel)
                        .environmentObject(navManager)
                        .environmentObject(settingsViewModel)
                        .environmentObject(toolStore)
            } else {
             // iPHONE VIEWS
        }
    }
        
    private func handleDeepLink(_ url: URL) {
        print("Handling deep link: \(url)")
        
        guard url.scheme == "sparky" else {
            print("Invalid URL scheme: \(url.scheme ?? "nil")")
            return
        }
        
        guard let host = url.host else {
            print("No host in URL")
            return
        }
        
        // Convert the hyphenated URL format back to a tool name
        let toolName: String
        if host == "nema-configurations" {
            toolName = "configurations"
        } else {
            toolName = host
                .replacingOccurrences(of: "-", with: " ")
                .localizedCapitalized
        }
        
        DispatchQueue.main.async {
            if toolName.lowercased() == "calculator" {
                navManager.isCalculatorPresented = true
            } else if toolName.lowercased() == "codeoftheday", let ruleNumber = url.pathComponents.last {
                NotificationCenter.default.post(
                    name: .showCodeOfTheDay,
                    object: ruleNumber
                )
            } else if let tool = toolStore.findTool(byName: toolName) {
                navManager.popToRoot()
                navManager.navigate(to: tool.route)
                selectedTool = tool
            } else {
                print("Could not find tool for name: \(toolName)")
            }
        }
    }
}

iPad 导航视图代码

struct iPadListView: View {
    @EnvironmentObject var navManager: NavigationManager
    @EnvironmentObject var toolStore: ToolStore
    
    @State private var selectedTool: ToolRoute? = nil

    var animationNamespace: Namespace.ID

    init(
        animationNamespace: Namespace.ID,
        selectedTool: ToolRoute?
    ) {
        self.animationNamespace = animationNamespace
        self.selectedTool = selectedTool
    }
    
    
    var body: some View {
        NavigationSplitView {
            List(selection: $selectedTool) {
// LIST VIEW
        } detail: {
         if let toolRoute = selectedTool {
                ToolRouteView(route: toolRoute)
                    .environmentObject(accentColorManager)
                    .environmentObject(calculatorViewModel)
                    .environmentObject(navManager)
                    .environmentObject(settingsViewModel)
                    .environmentObject(toolStore)
            } else {
                ContentUnavailableView("Select a Tool",
                                       systemImage: "hammer.circle",
                                       description: Text("Choose a tool from the sidebar to get started"))
            }
        }

        .onChange(of: selectedTool) {
            navManager.activeRoute = selectedTool
        }

        .onOpenURL { url in
            handleWidgetDeepLink(url)
        }
    }
    
    private func handleWidgetDeepLink(_ url: URL) {
        print("Handling iPad widget deep link: \(url)")
        
        guard url.scheme == "sparky" else {
            print("Invalid URL scheme: \(url.scheme ?? "nil")")
            return
        }
        
        guard let host = url.host else {
            print("No host in URL")
            return
        }
        
        let toolName: String
        if host == "nema-configurations" {
            toolName = "configurations"
        } else {
            toolName = host
                .replacingOccurrences(of: "-", with: " ")
                .localizedCapitalized
        }
        
        DispatchQueue.main.async {
            if toolName.lowercased() == "calculator" {
                showingCalc = true
            } else if toolName.lowercased() == "codeoftheday", let ruleNumber = url.pathComponents.last {
                if let code = codes.first(where: { $0.ruleNumber == ruleNumber }) {
                    codeOfTheDay = code
                }
            }
        }
    }
}
解决方案

核心问题是导航状态分散管理,导致Widget跳转时无法同步NavigationSplitView的选中状态。以下是修复步骤:

1. 统一导航状态管理

将所有导航逻辑收敛到NavigationManager,避免App层和View层重复处理DeepLink:

class NavigationManager: ObservableObject {
    static let shared = NavigationManager()
    
    @Published var activeRoute: ToolRoute?
    @Published var isCalculatorPresented = false
    @Published var selectedCodeOfTheDay: Code?
    
    func handleDeepLink(_ url: URL, toolStore: ToolStore, codes: [Code]) {
        guard url.scheme == "sparky", let host = url.host else { return }
        
        let toolName: String
        if host == "nema-configurations" {
            toolName = "configurations"
        } else {
            toolName = host.replacingOccurrences(of: "-", with: " ").localizedCapitalized
        }
        
        DispatchQueue.main.async {
            switch toolName.lowercased() {
            case "calculator":
                self.isCalculatorPresented = true
            case "codeoftheday":
                if let ruleNumber = url.pathComponents.last,
                   let code = codes.first(where: { $0.ruleNumber == ruleNumber }) {
                    self.selectedCodeOfTheDay = code
                }
            default:
                if let tool = toolStore.findTool(byName: toolName) {
                    self.activeRoute = tool.route
                }
            }
        }
    }
}

2. 简化App层代码

移除App层的selectedTool状态,统一通过NavigationManager处理DeepLink:

@main
struct SparkysCircKitApp: App {
    @StateObject private var navManager = NavigationManager.shared
    @Namespace var animationNamespace
    let deviceType = UIDevice.current.userInterfaceIdiom
    
    var body: some Scene {
        WindowGroup {
            if deviceType == .pad {
                iPadListView(animationNamespace: animationNamespace)
                    .environmentObject(accentColorManager)
                    .environmentObject(calculatorViewModel)
                    .environmentObject(navManager)
                    .environmentObject(settingsViewModel)
                    .environmentObject(toolStore)
            } else {
                // iPhone Views
            }
        }
        .onOpenURL { url in
            navManager.handleDeepLink(url, toolStore: toolStore, codes: codes)
        }
    }
}

3. 同步NavigationSplitView状态

让NavigationSplitView的选中状态直接绑定到NavigationManager的activeRoute,移除View层的重复状态和DeepLink处理:

struct iPadListView: View {
    @EnvironmentObject var navManager: NavigationManager
    @EnvironmentObject var toolStore: ToolStore
    var animationNamespace: Namespace.ID
    
    var body: some View {
        NavigationSplitView {
            List(selection: $navManager.activeRoute) {
                // LIST VIEW 内容,每个ListItem的tag对应ToolRoute枚举值
            } detail: {
                if let toolRoute = navManager.activeRoute {
                    ToolRouteView(route: toolRoute)
                        .environmentObject(accentColorManager)
                        .environmentObject(calculatorViewModel)
                        .environmentObject(navManager)
                        .environmentObject(settingsViewModel)
                        .environmentObject(toolStore)
                } else {
                    ContentUnavailableView("Select a Tool",
                                           systemImage: "hammer.circle",
                                           description: Text("Choose a tool from the sidebar to get started"))
                }
            }
            .sheet(isPresented: $navManager.isCalculatorPresented) {
                // 计算器模态视图
            }
            .sheet(item: $navManager.selectedCodeOfTheDay) { code in
                // 每日代码详情视图
            }
        }
    }
}

关键修复点

  • 所有导航状态由NavigationManager统一管理,避免多源状态导致的不同步
  • DeepLink逻辑收敛到NavigationManager,App启动和Widget跳转共用同一套解析逻辑
  • NavigationSplitView的列表选中状态直接绑定到navManager.activeRoute,自动同步详情页内容
  • 特殊页面(计算器、每日代码)通过NavigationManager的状态控制模态展示

内容的提问来源于stack exchange,提问作者The Sparky Software

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.15 07:29:52