iOS 开发如何与H5通信?

917 阅读3分钟

在 iOS 开发中,实现原生与 WKWebView 中的 JavaScript 交互及内容区域适配,需通过以下步骤完成:


一、原生与 JavaScript 的交互

1. 原生调用 JavaScript

使用 evaluateJavaScript 方法执行 JS 代码并获取返回值。

示例代码

// 执行 JS 函数并获取返回值
webView.evaluateJavaScript("getUserName()") { (result, error) in
    if let result = result {
        print("JS返回的用户名:\(result)")
    }
}

2. JavaScript 调用原生

通过注册消息处理器(Message Handler)实现。

步骤

  1. 添加消息处理器
let config = WKWebViewConfiguration()
config.userContentController.add(self, name: "nativeHandler")
webView = WKWebView(frame: .zero, configuration: config)
  1. 实现 WKScriptMessageHandler 协议
extension ViewController: WKScriptMessageHandler {
    func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
        if message.name == "nativeHandler", let body = message.body as? [String: Any] {
            print("收到JS消息:\(body)")
            // 处理逻辑,如调用原生功能
        }
    }
}
  1. JavaScript 发送消息
// 在 JS 中调用
window.webkit.messageHandlers.nativeHandler.postMessage({ "action": "showToast", "text": "Hello Native!" });

注意事项

  • 内存管理:在控制器销毁时移除消息处理器,避免循环引用。
    deinit {
        webView.configuration.userContentController.removeScriptMessageHandler(forName: "nativeHandler")
    }
    
  • 数据类型:JS 和原生之间传递的数据需为基本类型或 JSON 对象。

二、HTML 内容区域大小适配

1. 动态计算内容高度并调整 WKWebView

在页面加载完成后,通过 JS 获取内容高度并更新布局。

步骤

  1. 监听页面加载完成
extension ViewController: WKNavigationDelegate {
    func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
        // 注入 JS 代码获取内容高度
        webView.evaluateJavaScript("document.documentElement.scrollHeight") { [weak self] (height, error) in
            if let height = height as? CGFloat {
                // 调整 WKWebView 的高度约束
                self?.webViewHeightConstraint.constant = height
                self?.view.layoutIfNeeded()
            }
        }
    }
}
  1. HTML 中设置视口(Viewport)
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">

2. 处理动态内容变化

若内容高度动态变化(如 AJAX 加载),需实时监听并通知原生更新。

示例

// 在 JS 中监听内容变化,并通知原生
function checkContentHeight() {
    const height = document.documentElement.scrollHeight;
    window.webkit.messageHandlers.nativeHandler.postMessage({ "type": "heightUpdate", "height": height });
}

// 示例:每隔1秒检测高度变化
setInterval(checkContentHeight, 1000);

原生端处理高度更新

func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
    if let body = message.body as? [String: Any], body["type"] as? String == "heightUpdate" {
        if let height = body["height"] as? CGFloat {
            webViewHeightConstraint.constant = height
            UIView.animate(withDuration: 0.3) {
                self.view.layoutIfNeeded()
            }
        }
    }
}

三、完整代码示例

1. 原生与 JS 交互完整实现

import UIKit
import WebKit

class ViewController: UIViewController, WKNavigationDelegate, WKScriptMessageHandler {
    var webView: WKWebView!
    var webViewHeightConstraint: NSLayoutConstraint!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        // 配置 WKWebView
        let config = WKWebViewConfiguration()
        config.userContentController.add(self, name: "nativeHandler")
        webView = WKWebView(frame: .zero, configuration: config)
        webView.navigationDelegate = self
        view.addSubview(webView)
        
        // 设置自动布局约束
webView.translatesAutoresizingMaskIntoConstraints = false
        webViewHeightConstraint = webView.heightAnchor.constraint(equalToConstant: 200) // 临时测试高度
        NSLayoutConstraint.activate([

            webView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),

            webView.leadingAnchor.constraint(equalTo: view.leadingAnchor),

            webView.trailingAnchor.constraint(equalTo: view.trailingAnchor),

            webView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor) // 添加底部约束

        ])
        
        // 加载本地 HTML 文件
        if let url = Bundle.main.url(forResource: "index", withExtension: "html") {
            webView.load(URLRequest(url: url))
        }
    }
    
    // MARK: - WKScriptMessageHandler
    func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
        if message.name == "nativeHandler", let body = message.body as? [String: Any] {
            if let action = body["action"] as? String, action == "showToast" {
                let text = body["text"] as? String ?? ""
                showToast(text: text)
            } else if let height = body["height"] as? CGFloat {
                webViewHeightConstraint.constant = height
                UIView.animate(withDuration: 0.3) {
                    self.view.layoutIfNeeded()
                }
            }
        }
    }
    
    // MARK: - 原生功能示例:显示 Toast
    func showToast(text: String) {
        let alert = UIAlertController(title: nil, message: text, preferredStyle: .alert)
        present(alert, animated: true) {
            DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
                alert.dismiss(animated: true)
            }
        }
    }
    
    // MARK: - 内存管理
    deinit {
        webView.configuration.userContentController.removeScriptMessageHandler(forName: "nativeHandler")
    }
}

2. HTML 文件示例(index.html)

<!DOCTYPE html>
<html>
<head>
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta charset**=**"UTF-8">
    <script>
        // JS 调用原生示例
        function sendMessageToNative() {
            window.webkit.messageHandlers.nativeHandler.postMessage({
                "action": "showToast",
                "text": "Hello from JavaScript!"
            });
        }

        // 动态内容高度示例
        function addContent() {
            const div = document.createElement("div");
            div.textContent = "New content added!";
            document.body.appendChild(div);
            // 通知高度变化
            const height = document.documentElement.scrollHeight;
            window.webkit.messageHandlers.nativeHandler.postMessage({
                "type": "heightUpdate",
                "height": height
            });
        }
    </script>
</head>
<body>
    <h1>WKWebView 交互示例</h1>
    <button onclick="sendMessageToNative()">点击调用原生</button>
    <button onclick="addContent()">添加内容</button>
</body>
</html>

四、关键注意事项

  1. 安全性

    • 避免在 JS 中暴露敏感逻辑。
    • 对来自 JS 的参数做严格校验,防止注入攻击。
  2. 性能优化

    • 频繁调用 evaluateJavaScript 可能影响性能,建议在必要时使用。
    • 动态调整高度时,可添加防抖(Debounce)机制避免频繁布局更新。
  3. 布局适配

    • 使用自动布局约束(Auto Layout)确保 WKWebView 正确响应屏幕旋转。
    • 若内容含图片或异步加载资源,需在资源加载完成后重新计算高度。

通过上述方法,可以实现原生与 WKWebView 的高效交互,并确保内容区域自适应显示。