解决问题
1、ts中如何扩展window全局对象,给它增加方法和参数
2、组合式API如何构造一个工具类
3、同步、异步操作
4、组合式API 双向绑定,方法调用
5、H5和原生交互
6、WKWebView使用
7、window.webkit.messageHandlers使用说明
环境说明
VUE 组合式API + TS +原生Swift
功能说明
这是一个简单的示例,实现输入数据缓存到原生,并在适当的时候,又从原生取回来,模拟H5和原生数据交互。
实现步骤说明
vue ts中window挂载全局标识、方法
在单独的ts(如native.d.ts)文件中,给window挂载一个参数和一个方法
declare global {
interface Window {
/// 标识,用来区分是不是嵌入在原生里面
AppNative: NativeClass;
/// 方法,用来给原生调用,响应js端的回调
callbackFun: (callbackFunction: string, callBackFucName: string) => void
}
}
interface NativeClass {
/// 方法,用来调用原生的方法
callNative(service: string, data: string, callBackFucName:string) : void
}
export default {};
定义同步、异步的交互逻辑
在单独的ts(如nativeUtil.ts)文件中,实现与原生的交互逻辑
/// 所有的回调方法Map
const callbackFuncNameMap = new Map();
/// 交互方法
const callLocal = (
// 访问的服务,其实就是定义一个标识,给到原生
// 原生判断标识,去做双方确认好要干的事情,如:存数据,取数据
service: string,
// 传入给原生的参数
data: string,
// 回调方法
callbackFunc: any
) => {
/// 回调方法的函数都是:callbackFun,这里是给每个函数取个名字
/// 然后存在map中,传到原生
const callbackFuncName = service + "Callback"
callbackFuncNameMap.set(callbackFuncName, callbackFunc);
setTimeout(() => {
if (window.AppNative) {
window.AppNative.callNative(service, data, callbackFunction);
} else {
console.error('App native service not found!');
}
}, 0);
};
// 回调函数,拿给原生作为回调入口方法
// 这个方法可能会有多个地方调用,如存完数据要回调,取完数据要回调
// 所以需要给标识,告诉ts,是原生干完啥功能后在回调
// callbackFunction这个就是标识,它由
// *const callbackFunction = service + "Callback"*所确定
window.callbackFun = (callbackFunction: string, callbackData: string) => {
const getFunc = callbackFuncNameMap.get(callbackFunction);
getFunc(callbackData);
callbackFuncNameMap.delete(callbackFunction);
};
// 异步方法
const asyncCallLocal = (service: string, data: string):Promise<any> => new Promise((resolve) => {
callLocal(service, data, (res: any) => {
// 回调
resolve(res);
});
});
// 同步方法
const syncCallLocal = async (
// 服务名
service: string,
// 参数
data: string,
) => {
let result: any = null;
// 同步
await syncCallLocalNext(service, data).then((res) => {
result = res;
});
return result;
};
const syncCallLocalNext = (
// 服务名
service: string,
// 参数
data: string,
) => new Promise((resolve) => {
callLocal(service, data, (res: any) => {
// 回调
resolve(res);
});
});
// 方法导出
export { asyncCallLocal, syncCallLocal };
swift 新建webView
实现webView方法和代理,并配置configuration
lazy var webViewConfig: WKWebViewConfiguration = {
let config = WKWebViewConfiguration()
config.userContentController = WKUserContentController()
config.userContentController.add(self, name: "H5Native")
// 注入JS代码
if let scriptPath = Bundle.main.path(forResource: "H5Bridge", ofType: "js"),
let source = try? String(contentsOfFile: scriptPath) {
let bridgeScript = WKUserScript(source: source, injectionTime: .atDocumentStart, forMainFrameOnly: false)
config.userContentController.addUserScript(bridgeScript)
}
return config
}()
桥接H5Bridge.js文件
给H5的window上挂载AppNative标识,标明这是在原生之中,再通过window.webkit.messageHandlers[service] 访问原生监听的方法,使用postMessage触发传参
(function () {
window.AppNative = {
// service :H5要调用的服务名称,区分不同的原生功能
// data:H5传递给原生的数据,json字符串
// callBackFucName:将会在回调时原样传回给调用方
callNative(service, data, callBackFucName) {
let handler = window.webkit.messageHandlers[service];
if (handler) {
handler.postMessage({
data,
callBackFucName
});
}
}
};
})()
实现原生收发消息的处理
H5Native是事先双方定义好的标识,如我这里就被定义成为了存储和获取缓存数据事件,这是个自定义的可变项
class ViewController: UIViewController, WKScriptMessageHandler {
func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
if message.name == "H5Native" {
let base = try? BaseModel.decode(from: message.body)
if let model = base {
let cache = try? CacheParam.decode(from: model.data)
if let info = cache, info.type == 0, let str = try? info.data.toJsonString() {/// 存数据
UserDefaults.standard.setValue(str, forKey: "kCache")
} else {/// 取数据
if let cache = UserDefaults.standard.string(forKey: "kCache") {
let funcIndex = "'\(model.funcIndex)'"
let jsStr = """
callbackFun(\(funcIndex),\(cache));
"""
webView.evaluateJavaScript(jsStr) { item, err in
print(err)
}
}
}
}
}
}
}
注意:
一、两边进行交互的数据都是JSON,所以在使用的时候需要转为对应的实体对象,
二、通过WKScriptMessage.body取出来的并不是我们传递的参数,而是H5Bridge.js中postMessage({data,callbackFuncName})**{data,callbackFuncName}**这个对象,所以要注意取数据时要多解开一层
三、我这里使用了decode这类扩展方法是为了把json转为对象,因为我只定义了一个方法,用来存取数据,所以我需要传参中的type字段类型来区分是:存/取
界面及方法调用
两个按钮及内容展示标签
<div class="line">
<p>账号:</p>
<input type="text" v-model="userInput" placeholder="请输入用户名" />
</div>
<div class="line">
<p>账号:</p>
<input type="password" v-model="pwdInput" placeholder="请输入密码" />
</div>
<div class="btn-container">
<button @click="saveToNative">保存到原生</button>
</div>
<div class="param-info">传递的参数:{{ JSON.stringify({user: userInput, password: pwdInput}) }}</div>
<div class="btn-container">
<button @click="getFromNative">从原生取数据</button>
</div>
<div class="param-info">回来的数据:{{ backString }}</div>
</div>
script中方法调用及实现
const userInput = ref('')
const pwdInput = ref('')
const backString = ref('')
// 访问原生存储数据
const saveToNative = () => {
const info = {
user: userInput.value,
password: pwdInput.value
}
if (window.AppNative) {
asyncCallLocal("H5Native", JSON.stringify({type:0, data:info}))
}
}
// 访问原生获取存储数据
const getFromNative = async () => {
if (window.AppNative) {
setTimeout(async () => {
const backData = await syncCallLocal("H5Native", JSON.stringify({type:1, data:{}}))
backString.value = backData
}, 0);
}
}
交互的核心
这个示例其实核心的就几句代码,一是桥接H5Bridge.js文件中:
window.webkit.messageHandlers.methodName.postMessage(data);
使用window.webkit.messageHandlers对象将消息发送到iOS原生代码中。
其中methodName是iOS原生代码中要执行的方法名(示例中的:H5Native),data是要传递给原生方法的数据。
二是原生调用挂载在window上的全局方法,完成回调:
window.methodName = function(data) { }
methodName就是示例中的callbackFun,data是要传递给H5的数据