一、原理篇
在讲解原理之前,首先需要了解下iOS提供的UIWebView组件,苹果官方是这么介绍UIWebView组件的:
You can use the UIWebView class to embed web content in your application. To do so, you simply create a UIWebView object, attach it to a window, and send it a request to load web content. You can also use this class to move back and forward in the history of webpages, and you can even set some web content properties programmatically.
UIWebView是iOS内置的浏览器控件,UIWebView用于在APP中嵌入网页,通常是html网页,也可以是PDF、txt文档等。但需要注意的是,Safari浏览器使用的浏览器控件和UIwebView组件并不是同一个,两者在性能上有很大的差距。幸运的是,苹果发布iOS8的时候,新增了一个WKWebView组件,如果你的APP只考虑支持iOS8及以上版本,那么你就可以使用这个新的浏览器控件了。
关于UIWebView类,你需要知道的一些属性和方法
属性:
- loading:是否处于加载中
- canGoBack:A Boolean value indicating whether the receiver can move backward. (只读)
- canGoForward:A Boolean value indicating whether the receiver can move forward. (只读)
- request:The URL request identifying the location of the content to load. (read-only)
方法:
- loadData:Sets the main page contents, MIME type, content encoding, and base URL.
- loadRequest:加载网络内容
- loadHTMLString:加载本地HTML文件
- stopLoading:停止加载
- goBack:后退
- goForward:前进
- reload:重新加载
- stringByEvaluatingJavaScriptFromString:执行一段js脚本,并且返回执行结果
1.1 Native(Objective-C或Swift)调用Javascript方法
Native调用Javascript语言,是通过UIWebView组件的stringByEvaluatingJavaScriptFromString方法来实现的,该方法返回js脚本的执行结果。
// Swift
webview.stringByEvaluatingJavaScriptFromString("Math.random()")
// OC
[webView stringByEvaluatingJavaScriptFromString:@"Math.random();"];
1.2 Javascript调用Native(Objective-C或Swift)方法
反过来,Javascript调用Native,并没有现成的API可以直接拿来用,而是需要间接地通过一些方法来实现。UIWebView有个特性:在UIWebView内发起的所有网络请求,都可以通过delegate函数在Native层得到通知。这样,我们就可以在UIWebView内发起一个自定义的网络请求,通常是这样的格式:jsbridge://methodName?param1=value1¶m2=value2
于是在UIWebView的delegate函数中,我们只要发现是jsbridge://开头的地址,就不进行内容的加载,转而执行相应的调用逻辑。
首先通过iframe发起一个网络请求,这个请求的作用是唤起Native APP的分享组件,将网页分享到朋友圈或分享给其他好友。JS端的核心代码如下所示:
var url = 'jsbridge://doAction?title=分享标题&desc=分享描述&link=http%3A%2F%2Fwww.baidu.com';
var iframe = document.createElement('iframe');
iframe.style.width = '1px';
iframe.style.height = '1px';
iframe.style.display = 'none';
iframe.src = url;
document.body.appendChild(iframe);
setTimeout(function() {
iframe.remove();
}, 100);
然后Webview就可以拦截这个请求,并且解析出相应的方法和参数。如下代码所示:
func webView(webView: UIWebView, shouldStartLoadWithRequest request: NSURLRequest, navigationType: UIWebViewNavigationType) -> Bool {
print("shouldStartLoadWithRequest")
let url = request.URL
let scheme = url?.scheme
let method = url?.host
let query = url?.query
if url != nil && scheme == "jsbridge" {
print("scheme == \(scheme)")
print("method == \(method)")
print("query == \(query)")
switch method! {
case "getData":
self.getData()
case "putData":
self.putData()
case "gotoWebview":
self.gotoWebview()
case "gotoNative":
self.gotoNative()
case "doAction":
self.doAction()
case "configNative":
self.configNative()
default:
print("default")
}
return false
} else {
return true
}
}
二、库的封装
在项目的实践中,我们逐渐抽象出一些通用的方法,这些方法基本上都是可以满足项目的需求。如下所示:
1.getData(datatype, callback, extra) H5从Native APP获取数据
使用场景:H5需要从Native APP获取某些数据的时候,可以调用这个方法。
参数 | 类型 | 是否必须 | 示例值 | 说明 |
---|---|---|---|---|
datatype | String | 是 | userInfo | 数据类型 |
callback | Function | 是 | 回调函数 | |
extra | Object | 否 | 传递给Native APP的数据对象 |
示例代码:
JSBridge.getData('userInfo',function(data) {
console.log(data);
});
2.putData(datatype, data) H5告诉Native APP一些数据
使用场景:H5告诉Native APP一些数据,可以调用这个方法。
参数 | 类型 | 是否必须 | 示例值 | 说明 |
---|---|---|---|---|
datatype | String | 是 | userInfo | 数据类型 |
data | Object | 是 | { username: 'zhangsan', age: 20 } | 传递给Native APP的数据对象 |
示例代码:
JSBridge.putData('userInfo', {
username: 'zhangsan',
age: 20
});
3.gotoWebview(url, page, data) Native APP新开一个Webview窗口,并打开相应网页
参数 | 类型 | 是否必须 | 示例值 | 说明 |
---|---|---|---|---|
url | String | 是 | http://www.youzan.com | 网页链接地址,一般都只要传递URL参数就可以了 |
page | String | 否 | web | 网页page类型,默认为web |
data | Object | 否 | 额外参数对象 |
示例代码:
// 示例1:打开一个网页
JSBridge.gotoWebview('http://www.youzan.com');
// 示例2:打开一个网页,并且传递额外的参数给Native APP
JSBridge.gotoWebview('http://www.youzan.com', 'goodsDetail', {
goods_id: 10000,
title: '这是商品的标题',
desc: '这是商品的描述'
});
4.gotoNative(page, data) 从H5页面跳转到Native APP的某个原生界面
参数 | 类型 | 是否必须 | 示例值 | 说明 |
---|---|---|---|---|
page | String | 是 | loginPage | Native页面标示符,例如loginPage |
data | Object | 否 | { username: 'zhangsan', age: 20 } | 额外参数对象 |
示例代码:
// 示例1:打开Native APP登录页面
JSBridge.gotoNative('loginPage');
// 示例2:打开Native APP登录页面,并且传递用户名给Native APP
JSBridge.gotoNative('loginPage', {
username: '张三'
});
5.doAction(action, data) 功能上的一些操作
参数 | 类型 | 是否必须 | 示例值 | 说明 |
---|---|---|---|---|
action | String | 是 | copy | 操作功能类型,例如分享、复制 |
data | Object | 否 | { content: '这是要复制的内容' } | 额外参数 |
示例代码:
// 示例1:调用Native APP复制一段文本到剪切板
JSBridge.doAction('copy', {
content: '这是要复制的内容'
});
// 示例2:调用Native APP的分享组件,分享当前网页到微信
JSBridge.doAction('share', {
title: '分享标题',
desc: '分享描述',
link: 'http://www.youzan.com',
imgs_url: 'http://wap.koudaitong.com/v2/common/url/create?type=homepage&index%2Findex=&kdt_id=63077&alias=63077'
});
三、调试篇
2.1 使用Safari进行UIWebView的调试
(1)首先需要打开Safari的调试模式,在Safari的菜单中,选择“Safari”→“Preference”→“Advanced”,勾选上“Show Develop menu in menu bar”选项,如下图所示。
(2)打开真机或iPhone模拟器的调试模式,在真机或iPhone模拟器中打开设置界面,选择“Safari”→“高级”→“Web检查器”,选择开启即可,如下图所示。
(3)将真机通过USB连上电脑,或者开启模拟器,Safari的“Develop”菜单下便会多出相应的菜单项,如图所示。
(4)Safari连接上UIWebView之后,我们就可以直接在Safari中直接修改HTML、CSS,以及调试Javascript。
四、一些坑
1、通过修改window.location.href也可以达到发起网络请求的效果,但是有一个很严重的问题,就是如果我们连续多次修改window.location.href的值,在Native层只能接收到最后一次请求,前面的请求都会被忽略掉。所以JS端发起网络请求的时候,需要使用iframe,这样就可以避免这个问题。
五、参考链接
- UIWebView Class Reference
- WKWebView Class Reference
- https://github.com/marcuswestin/WebViewJavascriptBridge