swift&JS交互 - JavaScriptCore
自从iOS7之后Apple退出JavaScriptCore,极大的方便了iOS与H5的联系。
一、JavaScriptCore主要类
JSContext
:JSContext是JS的执行环境,通过evaluateScript()方法可以执行JS代码
JSValue
: JSValue封装了JS与ObjC中的对应的类型,以及调用JS的API等
JSExport
: JSExport是一个协议,遵守此协议,就可以定义我们自己的协议,在协议中声明的API都会在JS中暴露出来,这样JS才能调用原生的API
二、直接通过JSContext执行JS代码
import JavaScriptCore //记得导入JavaScriptCore
let context: JSContext = JSContext()
let result1: JSValue = context.evaluateScript("1 + 1")
print(result1) // 输出2
// 定义js变量和函数
context.evaluateScript("var num1 = 2; var num2 = 3;")
context.evaluateScript("function multiply(param1, param2) { return param1 * param2; }")
// 通过js方法名调用方法
let result2 = context.evaluateScript("multiply(num1, num2)")
print(result2 ?? "result2 = nil") // 输出6
// 通过下标来获取js方法并调用方法
let squareFunc = context.objectForKeyedSubscript("multiply")
let result3 = squareFunc?.call(withArguments: [2, 3]).toString()
print(result3 ?? "result3 = nil") // 输出6
三、通过JSContext注入模型,然后调用模型的方法
1、首先定义一个协议SwiftJavaScriptDelegate 该协议必须遵守JSExport协议
这里必须使用@objc
,因为JavaScriptCore
库是ObjectiveC
版本的。如果不加@objc
,则调用无效果。定义两个函数,有参和无参两个,带有参数的注意补全。
// 定义协议SwiftJavaScriptDelegate 该协议必须遵守JSExport协议
@objc protocol MallH5BridgeProtocol: JSExport {
/// 登录
///
/// - Parameter urlString: 登录成功后跳转的url
func login(_ urlString: String)
/// 扫码
func goToScanCode()
}
2、然后定义一个模型 该模型实现SwiftJavaScriptDelegate协议
创建一个模型类遵从上面的协议,如果需要修改UI等相关操作,我们需要在主线程中操作。
// 定义一个模型 该模型实现SwiftJavaScriptDelegate协议
@objc class MallH5Bridge: NSObject, MallH5BridgeProtocol {
weak var controller: MallH5ViewController?
weak var jsContext: JSContext?
/// js调用APP登录
func login(_ urlString: String) {
DispatchQueue.main.async {
[weak webController = self.controller] in
guard AppLoginUserManager.default.isLogin == false else {
AppShare.default.showMessage(message: "您已经登录了哦")
return
}
AppShare.goToLoginVC(sourceVC: webController) {
if urlString.count > 0 && urlString != "undefined" {
webController?.redirect(toUrl: urlString)
}
}
}
}
/// 扫码
func goToScanCode() {
DispatchQueue.main.async {
AppShare.goToGoodsQRCode(source: self.controller)
}
}
}
3、将模型注入到网页中,暴露给JS
注入操作在webViewDidFinishLoad
代理方法中。
func webViewDidFinishLoad(_ webView: UIWebView) {
if let jsContext = webView.value(forKeyPath: "documentView.webView.mainFrame.javaScriptContext") as? JSContext {
let model = MallH5Bridge()
model.controller = self
model.jsContext = jsContext
jsContext.setObject(model, forKeyedSubscript: "WebViewBridge" as NSCopying & NSObjectProtocol)
jsContext.exceptionHandler = { (context, exception) in
print("exception:", exception as Any)
}
self.jsContext = jsContext
}
stopWebLoading()
}
4、JS调用swift方法
在JS方法中如下调用即可。注意这里的WebViewBridge
是你在注入时定义的名称,可以自己设置。
//以下为JS中的方法
openScan() {
(window as any)["WebViewBridge"].goToScanCode()
}
login() {
(window as any)["WebViewBridge"].login("http://www.baidu.com")
}
5、swift调用JS方法
在JS中创建方法sayHello(),最重要的是要将此方法绑定到window下否则swift调用不到。(这里坑了我一天多)
componentDidMount() {
(window as any).sayHello = this.sayHello
(window as any).sayGoodbye = this.sayGoodbye
}
sayHello() {
alert('hello')
}
sayGoodbye(argument) {
let name = argument['name']
alert('goodbye ${name}')
}
当在JS中创建完成后,swift中如下调用有参数和无参数的JS方法。
//这里是swift调用无参数的JS方法
func sayHello() {
let jsHandlerFunc = self.jsContext?.objectForKeyedSubscript("sayHello")
jsHandlerFunc?.callWithArguments([])
}
//这里是swift调用有参数的JS方法
func sayGoodbye() {
let jsHandlerFunc = self.jsContext?.objectForKeyedSubscript("sayGoodbye")
let dict = ["name": "joeal"]
jsHandlerFunc?.callWithArguments([dict])
}
如果你要在componentDidMount
方法中直接调用原生方法,那么可能会发生找不到方法的错误。其实这是因为方法还未注入完成。你可以延迟一点调用:
componentDidMount() {
setTimeout(() => {
(window as any)["WebViewBridge"].hello();
}, 2000);
}