一. 环境
Xcode 11,Swift 5.0, ES6
1.1 介绍
- WKWebView —允许通过 URL 加载 Web 内容
- WKScriptMessage — 接收
postMessage()
时创建的对象
- WKUserContentController —管理
JavaScript
调用和注入
- WKScriptMessageHandler —访问
WKScriptMessage
委托方法的协议
- WKWebViewConfiguration —传递给 WKWebView 的配置
二. iOS 端
2.1 添加 WKScriptMessageHandler 协议和相关变量、方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
| class ViewController: UIViewController, WKScriptMessageHandler { let screenW = UIScreen.main.bounds.width let screenH = UIScreen.main.bounds.height
let statusBarHeight: CGFloat = { var heightToReturn: CGFloat = 0.0 for window in UIApplication.shared.windows { if #available(iOS 13.0, *) { if let height = window.windowScene?.statusBarManager?.statusBarFrame.height, height > heightToReturn { heightToReturn = height } } else { heightToReturn = UIApplication.shared.statusBarFrame.size.height } } return heightToReturn }()
public func safeAreaFrame(_ viewController: UIViewController) -> CGRect { let isIphoneX = UIScreen.main.bounds.height >= 812 ? true : false
var navigationBarHeight: CGFloat = isIphoneX ? 44 : 20 var tabBarHeight: CGFloat = isIphoneX ? 34 : 0
var noNavigationExists = true
if let navigation = viewController.navigationController { noNavigationExists = false navigationBarHeight += navigation.navigationBar.frame.height } if let tabBarController = viewController.tabBarController { tabBarHeight = noNavigationExists ? tabBarHeight : 0 tabBarHeight += tabBarController.tabBar.frame.height }
let frame = CGRect(x: 0, y: navigationBarHeight, width: screenW, height: screenH - tabBarHeight - navigationBarHeight)
return frame } }
|
Extension/UIColor+Extension.swift
1 2 3 4 5 6 7 8 9 10 11 12 13
| import UIKit
extension UIColor { convenience init(red: CGFloat, green: CGFloat, blue: CGFloat, alpha: CGFloat = 1.0) { self.init(displayP3Red: red / 255.0, green: green / 255.0, blue: blue / 255.0, alpha: alpha) }
class func globalBgColor() -> UIColor { return UIColor(red: 7, green: 130, blue: 255) } }
|
2.2 懒加载 webView
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
|
lazy var webView: WKWebView = { let preferences = WKPreferences() preferences.javaScriptEnabled = true preferences.javaScriptCanOpenWindowsAutomatically = true
let configuration = WKWebViewConfiguration() configuration.preferences = preferences configuration.userContentController = WKUserContentController()
configuration.userContentController.add(self, name: "iOS")
var webView = WKWebView(frame: safeAreaFrame(self), configuration: configuration) return webView }()
|
2.3 生命周期内加载和销毁 webView 相关内容
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
|
override func viewDidLoad() { super.viewDidLoad()
view.backgroundColor = .white
let width: CGFloat = screenW
let url = URL(string: "h5线上的https地址") let urlReq = URLRequest(url: url!) webView.load(urlReq)
view.addSubview(webView)
statusView = UIView(frame: CGRect(x: 0, y: 0, width: width, height: statusBarHeight)) statusView.backgroundColor = UIColor.globalBgColor() view.addSubview(statusView)
webView.evaluateJavaScript("window.iOS = 'iOS'") { _, error in print("Error : \(String(describing: error))") }
getPermissions() }
func webViewDidClose(_: WKWebView) { webView.configuration.userContentController.removeScriptMessageHandler(forName: "iOS") }
|
2.4 监听 js 调用 iOS 方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
|
func userContentController(_: WKUserContentController, didReceive message: WKScriptMessage) { if message.name == iOS { guard let dict = message.body as? [String: AnyObject], let method = dict["method"] as? String else { return } iOSHandle(method: method, params: dict["params"] as AnyObject) } }
private func iOSHandle(method: String, params: AnyObject) { switch method { case "getContacts": returnContacts { self.webView.evaluateJavaScript("getContacts('\(jsonString)')") { _, error in print("Error : \(String(describing: error))") } } case "makePhoneCall": if let p = params as? [String: AnyObject], let phoneStr = p["phone"] as? String { makePhoneCall(phoneStr: phoneStr) } default: print("请求失败") } }
|
三. JS 端
3.1 JS 传递参数调用原生方法,但无需返回值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
|
const makePhoneCall = phoneNumber => { window.webkit.messageHandlers.iOS.postMessage({ method: 'makePhoneCall', params: { phone } }); };
makePhoneCall(18712345678);
|
3.2 JS 调用原生方法并接收原生方法返回值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
|
const getContacts = cb => { window.webkit.messageHandlers.iOS.postMessage({ method: 'getContacts' }); window['getContacts'] = msg => { cb && cb(JSON.parse(msg)); }; };
getContacts(data => { this.contactsList = data; });
|
四. 参考资料