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

SwiftUI中如何配置onOpenUrl实现Widget跳转指定视图?

问题描述

在大尺寸Widget视图LargeMetarView中添加了Link组件,使每个机场条目可点击跳转,但点击后仅打开主应用首页,无法跳转到主应用内的WidgetStationView展示选中机场的详情。目前仅需实现链接跳转功能,尚未完成视图的完整数据展示。此前主要使用UIKit开发,刚接触SwiftUI,怀疑问题出在URL Scheme、host及path的定义上。

相关代码

LargeMetarView 代码

struct LargeMetarView: View {
    @ObservedObject var metarservice = MetarService()
    @State private var skyImage = String()
    var entry: MetarEntry
    var counter: Int = 0
    
    var body: some View {
        ZStack(alignment: .topLeading) {
            LinearGradient(gradient: Gradient(colors: [.black.opacity(1), .black.opacity(0.7), .black.opacity(1)]), startPoint: .bottomLeading, endPoint: .topTrailing)
            HStack(alignment: .top) {
                Text("FRAT Metars")
                Text("Updated: ")
                Text(entry.date, style: .time)
            }
            .frame(maxWidth: .infinity)
            .padding(10)
            .background(.blue)
            .foregroundColor(.white)
            .clipped()
            .shadow(radius: 8)
            
            HStack(alignment: .top, spacing: 8) {
                VStack(alignment: .leading, spacing: 5){
                    Spacer()
                    ForEach(entry.metars) { entries in
                        
                        Link(destination: URL(string: "frat://metar/\(entries.mairport)")!){
                            HStack(spacing: 5){
                                switch entries.mfltcat {
                                case "VFR":
                                    Image(systemName: entries.metarImage)
                                        .renderingMode(.original)
                                        .font(.system(size: 25))
                                        .foregroundColor(.green)
                                        .shadow(color: .black, radius: 1, x: 0.5, y: 0.5)
                                case "MVFR":
                                    Image(systemName: entries.metarImage)
                                        .renderingMode(.original)
                                        .font(.system(size: 25))
                                        .foregroundColor(.cyan)
                                        .shadow(color: .black, radius: 1, x: 0.5, y: 0.5)
                                case "IFR":
                                    Image(systemName: entries.metarImage)
                                        .renderingMode(.original)
                                        .font(.system(size: 25))
                                        .foregroundColor(.red)
                                        .shadow(color: .black, radius: 1, x: 0.5, y: 0.5)
                                case "LIFR":
                                    Image(systemName: entries.metarImage)
                                        .renderingMode(.original)
                                        .font(.system(size: 25))
                                        .foregroundColor(.purple)
                                        .shadow(color: .black, radius: 1, x: 0.5, y: 0.5)
                                default:
                                    Image(systemName: entries.metarImage)
                                        .renderingMode(.original)
                                        .font(.system(size: 25))
                                        .foregroundColor(.green)
                                        .shadow(color: .black, radius: 1, x: 0.5, y: 0.5)
                                }
                                
                                Text(entries.mairport)
                                switch entries.mfltcat {
                                case "VFR":
                                    Text(entries.mfltcat).bold()
                                        .foregroundColor(Color.green)
                                case "MVFR":
                                    Text(entries.mfltcat).bold()
                                        .foregroundColor(Color.cyan)
                                case "IFR":
                                    Text(entries.mfltcat).bold()
                                        .foregroundColor(Color.red)
                                case "LIFR":
                                    Text(entries.mfltcat).bold()
                                        .foregroundColor(Color.purple)
                                default:
                                    Text(entries.mfltcat)
                                        .foregroundColor(Color.white).bold()
                                }
                                Text(entries.mobstime + "Z")
                                Text(entries.mvis)
                                Text(entries.mwind)
                            } // End of HSTACK
                        }
                        .foregroundColor(.white)
                        .font(.subheadline)
                        Divider()
                            .frame(height: 2)
                            .overlay(.white)
                    }
                }
                .padding(20)
            }.onAppear(perform: WidgetCenter.shared.reloadAllTimelines) // HSTACK
            Spacer()
        } // ZSTACK
        //.widgetURL(URL(string: "metarnav"))
    } // End of someview
    
    struct LargeMetarView_Previews: PreviewProvider {
        static var previews: some View {
            LargeMetarView(entry: MetarEntry(date: .now, metars: tempData))
                .previewContext(WidgetPreviewContext(family: .systemLarge))
        }
    }
}

WidgetStationView 代码

import SwiftUI
import WidgetKit

struct WidgetStationView: View {
    var mdm: MetarDataModel
    var selectedStation: MetarDataModel?
    var metarservice = MetarService()
    
    var body: some View {
        HStack {
            Text("Metar Data")
        }
        
        GroupBox {
            VStack(alignment: .leading) {
                Text("\(mdm.mairport)")
                    .font(.headline)
            }
        } label: {
            Label("Metar #\(mdm.mairport)", systemImage: "person")
        }
        .padding()
    } // end of view
}

func getStation(with apt: String) async -> [MetarDataModel] {
    let metarservice = MetarService()
    var metars = [MetarDataModel]()
    do {
        metars = try await metarservice.fetchMetars(metarAPTs: apt)
    } catch {
        
    }
    return metars
}
解决方案

1. 确认URL Scheme配置

在项目的Info.plist中添加URL Scheme配置:

  • 添加CFBundleURLTypes数组,其中包含一个字典
  • 字典内添加CFBundleURLSchemes数组,值为frat
  • 可选:添加CFBundleURLName,设置为唯一标识符(比如com.yourcompany.frat)

2. 主应用处理URL跳转

SwiftUI应用需要在根层级处理URL打开事件,将跳转逻辑放在@main标记的App结构体中,配合导航栈实现页面跳转:

@main
struct FRATApp: App {
    @State private var navigationPath = NavigationPath()
    
    var body: some Scene {
        WindowGroup {
            NavigationStack(path: $navigationPath) {
                // 替换为你的主应用首页视图
                HomeView()
                    .navigationDestination(for: String.self) { airportCode in
                        // 根据机场码初始化WidgetStationView
                        WidgetStationView(mdm: MetarDataModel(mairport: airportCode))
                    }
            }
            .onOpenURL { url in
                // 解析URL,提取机场码
                guard url.scheme == "frat",
                      url.host == "metar",
                      let airportCode = url.pathComponents.last else {
                    return
                }
                // 将机场码加入导航栈,触发跳转
                navigationPath.append(airportCode)
            }
        }
    }
}

3. 修正URL解析逻辑

你的Widget中生成的URL格式为frat://metar/KJFK,其中path是/KJFK,因此不能直接用url.path == mdm.mairport判断,需要通过pathComponents.last获取机场码(即KJFK)。

4. 移除WidgetStationView中的onOpenURL

原代码中WidgetStationView的onOpenURL不会生效,因为从Widget打开应用时该视图尚未加载,跳转逻辑必须放在主应用的根层级处理。

内容的提问来源于stack exchange,提问作者ZDigitalPro

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.31 06:24:36