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

如何在WKWebView中捕获通知?macOS Swift4桌面应用实现

解决macOS Swift 4应用中WKWebView网页通知的显示与拦截计数问题

我来帮你搞定这个问题!在Swift 4的macOS应用里,要让WKWebView加载的网页能显示通知,还能拦截计数,得从权限申请、JS注入拦截和原生处理这几个方面入手,下面是具体的实现步骤:

1. 先申请应用的通知权限

macOS里不管是原生还是网页触发的通知,都需要先获得用户授权。你可以在应用启动时(比如AppDelegate的applicationDidFinishLaunching方法里)请求权限:

import Cocoa
import UserNotifications

func applicationDidFinishLaunching(_ aNotification: Notification) {
    // 针对macOS 10.14+使用UNUserNotificationCenter,更早版本用NSUserNotificationCenter
    if #available(macOS 10.14, *) {
        UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .sound]) { granted, error in
            if granted {
                print("通知权限已授权")
            } else {
                print("通知权限被拒绝")
            }
        }
    } else {
        let notificationCenter = NSUserNotificationCenter.default
        notificationCenter.requestAuthorization(options: [.alert, .sound]) { granted, error in
            // 根据需求处理授权结果
        }
    }
}

2. 注入JavaScript拦截网页的Notification API

网页默认的new Notification()调用不会触发原生权限请求,也无法被直接拦截。我们需要注入JS脚本重写Notification构造函数,把通知的标题、内容等信息发送给原生代码,同时模拟网页的权限状态,避免网页因权限问题不发送通知:

在ViewController中配置WKWebView并实现拦截逻辑:

class ViewController: NSViewController, WKUIDelegate, WKScriptMessageHandler {
    var webView: WKWebView!
    var notificationCount = 0 // 通知计数器
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        let contentController = WKUserContentController()
        // 添加脚本消息处理器,名字要和JS里的postMessage对应
        contentController.add(self, name: "notificationHandler")
        
        // 注入重写Notification的JS脚本
        let scriptSource = """
        // 保存原始的Notification构造函数
        const OriginalNotification = window.Notification;
        // 重写构造函数,拦截通知请求
        window.Notification = function(title, options) {
            // 把通知数据发送给原生代码
            window.webkit.messageHandlers.notificationHandler.postMessage({
                title: title,
                body: options.body || '',
                icon: options.icon || ''
            });
            // 如果需要让网页自身也能触发通知事件,可以取消下面的注释
            // return new OriginalNotification(title, options);
        };
        // 模拟网页已获得通知权限,避免网页反复请求权限
        Object.defineProperty(window.Notification, 'permission', {
            get: () => 'granted'
        });
        // 重写requestPermission方法,直接返回授权状态
        window.Notification.requestPermission = function(callback) {
            callback('granted');
            return Promise.resolve('granted');
        };
        """
        
        let userScript = WKUserScript(source: scriptSource, injectionTime: .atDocumentStart, forMainFrameOnly: true)
        contentController.addUserScript(userScript)
        
        // 配置WKWebView并添加到视图
        let config = WKWebViewConfiguration()
        config.userContentController = contentController
        
        webView = WKWebView(frame: view.bounds, configuration: config)
        webView.uiDelegate = self
        view.addSubview(webView)
        
        // 替换成你的目标网页URL
        if let url = URL(string: "https://your-target-webpage.com") {
            webView.load(URLRequest(url: url))
        }
    }
    
    // 实现WKScriptMessageHandler方法,接收网页发来的通知数据
    func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
        guard message.name == "notificationHandler",
              let notificationData = message.body as? [String: String] else {
            return
        }
        
        // 更新通知计数器
        notificationCount += 1
        print("当前通知总数:\(notificationCount)")
        // 这里可以绑定UI控件(比如NSTextField)实时显示计数器
        
        // 调用原生方法显示系统通知
        showNativeNotification(title: notificationData["title"] ?? "", body: notificationData["body"] ?? "")
    }
    
    // 封装显示原生通知的方法,兼容不同macOS版本
    func showNativeNotification(title: String, body: String) {
        if #available(macOS 10.14, *) {
            let content = UNMutableNotificationContent()
            content.title = title
            content.body = body
            content.sound = UNNotificationSound.default
            
            let request = UNNotificationRequest(identifier: UUID().uuidString, content: content, trigger: nil)
            UNUserNotificationCenter.current().add(request) { error in
                if let error = error {
                    print("显示通知失败:\(error.localizedDescription)")
                }
            }
        } else {
            let notification = NSUserNotification()
            notification.title = title
            notification.informativeText = body
            notification.soundName = NSUserNotificationDefaultSoundName
            NSUserNotificationCenter.default.deliver(notification)
        }
    }
    
    // 记得在销毁时移除脚本处理器,避免内存泄漏
    deinit {
        webView.configuration.userContentController.removeScriptMessageHandler(forName: "notificationHandler")
    }
}

3. 关键细节说明

  • JS注入时机:使用.atDocumentStart确保脚本在网页加载前生效,保证所有网页的Notification调用都会被拦截。
  • 权限模拟:重写permission属性和requestPermission方法是为了让网页认为已经获得授权,避免网页因权限判断逻辑不发送通知。
  • 计数器扩展:你可以把notificationCount绑定到应用的状态栏、侧边栏等UI位置,实现更直观的计数展示。

这样操作后,网页发送的通知会被完整拦截,原生代码会显示系统通知,同时你的计数器也能正常累加啦!

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.19 04:20:32