之前做过一些H5和iOS原生交互的相关工作,在这里做下总结。
H5和iOS原生交互现在主流的有两种方式:
- 通过Webview拦截url请求和Webview的执行js方法来实现
- 通过JavaScriptCore来实现
目前用的比较多的方式是第一种,一方面是因为JavaScriptCore是iOS7之后才出现的,另一方面也因为WebViewJavascriptBridge这个框架,它是采用第一种方式实现的,而且封装的也比较好。
拦截请求方式
原生调用H5
原生调用H5比较简单,直接调用UIWebView(WKWebView同理)当中的
- (nullable NSString *)stringByEvaluatingJavaScriptFromString:(NSString *)script;
方法即可。同时WKWebView当中还增加了一个异步调用的方法
- (void)evaluateJavaScript:(NSString *)javaScriptString completionHandler:(void (^ __nullable)(__nullable id, NSError * __nullable error))completionHandler;
H5调用原生
H5调用原生通过在UIWebView(WKWebView同理)的
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType;
方法中拦截请求的url,通过url携带的信息调用相关原生方法。(实例代码来源:WebViewJavascriptBridge)
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType {
if (webView != _webView) { return YES; }
NSURL *url = [request URL];
__strong WVJB_WEBVIEW_DELEGATE_TYPE* strongDelegate = _webViewDelegate;
if ([_base isCorrectProcotocolScheme:url]) {
if ([_base isBridgeLoadedURL:url]) {
[_base injectJavascriptFile];
} else if ([_base isQueueMessageURL:url]) {
NSString *messageQueueString = [self _evaluateJavascript:[_base webViewJavascriptFetchQueyCommand]];
[_base flushMessageQueue:messageQueueString];
} else {
[_base logUnkownMessage:url];
}
return NO;
} else if (strongDelegate && [strongDelegate respondsToSelector:@selector(webView:shouldStartLoadWithRequest:navigationType:)]) {
return [strongDelegate webView:webView shouldStartLoadWithRequest:request navigationType:navigationType];
} else {
return YES;
}
}
以上代码通过isCorrectProcotocolScheme:
、isBridgeLoadedURL:
和isQueueMessageURL:
三个方法对拦截的url进行判断需要执行哪个原生的方法。
一些H5和原生的交互的比较简单的使用将参数拼在url中,在url拦截时将url截取获得相关参数。而WebViewJavascriptBridge则是将需要获取的数据放到js对象当中,让原生通过[self _evaluateJavascript:[_base webViewJavascriptFetchQueyCommand]]
调用js去取到数据。
JavaScriptCore方式
JavaScriptCore是iOS7之后添加的库,这个库使H5和原生的交互变得比较简单。
原生调用H5
JSContext *context = [self.webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
NSString *textJS = @"showAlert('这里是JS中alert弹出的message')";
[context evaluateScript:textJS];
H5调用原生
JavaScriptCore方式H5和原生交互更像是将一个原生对象注册到H5的js当中,通过js来调用。
js直接调用方法
- (void)webViewDidFinishLoad:(UIWebView *)webView
{
JSContext *context=[self.webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
context[@"test"] = ^() {
//此处获得方法传过来的参数,参数数量不固定,由js在调用时传递个数决定
NSArray *args = [JSContext currentArguments];
for (id obj in args) {
NSLog(@"%@",obj);
}
};
}
//我们模拟一下H5当中的js调用
NSString *jsStr1=@"test()";
[context evaluateScript:jsStr1];
NSString *jsStr2=@"test('参数1')";
[context evaluateScript:jsStr2];
NSString *jsStr3=@"test('参数A','参数B')";
[context evaluateScript:jsStr3];
js通过对象调用方法,此时需要向js当中传递一个实现了JSExport协议的对象,通过该对象调用在协议中声明并实现的自定义方法。
创建一个继承JSExport协议的协议,并添加自定义方法
@protocol TestProtocol <JSExport>
//无参数
-(void)test;
//一个参数
-(void)testOne:(NSString *)one;
//两个参数
-(void)testOne:(NSString *)one two:(NSString *)two;
@end
//在需要调用的对象当中实现这个协议
@interface TestJSObject : NSObject<TestProtocol>
@end
@implementation TestJSObject
-(void)test
{
NSLog(@"this is a test");
}
-(void)testOne:(NSString *)one
{
NSLog(@"this is a test,one=%@",one);
}
-(void)testOne:(NSString *)one two:(NSString *)two
{
NSLog(@"this is a test,one=%@,two=%@",one,two);
}
@end
//将实现TestProtocol协议的对象注册进js当中即可
- (void)webViewDidFinishLoad:(UIWebView *)webView
{
JSContext *context=[self.webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
TestJSObject *testJS=[[TestJSObject alloc] init];
context[@"testJS"]=testJS;
}
在js当中调用时需要注意当调用多个参数的方法时需要需要将方法名拼接起来调用,比如我们写的两个参数调用就需要写成testJS.testOnetwo(‘参数1’,’参数2’)
,如下:
//我们模拟一下H5当中的js调用
NSString *jsStr1=@"testJS.test()";
[context evaluateScript:jsStr1];
NSString *jsStr2=@"testJS.testOne('参数1')";
[context evaluateScript:jsStr2];
NSString *jsStr3=@"testJS.testOnetwo('参数A','参数B')";
[context evaluateScript:jsStr3];