如何从嵌入式 HTML 与 Swift 进行通信以更改 bool

2023-12-25

您好,我想在执行 html 中的 onReady 块后更改绑定变量“click”的值。我可以使用评估java脚本从swift到html进行通信。但是我如何从 html 中的 onReady 与 swift 进行通信以更改 bools val?我尝试制作协调员课程,但是

struct SmartReelView: UIViewRepresentable {
    let link: String
    @Binding var isPlaying: Bool
    @Binding var click: Bool        //variable to be changed
    
    func makeUIView(context: Context) -> WKWebView {
        let webConfiguration = WKWebViewConfiguration()
        webConfiguration.allowsInlineMediaPlayback = true
        let webView = WKWebView(frame: .zero, configuration: webConfiguration)
        
        loadInitialContent(in: webView)
        
        return webView
    }

    func updateUIView(_ uiView: WKWebView, context: Context) {
        let jsString = "isPlaying = \((isPlaying) ? "true" : "false"); watchPlayingState();"
        uiView.evaluateJavaScript(jsString, completionHandler: nil)
    }
    
    private func loadInitialContent(in webView: WKWebView) {
        let embedHTML = """
        <style>
            body {
                margin: 0;
                background-color: black;
            }
            .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;
            var isPlaying = true;
            function onYouTubeIframeAPIReady() {
                player = new YT.Player('player', {
                    width: '100%',
                    videoId: '\(link)',
                    playerVars: { 'playsinline': 1, 'controls': 0},
                    events: {
                        'onReady': function(event) {
                            //change here
                        },
                        'onStateChange': function(event) {
                            if (event.data === YT.PlayerState.ENDED) {
                                player.seekTo(0);
                                player.playVideo();
                            }
                        }
                    }
                });
            }
            
            function watchPlayingState() {
                if (isPlaying) {
                    player.playVideo();
                } else {
                    player.pauseVideo();
                }
            }
        </script>
        """
        
        webView.scrollView.isScrollEnabled = false
        webView.loadHTMLString(embedHTML, baseURL: nil)
    }
}

Update

根据评论中的文章我改变了我的代码:

struct SmartReelView: UIViewRepresentable {
    let link: String
    @Binding var isPlaying: Bool
    @Binding var click: 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) {
            if message.name == "toggleMessageHandler", let messageBody = message.body as? [String: Any] {
                if let messageValue = messageBody["message"] as? String, messageValue == "click now" {
                    DispatchQueue.main.async {
                        self.parent.click = true
                    }
                }
            }
        }
    }
    
    private func loadInitialContent(in webView: WKWebView) {
        let embedHTML = """
        <style>
            body {
                margin: 0;
                background-color: black;
            }
            .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;
            var isPlaying = true;
            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" });
                            }
                        },
                        'onStateChange': function(event) {
                            if (event.data === YT.PlayerState.ENDED) {
                                player.seekTo(0);
                                player.playVideo();
                            }
                        }
                    }
                });
            }
            
            function watchPlayingState() {
                if (isPlaying) {
                    player.playVideo();
                } else {
                    player.pauseVideo();
                }
            }
        </script>
        """
        
        webView.scrollView.isScrollEnabled = false
        webView.loadHTMLString(embedHTML, baseURL: nil)
    }
}

要从嵌入的 HTML 与 Swift 进行通信以更改布尔值,您需要使用WKUserContentController https://developer.apple.com/documentation/webkit/wkusercontentcontroller及其委托方法来监听自定义 JavaScript 事件。您可以使用window.webkit.messageHandlers[handlerName].postMessage(message) from WKScriptMessageHandler https://developer.apple.com/documentation/webkit/wkscriptmessagehandler在 JavaScript 中将消息从 JavaScript 发送到 Swift (看到这个问题 https://stackoverflow.com/q/62035494/6309用于说明)。

The SmartReelView需要创建自己的Coordinator https://developer.apple.com/documentation/swiftui/uiviewcontrollerrepresentable/coordinator并符合WKScriptMessageHandler处理 JavaScript 消息:

struct SmartReelView: UIViewRepresentable {
    let link: String
    @Binding var isPlaying: Bool
    @Binding var click: 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
        
        // Create user content controller
        let userContentController = WKUserContentController()
        
        // Add the message handler script
        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) {
            if message.name == "toggleMessageHandler", let messageBody = message.body as? [String: Any] {
                if let messageValue = messageBody["message"] as? String, messageValue == "click now" {
                    DispatchQueue.main.async {
                        self.parent.click = true
                    }
                }
            }
        }
    }
    
    // existing loadInitialContent function
}

A Coordinator引入的类符合WKScriptMessageHandler。这将处理来自 JavaScript 的消息。
Coordinator 作为脚本消息处理程序添加到 WebView 中,名称为“toggleMessageHandler”。
在你的 HTML 中onReadyJavaScript 函数,一条消息被发布到该脚本消息处理程序。斯威夫特的userContentController(_:didReceive:)然后接收该消息,处理它,并更新@Binding var click.

这应该允许 Swift 代码对 HTML 中的事件做出反应,改变click变量时onReadyJavaScript 中触发事件。


似乎从未收到过该消息。在“的开头添加打印userContentController“永远不要跑。

这可能来自操作的顺序,也可能来自于所提供的代码中不明显的特定环境约束。鉴于打印内部userContentController方法没有显示,这表明该方法从未被调用。这可能是由于 JavaScript 未成功发送消息,或者是因为 Swift 中的消息处理程序未正确设置。

确保WKUserContentController及其消息处理程序已设置before任何网页内容均已加载。您在您的系统中正确地执行了此操作makeUIView功能,但值得强调。

并仔细检查您的 JavaScript 是否确实执行了将消息发布到的部分window.webkit.messageHandlers.toggleMessageHandler。您可以将控制台日志添加到 JavaScript 中,看看它是否达到了这一点。

您已经设置了navigationDelegate给您的协调员,但请确保您还设置了WKScriptMessageHandler通过使用userContentController.add(context.coordinator, name: "toggleMessageHandler").


The onReady块确实会执行,但是“.postMessage({"message": "click now" });“ 从未达到,这意味着if其之前的语句被评估为 false。删除if语句仍然不会触发该消息。这意味着window.webkit is nil.

我还尝试在真实设备上运行它,但这没有帮助。唯一的另一件事是每次播放和暂停视频时我都会收到一条警告:“originator doesn't have entitlement com.apple.runningboard.assertions.webkit AND originator doesn't have entitlement com.apple.multitasking.systemappassertions"

确保WKUserContentController并且消息处理程序已在加载网页内容之前设置。如果没有这个可能会导致无效window.webkit目的。并检查WebView是否完全初始化onReady函数被调用。这WKScriptMessageHandler那时可能尚未完全设置,因此window.webkit可能不可用。

您可以尝试调用window.webkit.messageHandlers.toggleMessageHandler.postMessage({"message": "click now"});直接从 Safari 的 Web Inspector 控制台手动检查网页完全加载后 JavaScript 和 Swift 之间的桥梁是否正常工作。这至少可以排除本机代码的任何问题,并将范围缩小到 JavaScript 方面。

有关权利的警告消息,例如com.apple.runningboard.assertions.webkit and com.apple.multitasking.systemappassertions通常表明应用程序正在尝试执行需要特殊权限或权利的操作。尽管这些警告不一定会影响WKScriptMessageHandler,确保您拥有所有必要的权限Info.plist https://developer.apple.com/documentation/bundleresources/information_property_list。也可以看看哪里Info.plist在 Xcode 13 中 https://sarunw.com/posts/where-is-info-plist/.


I think WKUserContentController只是设置不正确。当我插入 iPhone 并运行该应用程序时,转到 Mac 上的 YouTube 视频并按“开发”,然后将鼠标悬停在 iPhone 上,但我没有看到任何加载的网页。退一步来说,这是我想要更改 bool 值的唯一原因,因为如果我这样做,我可以监听 var 的更改,然后在onChange块我可以设置isPlaying变量设置为 true 将返回并取消暂停视频。这只是为了播放视频(UNMUTED)准备好后。由于某种原因打电话watchPlayingState from onReady不起作用。

我认为 YouTube AGO 会通过 js 并确保暂停操作是由用户的物理操作激发的?否则它怎么知道不播放视频。呼唤watchPlayingState in the onReady不起作用(但在自动播放视频之前将视频静音)。但如果我放一个简单的onAppear在我的主视图中并切换isPlaying然后视频会自动播放声音(也许 API 被欺骗认为是用户造成的)。一切onAppear确实是让watchPlayingState run.

您遇到的行为很可能是由于 YouTube 的 API 和浏览器安全准则对自动播放有声视频施加的限制造成的。由于浏览器政策限制自动播放,视频通常不会自动播放有声视频,除非视频被静音或由用户操作触发。即增强用户体验并减少意外的有声视频播放。 YouTube API 可能有类似的限制。即使您尝试在中使用 JavaScript 播放视频onReady事件时,API 可能会检查此请求是否是由用户操作引起的。

鉴于您在检查设备时没有看到任何加载的网页,这确实会引发以下问题:WKUserContentController并且 JavaScript 桥设置正确。如果没有在正确的时间添加消息处理程序,JavaScript 将无法调用该消息处理程序。

Your onAppear这个技巧可能会起作用,因为 SwiftUI 在视图出现后发送此消息,从而可能欺骗 YouTube API 将其视为用户操作。

如果目标是自动播放有声视频,考虑到网络技术和 YouTube API 的限制,如果不进行一些用户操作,您可能无法实现此目标。然而,使用 Swift 的方法@Binding变量和onChange检测视频何时准备好播放是合理的。您本质上是在尝试在 Swift 代码和 JavaScript 代码之间创建握手来协调自动播放。

确保WKUserContentController and WKScriptMessageHandler已正确设置;否则,window.webkit对象在 JavaScript 上下文中不可用,并且你的 Swift@Binding将不会收到更新。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

如何从嵌入式 HTML 与 Swift 进行通信以更改 bool 的相关文章

随机推荐

  • OCaml中的fold_tree

    你可能知道 OCaml中有一些高阶函数 例如fold left fold right filter等 在我的函数式编程课程中 引入了名为fold tree的函数 它类似于fold left right 不是在列表上 而是在 二元 树上 它看
  • .NET 4.5 异步等待和重载方法

    我有一个异步方法 public async Task
  • 不同的闭包在快速保留周期中给出不同的结果

    我正在阅读 Apple 的 Swift 编程语言指南 在关于闭包的强引用循环的部分中 我尝试了一种不同类型的闭包 但它没有给出预期的输出 class HTMLElement let name String let text String l
  • scipy.io 的导入问题

    我一直在尝试开始使用 scipy 但该软件包给我带来了一些问题 本教程很大程度上依赖于 scipy io 但是当我导入 scypi 并尝试使用 scipy io 时 出现错误 In 1 import scipy In 2 help scip
  • 线程安全哈希映射?

    我正在编写一个应用程序 它将返回一个 HashMap 给用户 用户将获得此地图的参考 在后端 我将运行一些线程来更新地图 到目前为止我做了什么 我已经创建了所有后端线程 因此共享一个公共通道来更新 MAP 因此 在后端 我确信并发写入操作不
  • 在自动 Teams 消息中标记团队成员

    我有一个用于待命轮换的 Excel 电子表格 在 的帮助下这个答案 https stackoverflow com a 73039284 9124454 我能够使用 Power Automate 触发一条自动 Microsoft Teams
  • 使用 os.walk 时,有没有办法确定子目录是否位于 python 的同一文件系统中?

    我正在编写一个 python 脚本 它使用 os walk 来遍历目录树 我想赋予它跳过安装到不同文件系统的子目录的能力 这样find xdev做 检查 os walk 的文档 我没有看到任何参数可以让它自动执行此操作 我可以用什么东西来自
  • 片段测试错误:android.view.InflateException:二进制 XML 文件行 #16:二进制 XML 文件行 #16:错误膨胀类 <未知>

    我正在尝试按照以下说明测试片段 https developer android com training basics fragments testing https developer android com training basic
  • 仅将混合模式应用于投影

    可以混合吗only元素的投影与它重叠的元素的颜色 例如 我有一个元素与另一个元素重叠 顶部的元素有一个浅灰色的阴影 下面的元素是黑色的 我不希望对任何一个元素本身应用任何混合 但希望重叠元素 的投影与下面元素的颜色混合 在阴影落在重叠元素上
  • mysql 使用子查询更新查询

    谁能看出下面的查询有什么问题吗 当我运行它时 我得到 1064 你的 SQL 语法有错误 检查与您的 MySQL 服务器版本相对应的手册 了解要使用的正确语法 第 8 行的 a where a CompetitionID Competiti
  • 通用电子邮件验证器

    我想创建一个表单 用户将在其中输入他的电子邮件 我想验证客户端的电子邮件格式 Angular 2 中有通用的电子邮件验证器吗 注意 类似于AngularJS 验证器 https docs angularjs org api ng input
  • marklogic mlcp 自定义转换拆分聚合文档为多个文件

    我有一个 JSON 聚合 文件 我想使用 mlcp 将其拆分并作为多个文档摄取到 MarkLogic 中 我想要使用 javascript 在摄取过程中转换内容 http docs marklogic com guide mlcp impo
  • 同时声明多个变量的更优雅的方式

    要 同时 声明多个变量 我会这样做 a b True False 但如果我必须声明更多的变量 它就会变得越来越不优雅 a b c d e f g h i j True True True True True False True True
  • 如何以编程方式更改 Window 注册表中的值?

    我需要以编程方式将 HKEY CURRENT USER Software Intuit QBWebConnector 中找到的 Level 字符串更改为 Verbose 做这个的最好方式是什么 C bat 文件 我以前从来没有修改过注册表
  • GCM 的注册 ID 重复

    我们有一个使用 GCM 的应用程序 当用户首次打开应用程序时 应用程序会检查共享首选项以查看用户之前是否注册过 如果没有 则从 GCM 获取注册 ID 并将其存储到共享首选项中 还有一个存储用户 ID 和注册 ID 的第 3 方服务器 我阅
  • Javascript URL 深度(级别)

    是否可以使用 Javascript 获取 url 深度 级别 如果我有这个网址 www website com site product category item gt 深度 4 www website com site product
  • Spring Security 中的 beans.NotReadablePropertyException

    我对 Spring Security 非常陌生 我捡起来了this https rads stackoverflow com amzn click com 1847199747书并尝试执行代码 当我这样做时 我得到了 org springf
  • 如何读取 Objective-C 堆栈跟踪

    我有以下堆栈跟踪 0 MyApp 0x000833a3 TFCrashHandler backtrace 26 1 MyApp 0x000836bd TFSignalHandler 28 2 libsystem c dylib 0x33ea
  • 删除重复行并更新引用

    如何删除一个表中的重复行并将另一表中的引用更新为剩余行 重复仅出现在名称中 Id 列是标识列 Example 假设我们有两张表Doubles and Data Doubles table Id int Name varchar 50 Dat
  • 如何从嵌入式 HTML 与 Swift 进行通信以更改 bool

    您好 我想在执行 html 中的 onReady 块后更改绑定变量 click 的值 我可以使用评估java脚本从swift到html进行通信 但是我如何从 html 中的 onReady 与 swift 进行通信以更改 bools val