按照官方文档上的意思简单介绍这几个类的作用:
JSVirtualMachine 是JavaScript的一个封闭的运行环境,主要用于支持JavaScript并行运行和管理JavaScript与OC或者Swift之间桥接的内存。
JSContext是JavaScript的运行环境,可以在OC或者Swift中创建一个上下文环境来执行JavaScript代码。
JSValue的实例是对JavaScript值的封装引用。可以用来在JavaScript和原生代码之间传递数据。
JSManagedValue是对JSValue的包装,使用JSManagedValue不会引起循环引用。
JSExport将遵守该协议的方法、属性等导出给JavaScript使用。
单纯的采用JS调用原生方法.
html代码
js和原生的相互调用
js和原生的相互调用
---------------------------------
// 原生核心代码
extension ViewController: UIWebViewDelegate {
func webView(_ webView: UIWebView, shouldStartLoadWith request: URLRequest, navigationType: UIWebViewNavigationType) -> Bool {
let url = request.url!
let scheme = url.scheme
let absoluteString = url.absoluteString
let query = url.query
let host = url.host
print("url = \(url), absoluteString = \(absoluteString), scheme = \(scheme), query = \(query), host = \(host)")
if scheme! == "openvc" {
// 根据Url传递的信息处理逻辑
print("拦截js操作,处理原生逻辑")
return false
}
return true
}
}
利用JavaScriptCore来实现JS调用原生方法,处理操作之后,再用原生调用JS方法。
实现步骤:
1、在html页面中定义需要调用原生的方法、原生回调JS的方法
2、在webViewDidFinishLoad方法中实现html需要调用的方法,获取参数等
3、利用evaluateScript执行回调html的方法
方法一:
// html代码
js和原生的相互调用
js和原生的相互调用
------------------------
// OC核心代码
- (void)webViewDidFinishLoad:(UIWebView *)webView {
JSContext * context = [webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
__weak typeof(self) weakSelf = self;
context[@"openImagePickerVC"] = ^() {
// 获取js传递过来的参数
NSArray * params = [JSContext currentArguments];
NSLog(@"js 传递的参数 params = %@", params);
[weakSelf handleOtherOperating];
};
}
- (void)handleOtherOperating {
// 其他处理
NSLog(@"原生处理方法 thread = %@", [NSThread currentThread]);
// 回调js方法
JSContext * context = [_webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
[context evaluateScript:@"handleOCCallJSMothod('oc传递的参数')"];
}
@end
方法二:利用JSExport协议来处理
实现步骤:
1、在html页面中定义需要调用原生的方法、原生回调JS的方法
2、创建一个遵守JSExport的协议。提供一些html中需要调用的方法
3、创建一个类来实现这个协议中的方法
4、在webViewDidFinishLoad中利用JSContext将这个类暴露给html
// 创建一个工具类
@protocol JSCallOCProtocol
// 提供给js调用的方法,如果想暴露一些属性也是可以的
- (void)jsCallMethod;
@end
@interface Tools : NSObject
@property (nonatomic, strong) JSContext * context;
@end
#import "Tools.h"
@implementation Tools
- (void)jsCallMethod {
NSLog(@"js 调用 原生方法");
// 如果还想要调用js的方法就需要拿到webView的JSContext
[self.context evaluateScript:@"handleOCCallJSMothod('oc传递的参数')"];
}
@end
// html
js和原生的相互调用
js和原生的相互调用
// 原生核心代码
- (void)webViewDidFinishLoad:(UIWebView *)webView {
JSContext * context = [webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
Tools * tools = [Tools new];
tools.context = context;
// 方式一
context[@"tools"] = tools;
// 方式二
// [context setObject:tools forKeyedSubscript:@"tools"];
}
Swift版本:
// Tools.swift
import UIKit
import JavaScriptCore
@objc protocol SwiftTools: JSExport {
func jsCallMethod(_ param: String)
}
class Tools: NSObject, SwiftTools {
var context: JSContext?
func jsCallMethod(_ param: String) {
// 如果还想要调用js的方法就需要拿到webView的JSContext
print("js 调用 原生方法 param = \(param)")
_ = context?.evaluateScript("handleOCCallJSMothod('swift传递的参数')")
}
}
// html
js和原生的相互调用
js和原生的相互调用
// 核心代码
func webViewDidFinishLoad(_ webView: UIWebView) {
// 获取上下文
let jsContext = webView.value(forKeyPath: "documentView.webView.mainFrame.javaScriptContext") as? JSContext
let tools = Tools()
tools.context = jsContext
jsContext?.setObject(tools, forKeyedSubscript: "swiftTools" as NSCopying & NSObjectProtocol)
}
一些基础知识介绍
配置信息
WKWebViewConfiguration用来初始化WKWebView的配置。
WKPreferences配置webView能否使用JS或者其他插件等
WKUserContentController用来配置JS交互的代码
UIDelegate
UIDelegate用来控制WKWebView中一些弹窗的显示(alert、confirm、prompt)。
JS端调用alert()方法会触发下面这个方法,并且通过message获取到alert的信息
- (void)webView:(WKWebView *)webView runJavaScriptAlertPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(void))completionHandler;
拦截URL的方式处理
// html 参考上面UIWebView例子代码
- (void)viewDidLoad {
[super viewDidLoad];
WKWebViewConfiguration * configuration = [[WKWebViewConfiguration alloc] init];
configuration.userContentController = [WKUserContentController new];
WKPreferences * preferences = [WKPreferences new];
preferences.javaScriptCanOpenWindowsAutomatically = YES;
preferences.minimumFontSize = 50.0;
configuration.preferences = preferences;
_wkWebView = [[WKWebView alloc] initWithFrame:[UIScreen mainScreen].bounds configuration:configuration];
[self.view addSubview:_wkWebView];
NSURL * urlstring = [[NSBundle mainBundle] URLForResource:@"index" withExtension:@"html"];
[_wkWebView loadRequest:[[NSURLRequest alloc] initWithURL:urlstring]];
_wkWebView.navigationDelegate = self;
_wkWebView.UIDelegate = self;
}
// 核心代码
- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler {
NSURL * url = navigationAction.request.URL;
NSString * scheme = url.scheme;
NSString * query = url.query;
NSString * host = url.host;
NSLog(@"scheme = %@, query = %@, host = %@", scheme, query, host);
if ([scheme isEqualToString:@"openvc"]) {
[self handleJSMessage];
decisionHandler(WKNavigationActionPolicyCancel);
return;
}
decisionHandler(WKNavigationActionPolicyAllow);
}
- (void)handleJSMessage {
// 回调JS方法
[_wkWebView evaluateJavaScript:@"handleOCCallJSMothod('123')" completionHandler:^(id _Nullable x, NSError * _Nullable error) {
NSLog(@"x = %@, error = %@", x, error.localizedDescription);
}];
}
#pragma mark - WKUIDelegate
// 处理JS中回调方法的alert方法
- (void)webView:(WKWebView *)webView runJavaScriptAlertPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(void))completionHandler {
NSLog(@"message = %@", message);
UIAlertController * alert = [UIAlertController alertControllerWithTitle:@"温馨提示" message:message preferredStyle:UIAlertControllerStyleAlert];
[alert addAction:[UIAlertAction actionWithTitle:@"确定" style:UIAlertActionStyleCancel handler:nil]];
[self presentViewController:alert animated:YES completion:nil];
completionHandler();
}
步骤:
1.在原生代码中利用userContentController添加JS端需要调用的原生方法
2.实现WKScriptMessageHandler协议中唯一一个方法
3.在该方法中根据message.name获取调用的方法名做相应的处理,通过message.body获取JS端传递的参数
4.在JS端window.webkit.messageHandlers.methodName.postMessage([“name”,“zhangdan”,“age”, 18])调用该方法
代码如下:
// html
// js调用原生的方法
function handleCallOCMethod() {
// callMethod是原生定义接收的方法名
window.webkit.messageHandlers.callMethod.postMessage(["name","zhangdan","age", 18]);
}
function handleOCCallJSMothod(param) {
alert('原生调用JS方法 参数 = ' + param);
}
// 原生代码
// 添加JS端调用的代码
[configuration.userContentController addScriptMessageHandler:self name:@"callMethod"];
#pragma mark - WKScriptMessageHandler
- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message {
NSLog(@"message = %@", message.name);
NSLog(@"params = %@", message.body);
if ([message.name isEqualToString:@"callMethod"]) {
// 其他处理,如果要回调JS端方法,依然采用- (void)evaluateJavaScript: completionHandler:方法,和上面一样;
}
}
// 注意:在合适的地方将添加的方法移除,防止循环引用
[_wkWebView.configuration.userContentController removeScriptMessageHandlerForName:@"callMethod"];
swift版本代码
class WkWebViewViewController: UIViewController {
private let methodName = "callNativeMethod"
private lazy var wkWebView: WKWebView = {
let configuration = WKWebViewConfiguration()
configuration.userContentController = WKUserContentController()
configuration.userContentController.add(self, name: methodName)
let preferences = WKPreferences()
preferences.javaScriptCanOpenWindowsAutomatically = true
preferences.minimumFontSize = 50.0
configuration.preferences = preferences
let wkWebView = WKWebView(frame: UIScreen.main.bounds, configuration: configuration)
view.addSubview(wkWebView)
let urlstring = Bundle.main.url(forResource: "index-wkWebView", withExtension: "html")
wkWebView.load(URLRequest(url: urlstring!))
wkWebView.navigationDelegate = self;
wkWebView.uiDelegate = self;
return wkWebView
}()
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(wkWebView)
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
wkWebView.configuration.userContentController.removeScriptMessageHandler(forName: methodName)
}
deinit {
print("deinit")
}
private func handleJSMessage() {
wkWebView.evaluateJavaScript("handleOCCallJSMothod('123')") { (x, error) in
print("x = \(x ?? ""), error = \(error?.localizedDescription ?? "")")
}
}
}
extension WkWebViewViewController: WKScriptMessageHandler {
func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
let params = message.body
if methodName == message.name {
print("原生处理 params = \(params)")
self.handleJSMessage()
}
}
}
extension WkWebViewViewController: WKNavigationDelegate {
func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {
let url = navigationAction.request.url!
let scheme = url.scheme ?? ""
let query = url.query ?? ""
let host = url.host ?? ""
print("url = \(url), scheme = \(scheme), query = \(query), host = \(host)")
if scheme == "openvc" {
// 根据Url传递的信息处理逻辑
print("拦截js操作,处理原生逻辑")
self.handleJSMessage()
decisionHandler(.cancel)
return
}
decisionHandler(.allow)
}
}
extension WkWebViewViewController: WKUIDelegate {
func webView(_ webView: WKWebView, runJavaScriptAlertPanelWithMessage message: String, initiatedByFrame frame: WKFrameInfo, completionHandler: @escaping () -> Void) {
let alert = UIAlertController(title: "温馨提示", message: message, preferredStyle: UIAlertControllerStyle.alert)
alert .addAction(UIAlertAction(title: "确定", style: UIAlertActionStyle.default, handler: nil))
self.present(alert, animated: true, completion: nil)
completionHandler()
}
}
该框架地址:https://github.com/marcuswestin/WebViewJavascriptBridge
根据GitHub的使用方法就能明白该框架的使用了。该框架的实现原理也是对URL进行拦截来实现的。具体详见代码。
//
WebViewJavascriptBridge
WebViewJavascriptBridge
// 原生代码
class JavascriptBridgeViewController: UIViewController {
private var bridge: WebViewJavascriptBridge!
private lazy var wkWebView: WKWebView = {
let configuration = WKWebViewConfiguration()
configuration.userContentController = WKUserContentController()
let preferences = WKPreferences()
preferences.javaScriptCanOpenWindowsAutomatically = true
preferences.minimumFontSize = 50.0
configuration.preferences = preferences
let wkWebView = WKWebView(frame: UIScreen.main.bounds, configuration: configuration)
view.addSubview(wkWebView)
let urlstring = Bundle.main.url(forResource: "index-JavascriptBridge", withExtension: "html")
wkWebView.load(URLRequest(url: urlstring!))
wkWebView.uiDelegate = self
return wkWebView
}()
override func viewDidLoad() {
super.viewDidLoad()
testWkWebView()
}
private func testWebView() {
let webView = UIWebView(frame: UIScreen.main.bounds)
let path = Bundle.main.path(forResource: "index-JavascriptBridge", ofType: "html")
let url = URL.init(fileURLWithPath: path!)
webView.loadRequest(URLRequest(url: url))
view.addSubview(webView)
bridge = WebViewJavascriptBridge(webView)
bridge.registerHandler("nativeMothod") { (data, callback) in
print("js 传递过来的数据 = \(String(describing: data))")
callback!("回调")
}
}
private func testWkWebView() {
view.addSubview(wkWebView)
bridge = WebViewJavascriptBridge(forWebView: wkWebView)
bridge.registerHandler("nativeMothod") { (data, callback) in
print("js 传递过来的数据 = \(String(describing: data))")
callback!("回调")
}
}
@IBAction func nativeCallJsMethod(_ sender: Any) {
bridge.callHandler("jsMethod", data: ["name" : 123]) { (response) in
print("response = \(response ?? "")")
}
}
}
extension JavascriptBridgeViewController: WKUIDelegate {
func webView(_ webView: WKWebView, runJavaScriptAlertPanelWithMessage message: String, initiatedByFrame frame: WKFrameInfo, completionHandler: @escaping () -> Void) {
let alert = UIAlertController(title: "温馨提示", message: message, preferredStyle: UIAlertControllerStyle.alert)
alert .addAction(UIAlertAction(title: "确定", style: UIAlertActionStyle.default, handler: nil))
self.present(alert, animated: true, completion: nil)
completionHandler()
}
}
上面的几种方式基本就包括了常规的JS和原生之间的交互方式.
代码地址