iOS H5 混编 (项目可用)

1,176 阅读2分钟

方案

  • WKWebView - 拦截URL
  • WKWebView - MessageHandler MessageHandler 方案虽然更加优雅,但是考虑与 Android 兼容的问题,我们采用 WKWebView - 拦截URL 方案。

使用场景/需求

场景 A
H5 页面点击按钮后,App 只需要完成操作。

场景 B
H5 页面点击按钮后,App需要操作之后,把操作结果通知H5。

场景 A,场景 B 两个场景都涉及 H5 与 App 之间的通信,不过场景 B 多了一个回调操作。
回调操作其实就是 App 调用 JS 函数

App H5 通信基础

App 通知 JS

var js = "App.callJS()"
webView.evaluateJavaScript(js) { (response, error) in    
}

JS 通知 App

var iframe = document.createElement("IFRAME");    
iframe.setAttribute("src", "");    
document.documentElement.appendChild(iframe);
iframe.parentNode.removeChild(iframe);

设想

H5 发出指令拍照,App 接收到并执行拍照,拍照完成之后把图片发送给 H5
那么我们在 H5 页面是这么使用的

// 此处参数可以不填,没任何意义
App.takePicture("参数", function(data) {
    // 成功,展示图片
}, function(data) {
    // 失败,错误信息
});

思路

H5 用 ID 来标识指令的唯一性,ID 关联当前指令的名称,参数,回调。
App 用 ID 来获取名称,参数,执行回调。

通过 ID 获取回调函数,使用 setTimeout 来完成 JS 函数调用。

// 执行函数,效果相同
setTimeout("App.callJS()", 0);
App.callJS()

实现

H5 核心代码

var AppServer = {
  
  ID_COUNTER: 0,
  COMMAND_NAME_SET: {},
  COMMAND_ARGS_SET: {},
  COMMAND_SUCCESS_COMPLETION_SET: {},
  COMMAND_FAIL_COMPLETION_SET: {},
  
  // JS调用App
  callNative: function (cmd_name, cmd_args, cmd_success, cmd_fail) {

    var key = this.ID_COUNTER++;     // 指令唯一标识  
    this.COMMAND_NAME_SET[key] = cmd_name;
    this.COMMAND_ARGS_SET[key] = cmd_args;
    if (typeof cmd_success != 'undefined') this.COMMAND_SUCCESS_COMPLETION_SET[key] = cmd_success;
    if (typeof cmd_fail != 'undefined') this.COMMAND_FAIL_COMPLETION_SET[key] = cmd_fail;
    
    var iframe = document.createElement("IFRAME");    
    iframe.setAttribute("src", "DEMO://AppServer?id=" + key);    
    document.documentElement.appendChild(iframe);
    iframe.parentNode.removeChild(iframe);
    iframe = null;
  },
  
  // 指令名称
  getCommandName: function(key) {
    return this.COMMAND_NAME_SET[key];
  },
    
  // 指令参数
  getCommandArgs: function(key) {
    return this.COMMAND_ARGS_SET[key];
  },

  // App调用JS
  callJS: function (key, args) {

    if (typeof args == "undefined") {
      args = JSON.stringify({});
      args = JSON.parse(args);
    }  

    var data = args.data;
    try {
      data = JSON.stringify(data);
    } catch(e) {
      alert(e.message);
    }
    
    // 回调
    var status = args.status;
    if (status == 1) {
      // 成功
      if (typeof this.COMMAND_SUCCESS_COMPLETION_SET[key] == "undefined") return;
      setTimeout("AppServer.COMMAND_SUCCESS_COMPLETION_SET['" + key + "']('" + data + "')", 0);
    }
    else if (status == 0) {
      // 失败
      if (typeof this.COMMAND_FAIL_COMPLETION_SET[key] == "undefined") return;
      setTimeout("AppServer.COMMAND_FAIL_COMPLETION_SET['" + key + "']('" + data + "')", 0);
    }
  }

};

App 核心代码

// 指令名称
func getCommandName(_ commandID: String) {
    
    let JS = "AppServer.getCommandName(\"\(commandID)\")"
    webView.evaluateJavaScript(JS) { (response, error) in        
    }
}

// 指令参数    
func getCommandArguments(_ commandID: String)  {
    
    let JS = "AppServer.getCommandArgs(\"\(commandID)\")"
    webView.evaluateJavaScript(JS) { (response, error) in
    }
}    

// App调用JS    
func callJS(_ commandID: String, data: String) {
    
    let JS = "AppServer.callJS(\"\(commandID)\"," + data  + ")"
    webView.evaluateJavaScript(JS) { (response, error) in        
    }
}

实现效果

IMG_6362.GIF

Demo

Demo 演示使用

觉得不错的,star 一下🌟

App 使用的 H5 是打包编译好的,H5 项目在 my-project 目录下。
实际项目使用,H5 只需引入 AppServer.js 文件,与 App 约定好指令名称和参数即可。

补充

如果想测试自定义指令的话,可以自行修改 H5 项目。
H5 Demo 使用 Vue 写的,修改后直接编译打包。
Xcode 清理(shift + command + k)之后,重新运行就可以看到修改之后的效果。