swift&JS交互 - JavaScriptCore

2019-08-23 17:42:37 浏览数 (1)

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代码

代码语言:javascript复制
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,则调用无效果。定义两个函数,有参和无参两个,带有参数的注意补全。

代码语言:javascript复制
// 定义协议SwiftJavaScriptDelegate 该协议必须遵守JSExport协议
@objc protocol MallH5BridgeProtocol: JSExport {
    
    /// 登录
    ///
    /// - Parameter urlString: 登录成功后跳转的url
    func login(_ urlString: String)
    
    /// 扫码
    func goToScanCode()
}
2、然后定义一个模型 该模型实现SwiftJavaScriptDelegate协议

创建一个模型类遵从上面的协议,如果需要修改UI等相关操作,我们需要在主线程中操作。

代码语言:javascript复制
// 定义一个模型 该模型实现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代理方法中。

代码语言:javascript复制
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是你在注入时定义的名称,可以自己设置。

代码语言:javascript复制
//以下为JS中的方法
openScan() {
    (window as any)["WebViewBridge"].goToScanCode()
}

login() {
    (window as any)["WebViewBridge"].login("http://www.baidu.com")
}
5、swift调用JS方法

在JS中创建方法sayHello(),最重要的是要将此方法绑定到window下否则swift调用不到。(这里坑了我一天多)

images.jpeg

代码语言:javascript复制
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方法。

代码语言:javascript复制
//这里是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方法中直接调用原生方法,那么可能会发生找不到方法的错误。其实这是因为方法还未注入完成。你可以延迟一点调用:

代码语言:javascript复制
componentDidMount() {
  setTimeout(() => {
        (window as any)["WebViewBridge"].hello();
    }, 2000);
}

0 人点赞