iOS(swift)与H5(Vue+ts)交互

解决问题

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的数据

完整项目示例

点我查看

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

张子都

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值