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

SwiftUI WebView首次加载不显示内容问题求助

问题描述

我想用WebView结合HTML与JavaScript渲染内容,以下是复现问题的完整代码。预期TestViewPreview先渲染文本“hello testview”,5秒后渲染“hello2 testview”,但只有第二个内容正常显示,首次加载出现如下错误:

evaluateJavaScript: render('hello testview'); TestView.updateUIView
error: Error Domain=WKErrorDomain Code=4 "A JavaScript exception occurred" UserInfo={WKJavaScriptExceptionLineNumber=0, WKJavaScriptExceptionMessage=TypeError: undefined is not a function, WKJavaScriptExceptionColumnNumber=0, NSLocalizedDescription=A JavaScript exception occurred}

提供的代码

Swift代码

struct MyState {
  var text: String = "abc"
}

struct TestView: UIViewRepresentable {
  let htmlPath: String
  @Binding var state: MyState
  
  func makeUIView(context: Context) -> WKWebView {
    if let htmlPath = Bundle.main.path(forResource: htmlPath, ofType: "html") {
      do {
        let htmlString = try String(contentsOfFile: htmlPath)
        WebViewHelper.webView.loadHTMLString(htmlString, baseURL: Bundle.main.bundleURL)
        WebViewHelper.webView.scrollView.showsHorizontalScrollIndicator = true
        print("TestView.makeUIView: Finished inital loading.")
      } catch {
        print("TestView.makeUIView: Error loading HTML file: \(error)")
      }
    }
    return WebViewHelper.webView
  }
  
  func updateUIView(_ webView: WKWebView, context: Context) {
    let script = "render('\(state.text)');"
    print("evaluateJavaScript: \(script)")
    webView.evaluateJavaScript(script) { _, error in
      if let error = error {
        print("TestView.updateUIView error: \(error)")
      }
    }
  }
}

struct TestViewPreview: View {
  @State private var state = MyState(text: "hello testview")
  
  var body: some View {
    TestView(htmlPath: "test_html", state: self.$state)
      .onAppear {
        DispatchQueue.main.asyncAfter(deadline: .now() + 5.0) {
          state.text = "hello2 testview"
        }
      }
  }
}

#Preview {
  TestViewPreview()
}

HTML代码(test_html.html)

<!DOCTYPE html>
<head>
  <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
</head>
<body>
  <div id="output"></div>
  <script>
    function render(text) {
      document.getElementById("output").innerHTML = text
    }
  </script>
</body>
</html>
问题排查与修复

问题根源

  1. HTML未加载完成就执行JS:updateUIView会在makeUIView返回后立即触发,但此时WebView的HTML还未加载完毕,render函数尚未定义,调用时自然会报“undefined is not a function”错误。
  2. 全局WebView单例风险:使用WebViewHelper.webView全局单例会导致视图生命周期混乱,多个TestView实例共用同一个WebView,易引发不可预期的行为。
  3. 字符串转义缺失:如果state.text包含单引号,直接拼接JS脚本会破坏语法结构,导致执行失败。

修复方案

1. 等待HTML加载完成后执行JS

通过WKNavigationDelegate监听页面加载状态,仅在页面加载完成后调用render函数:

修改后的TestView代码:

struct MyState {
  var text: String = "abc"
}

struct TestView: UIViewRepresentable {
    let htmlPath: String
    @Binding var state: MyState
    
    func makeCoordinator() -> Coordinator {
        Coordinator(self)
    }
    
    class Coordinator: NSObject, WKNavigationDelegate {
        var parent: TestView
        
        init(_ parent: TestView) {
            self.parent = parent
        }
        
        // 页面加载完成回调
        func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
            parent.updateWebViewContent(webView)
        }
    }
    
    func makeUIView(context: Context) -> WKWebView {
        // 为每个TestView创建独立的WebView实例,避免单例冲突
        let webView = WKWebView()
        webView.navigationDelegate = context.coordinator
        webView.scrollView.showsHorizontalScrollIndicator = true
        
        if let htmlPath = Bundle.main.path(forResource: htmlPath, ofType: "html") {
            do {
                let htmlString = try String(contentsOfFile: htmlPath)
                webView.loadHTMLString(htmlString, baseURL: Bundle.main.bundleURL)
                print("TestView.makeUIView: 完成初始加载.")
            } catch {
                print("TestView.makeUIView: 加载HTML文件出错: \(error)")
            }
        }
        return webView
    }
    
    func updateUIView(_ webView: WKWebView, context: Context) {
        // 页面加载中则暂存状态,加载完成后自动更新;否则立即更新
        if webView.isLoading {
            context.coordinator.parent.state = state
        } else {
            updateWebViewContent(webView)
        }
    }
    
    private func updateWebViewContent(_ webView: WKWebView) {
        // 转义单引号,避免JS语法错误
        let escapedText = state.text.replacingOccurrences(of: "'", with: "\\'")
        let script = "render('\(escapedText)');"
        print("evaluateJavaScript: \(script)")
        webView.evaluateJavaScript(script) { _, error in
            if let error = error {
                print("TestView.updateUIView 错误: \(error)")
            }
        }
    }
}

struct TestViewPreview: View {
  @State private var state = MyState(text: "hello testview")
  
  var body: some View {
    TestView(htmlPath: "test_html", state: self.$state)
      .onAppear {
        DispatchQueue.main.asyncAfter(deadline: .now() + 5.0) {
          state.text = "hello2 testview"
        }
      }
  }
}

#Preview {
  TestViewPreview()
}

2. 移除全局WebView单例

每个TestView实例创建独立的WKWebView,确保视图生命周期独立,避免资源冲突。

3. 处理字符串转义

通过replacingOccurrences(of: "'", with: "\\'")转义文本中的单引号,保证JS脚本语法正确。

4. 验证HTML文件

确保test_html.html已正确添加到项目中,且在Build Phases的Copy Bundle Resources列表内,保证Bundle能正确读取该文件。

修复后,首次加载时WebView会等待HTML加载完成,执行render显示“hello testview”;5秒后状态更新,再次执行render显示“hello2 testview”,不会再出现首次加载的错误。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.22 18:35:19