如何在 WKWebView 中捕获通知?

BAn*_*ski 7 macos webkit web-notifications swift

我正在 Swift 4 中开发 macOS 桌面应用程序。
它有一个 WKWebView,可以加载一个发送通知的网页。
默认情况下不显示任何通知,也没有权限请求。

我需要一种方法来显示通知并拦截它们,以便我可以显示一个计数器。

知道如何实现这一目标吗?

Pet*_*ter 11

我面临着同样的挑战,并通过注入一个脚本 ( WKUserScript) 来解决它,该脚本使用自定义实现覆盖 Web 通知 API,该实现利用WKUserContentController将消息发送到最终发布最终通知的本机应用程序代码。

配置 WKWebView

据我所知,WKWebView必须以编程方式创建 a才能使用自定义WKWebViewConfiguration。创建一个新的 macOS 应用程序项目,我像这样扩展我viewDidLoadViewController函数:

override func viewDidLoad() {
    super.viewDidLoad()

    let userScriptURL = Bundle.main.url(forResource: "UserScript", withExtension: "js")!
    let userScriptCode = try! String(contentsOf: userScriptURL)
    let userScript = WKUserScript(source: userScriptCode, injectionTime: .atDocumentStart, forMainFrameOnly: false)
    let configuration = WKWebViewConfiguration()
    configuration.userContentController.addUserScript(userScript)
    configuration.userContentController.add(self, name: "notify")

    let documentURL = Bundle.main.url(forResource: "Document", withExtension: "html")!
    let webView = WKWebView(frame: view.frame, configuration: configuration)
    webView.loadFileURL(documentURL, allowingReadAccessTo: documentURL)

    view.addSubview(webView)
}
Run Code Online (Sandbox Code Playgroud)

首先,我从应用程序包中加载用户脚本并将其添加到用户内容控制器。我还添加了一个名为的消息处理程序notify,可用于从 JavaScript 上下文回拨到本机代码。最后,我从应用程序包中加载了一个示例 HTML 文档,并使用窗口中的整个可用区域来呈现 Web 视图。

覆盖通知 API

这是注入的用户脚本和 Web Notification API的部分覆盖。在这个通用问题的范围内处理典型的通知许可请求过程和发布通知就足够了。

/**
 * Incomplete Notification API override to enable native notifications.
 */
class NotificationOverride {
    // Grant permission by default to keep this example simple.
    // Safari 13 does not support class fields yet, so a static getter must be used.
    static get permission() {
        return "granted";
    }

    // Safari 13 still uses callbacks instead of promises.
    static requestPermission (callback) {
        callback("granted");
    }

    // Forward the notification text to the native app through the script message handler.
    constructor (messageText) {
        window.webkit.messageHandlers.notify.postMessage(messageText);
    }
}

// Override the global browser notification object.
window.Notification = NotificationOverride;
Run Code Online (Sandbox Code Playgroud)

每次在 JavaScript 上下文中创建新通知时,都会调用用户内容控制器消息处理程序。

处理脚本消息

ViewController(或任何其他应该处理脚本消息)需要顺应WKScriptMessageHandler和实现以下函数来处理调用:

func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
        let content = UNMutableNotificationContent()
        content.title = "WKWebView Notification Example"
        content.body = message.body as! String

        let uuidString = UUID().uuidString
        let request = UNNotificationRequest(identifier: uuidString, content: content, trigger: nil)

        let notificationCenter = UNUserNotificationCenter.current()
        notificationCenter.add(request) { (error) in
            if error != nil {
                NSLog(error.debugDescription)
            }
        }
    }
Run Code Online (Sandbox Code Playgroud)

整个实现是关于在 macOS 中创建本地、本机通知。但是,如果没有额外的努力,它仍然无法工作。

应用代理调整

在允许 macOS 应用程序发布通知之前,它必须请求这样做的权限,就像网站一样。

func applicationDidFinishLaunching(_ aNotification: Notification) {
        UNUserNotificationCenter.current().requestAuthorization(options: [.alert]) { (granted, error) in
            // Enable or disable features based on authorization.
        }
    }
Run Code Online (Sandbox Code Playgroud)

如果应在应用程序处于前台时显示通知,则必须进一步扩展应用程序委托以符合UNUserNotificationCenterDelegate并实现:

func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {
        completionHandler(UNNotificationPresentationOptions.alert)
    }
Run Code Online (Sandbox Code Playgroud)

这需要委托分配applicationDidFinishLaunching(_:)

UNUserNotificationCenter.current().delegate = self
Run Code Online (Sandbox Code Playgroud)

UNNotificationPresentationOptions可根据您的要求各不相同。

参考

在 GitHub 上创建了一个示例项目,它呈现了整个画面。

  • 我扩展了用户脚本以涵盖通用授权过程。我最初认为这超出了范围,因为问题更多的是关于通知的概念桥接。 (2认同)