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

Shield配置通知点击后跳转至指定SwiftUI视图问题求助

问题:点击Shield通知无法跳转到指定SwiftUI视图

我已经完成了大部分功能开发:当预选中应用的Shield配置中主按钮被点击时,用户会收到通知。但目前点击通知仅会跳转到MainView(),我希望能跳转到指定的SwiftUIView()。我尝试修改应用的info.plist,但没有效果。

以下是当前代码:

ContentView.swift

import SwiftUI
import FamilyControls
import ManagedSettings
import Combine

struct ContentView: View {
    @State private var isAuthorized = UserDefaults.standard.bool(forKey: "HasRequestedScreenTimeAuthorization")
    @State private var navigateToSwiftUIView = false
    @State private var selectedApp: ApplicationToken?

    private let selectedAppKey = "SelectedAppToken"

    var body: some View {
        NavigationView {
            VStack {
                if isAuthorized {
                    if navigateToSwiftUIView, let appToPause = selectedApp {
                        SwiftUIView(appToPause: appToPause)
                    } else {
                        MainView()
                    }
                } else {
                    AuthorizationRequestView(isAuthorized: $isAuthorized)
                }
            }
            .onAppear {
                NotificationCenter.default.addObserver(forName: .navigateToSwiftUIView, object: nil, queue: .main) { _ in
                    if let data = UserDefaults.standard.data(forKey: selectedAppKey),
                       let appToken = try? JSONDecoder().decode(ApplicationToken.self, from: data) {
                        selectedApp = appToken
                        navigateToSwiftUIView = true
                    }
                }
            }
            .onOpenURL { url in
                if url.absoluteString == "test://navigateToSwiftUIView" {
                    if let data = UserDefaults.standard.data(forKey: selectedAppKey),
                       let appToken = try? JSONDecoder().decode(ApplicationToken.self, from: data) {
                        selectedApp = appToken
                        navigateToSwiftUIView = true
                    }
                }
            }
        }
    }
}

App入口与AppDelegate

import SwiftUI
import UserNotifications

@main
struct YourApp: App {
    @UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate

    var body: some Scene {
        WindowGroup {
            ContentView()
        }
    }
}

class AppDelegate: NSObject, UIApplicationDelegate, UNUserNotificationCenterDelegate {
    
    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        UNUserNotificationCenter.current().delegate = self
        return true
    }
    
    func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) {
        // Trigger URL to navigate to SwiftUIView when notification is tapped
        if response.notification.request.content.userInfo["navigateTo"] as? String == "SwiftUIView" {
            UIApplication.shared.open(URL(string: "test://navigateToSwiftUIView")!)
        }
        completionHandler()
    }
    
    func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool {
        // Post notification for navigation in ContentView
        if url.absoluteString == "test://navigateToSwiftUIView" {
            NotificationCenter.default.post(name: .navigateToSwiftUIView, object: nil)
            return true
        }
        return false
    }
}

extension Notification.Name {
    static let navigateToSwiftUIView = Notification.Name("navigateToSwiftUIView")
}

ShieldActionExtension.swift

import ManagedSettings
import UserNotifications

class ShieldActionExtension: ShieldActionDelegate {
    private let selectedAppKey = "SelectedAppToken"

    override func handle(action: ShieldAction, for application: ApplicationToken, completionHandler: @escaping (ShieldActionResponse) -> Void) {
        switch action {
        case .primaryButtonPressed:
            saveApplicationToken(application)
            sendShieldNotification()
            completionHandler(.defer)
        case .secondaryButtonPressed:
            completionHandler(.defer)
        @unknown default:
            fatalError("Unknown action")
        }
    }

    private func saveApplicationToken(_ application: ApplicationToken) {
        // Encode the application token and save to UserDefaults
        if let data = try? JSONEncoder().encode(application) {
            UserDefaults.standard.set(data, forKey: selectedAppKey)
        }
    }

    private func sendShieldNotification() {
        let content = UNMutableNotificationContent()
        content.title = "Shield Alert"
        content.body = "Access attempt detected."
        content.userInfo = ["navigateTo": "SwiftUIView"]
        
        let request = UNNotificationRequest(identifier: UUID().uuidString, content: content, trigger: nil)
        UNUserNotificationCenter.current().add(request) { error in
            if let error = error {
                print("Failed to add notification: \(error.localizedDescription)")
            } else {
                print("Notification scheduled successfully.")
            }
        }
    }
}

解决方案

1. 确认URL Scheme配置无误

先检查info.plist的URL Scheme是否正确添加:

  • 添加URL types数组,新增一项
  • 设置URL Schemes为test(对应代码中的test://协议)
  • 填写URL Identifier为唯一标识(例如com.yourcompany.yourapp)

2. 简化导航逻辑(去掉冗余的URL跳转)

当前流程绕了URL跳转+通知的弯路,直接在通知响应回调中处理导航更可靠:

修改AppDelegate的通知响应方法:

func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) {
    if response.notification.request.content.userInfo["navigateTo"] as? String == "SwiftUIView" {
        let selectedAppKey = "SelectedAppToken"
        // 直接读取保存的AppToken并通过通知传递
        if let data = UserDefaults.standard.data(forKey: selectedAppKey),
           let appToken = try? JSONDecoder().decode(ApplicationToken.self, from: data) {
            NotificationCenter.default.post(name: .navigateToSwiftUIView, object: appToken)
        }
    }
    completionHandler()
}

更新ContentView的通知监听逻辑:

.onAppear {
    NotificationCenter.default.addObserver(forName: .navigateToSwiftUIView, object: nil, queue: .main) { notification in
        // 直接从通知对象获取AppToken
        if let appToken = notification.object as? ApplicationToken {
            selectedApp = appToken
            navigateToSwiftUIView = true
        }
    }
}

之后可以移除AppDelegate中的application(_:open:options:)方法,以及ContentView中的onOpenURL回调,减少不必要的环节。

3. 确保ApplicationToken支持Codable

系统的ApplicationToken默认可能不遵循Codable,需要手动扩展:

extension ApplicationToken: Codable {
    enum CodingKeys: String, CodingKey {
        case bundleID = "bundleIdentifier"
        // 根据需求添加其他需要编码的属性
    }
    
    public func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)
        try container.encode(bundleIdentifier, forKey: .bundleID)
        // 编码其他属性
    }
    
    public init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        let bundleID = try container.decode(String.self, forKey: .bundleID)
        self.init(bundleIdentifier: bundleID)
        // 初始化其他属性
    }
}

4. 测试验证

  • 测试应用在后台/关闭状态下的通知点击
  • 打印UserDefaults中保存的ApplicationToken数据,确认编码解码正常
  • 在通知响应回调中添加日志,确认代码执行路径

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.16 17:15:55