如何在WKWebView中捕获通知?

10

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

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

你有什么想法如何实现这个功能吗?


你找到解决方案了吗? - user8943606
@MICKAELBELHASSEN 我恐怕不行。 - BAndonovski
1个回答

18
我面临同样的挑战,并通过注入脚本(WKUserScript)来解决它,该脚本会覆盖网络通知 API 并使用自定义实现利用WKUserContentController发送消息给本地应用程序代码,最终在结束时发布最终通知。

设置 WKWebView

据我所知,必须以编程方式创建 WKWebView 才能使用自定义的 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)
}

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

覆盖通知API

这是注入的用户脚本和Web通知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;

在 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)
            }
        }
    }

整个实现是关于在 macOS 上创建本地的本地通知。但是,如果没有额外的努力,它还无法正常工作。

应用程序委托调整

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

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

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

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

需要在 applicationDidFinishLaunching(_:) 中进行委托分配:

UNUserNotificationCenter.current().delegate = self

UNNotificationPresentationOptions 可根据您的需求而异。

参考资料

我在GitHub上创建了一个示例项目,以呈现整个场景。


你的解决方案虽然可行,但不适用于像WhatsApp或Facebook Messenger这样的Web视图,也不适用于大多数发送通知的网页。因为在发送通知之前,网页会首先检查window.Notification.permission是否为“denied”或“enabled”。因此,你的解决方案只在理论上是好的,但在实际的网页中是无法使用的。 - Mickael Belhassen
2
我扩展了用户脚本以涵盖通用授权过程。最初,我认为这超出了范围,因为问题更多地涉及通知的概念桥接。 - Peter
@MickaelBelhassen,应该使用“granted”而不是“enabled”(请参见规范)。 - Peter
非常好的工作,我点赞了。这是第一次有人发布了这个问题的解决方案。 - Mickael Belhassen
这个方法在iOS上有没有问题?此外,这种方法是否符合应用商店审核指南(苹果会通过吗)? - Mikis
1
已经过了一些年头,但我记得最初是在公司的iOS应用中实现的。它应该同样适用。关于批准方面:实际上我不知道。我也无法想象为什么原始触发器在Web视图内部会引起问题。这相当于在设备上本地分派通知的应用程序。标准任务 - Peter

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接