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

WKUserContentController与WKScriptMessageHandler配置错误,HTML无法向Swift发消息

问题描述
  • 使用WKUserContentController和WKScriptMessageHandler尝试从嵌入的HTML向Swift发送消息,修改布尔值isPlaying
  • HTML中的onReady代码块已执行,但消息始终无法发送,判断条件window.webkit && window.webkit.messageHandlers && window.webkit.messageHandlers.toggleMessageHandler返回false,说明WebView未正确配置
  • 连接Mac查看开发者菜单,设备下看不到已打开的WebView,进一步确认WebView配置存在问题
  • 需求是让Swift端知晓视频已准备好播放,为之前问题的延伸
问题代码
struct SmartReelView: UIViewRepresentable {
    let link: String
    @Binding var isPlaying: Bool
    
    func makeCoordinator() -> Coordinator {
        Coordinator(self)
    }
    
    func makeUIView(context: Context) -> WKWebView {
        let webConfiguration = WKWebViewConfiguration()
        webConfiguration.allowsInlineMediaPlayback = true
        let webView = WKWebView(frame: .zero, configuration: webConfiguration)
        webView.navigationDelegate = context.coordinator

        let userContentController = WKUserContentController()

        userContentController.add(context.coordinator, name: "toggleMessageHandler")
        
        webView.configuration.userContentController = userContentController

        loadInitialContent(in: webView)
        
        return webView
    }

    func updateUIView(_ uiView: WKWebView, context: Context) {
        let jsString = "isPlaying = \((isPlaying) ? \"true\" : \"false\"); watchPlayingState();"
        uiView.evaluateJavaScript(jsString, completionHandler: nil)
    }
    
    class Coordinator: NSObject, WKScriptMessageHandler, WKNavigationDelegate {
        var parent: SmartReelView

        init(_ parent: SmartReelView) {
            self.parent = parent
        }

        func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
            print("message recieved")
            self.parent.isPlaying = true
        }
    }
    
    private func loadInitialContent(in webView: WKWebView) {
        let embedHTML = """
        <style>
            .iframe-container iframe {
                top: 0;
                left: 0;
                width: 100%;
                height: 100%;
            }
        </style>
        <div class="iframe-container">
            <div id="player"></div>
        </div>
        <script>
            var tag = document.createElement('script');
            tag.src = "https://www.youtube.com/iframe_api";
            var firstScriptTag = document.getElementsByTagName('script')[0];
            firstScriptTag.parentNode.insertBefore(tag, firstScriptTag);

            var player;
            function onYouTubeIframeAPIReady() {
                player = new YT.Player('player', {
                    width: '100%',
                    videoId: '\(link)',
                    playerVars: { 'playsinline': 1, 'controls': 0},
                    events: {
                        'onReady': function(event) {
                            if (window.webkit && window.webkit.messageHandlers && window.webkit.messageHandlers.toggleMessageHandler) {
                                window.webkit.messageHandlers.toggleMessageHandler.postMessage({"message": "click now" });
                            }
                        }
                    }
                });
            }
        </script>
        """
        
        webView.scrollView.isScrollEnabled = false
        webView.loadHTMLString(embedHTML, baseURL: nil)
    }
}
解决方案

核心问题是WKWebView初始化后,其configuration属性是只读的,无法直接修改。原代码中先创建WebView,再给webView.configuration.userContentController赋值的操作无效,导致消息处理器未被正确添加。

修正步骤:

  1. 先创建并配置WKUserContentController,添加消息处理器
  2. 将配置好的userContentController赋值给WKWebViewConfiguration
  3. 用这个配置好的webConfiguration初始化WKWebView

修正后的makeUIView函数代码:

func makeUIView(context: Context) -> WKWebView {
    // 先创建并配置userContentController
    let userContentController = WKUserContentController()
    userContentController.add(context.coordinator, name: "toggleMessageHandler")
    
    // 配置webConfiguration,加入userContentController
    let webConfiguration = WKWebViewConfiguration()
    webConfiguration.allowsInlineMediaPlayback = true
    webConfiguration.userContentController = userContentController
    
    // 用配置好的webConfiguration初始化WebView
    let webView = WKWebView(frame: .zero, configuration: webConfiguration)
    webView.navigationDelegate = context.coordinator

    loadInitialContent(in: webView)
    
    return webView
}

额外优化建议:

  • 可以在HTML的onReady里增加调试日志,比如console.log(window.webkit),方便排查是否能访问到webkit对象
  • 确保WebView在SwiftUI中被正确渲染(比如没有被设置为frame .zero或者被其他视图遮挡)

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.12 04:17:23