前一篇文章已经介绍了JS与iOS交互,用UIWebView实现的方法。需要了解相关知识,请查看前一篇博客。
这里我们来介绍另外一种基于iOS8新推出的WKWebView组件实现的方法。WKWebView实际上就是一个新的、高性能的WebView 解决方案。下面我们就来看看如何利用WKWebView实现混合开发框架。先看看实现效果:
说明:本文我们只讨论如何实现iOS与JS的交互,由于不精通HTML,所以小Demo可能会有小瑕疵,大神莫怪。
1. 配置偏好设置
WKWebViewConfiguration *config = [[WKWebViewConfiguration alloc] init];
// 设置偏好设置
config.preferences = [[WKPreferences alloc] init];
// 默认为0
config.preferences.minimumFontSize = 10;
// 默认认为YES
config.preferences.javaScriptEnabled = YES;
// 在iOS上默认为NO,表示不能自动通过窗口打开
config.preferences.javaScriptCanOpenWindowsAutomatically = NO;
2. 配置JS与iOS的交互内容
WKUserContentController是用于给JS注入对象的,注入对象后,JS端就可以使用。直白点说就是给JS与iOS的交互提供一个通道,以后JS与iOS交互就是通过这个AppModel值识别和传值的,传数据统一通过body传,可以是多种类型,只支持NSNumber, NSString, NSDate, NSArray,NSDictionary, and NSNull类型。
// 通过JS与webview内容交互
config.userContentController = [[WKUserContentController alloc] init];
// 注入JS对象名称AppModel,当JS通过AppModel来调用时,
// 我们可以在WKScriptMessageHandler代理中接收到
[config.userContentController addScriptMessageHandler:self name:@"AppModel"];
下图是HTML中想iOS发送消息的代码,从这句代码我们就能看出AppModel值得作用
3. 加载网页
//通过默认的构造器来创建对象
self.webView = [[WKWebView alloc] initWithFrame:self.view.bounds
configuration:config];
NSURL *path = [[NSBundle mainBundle] URLForResource:@"test" withExtension:@"html"];
[self.webView loadRequest:[NSURLRequest requestWithURL:path]];
[self.view addSubview:self.webView];
4. 加载网页时调用的代理方法
完成以上配置以后我们就按照程序的执行顺序解析代码,这里我们以点击提示弹窗为例进行分析,其他几个功能里也有一些需要注意的点就需要大家自己思考了。
在HTML中添加点击事件
点击事件为HTML代码,不做详述。实现点击事件,这个方法会调用iOS中的方法把body值传过去
function callJsAlert() {
//在JS端调用alert函数时,会触发代理方法一,括号中的内容会传到此代理方法中
alert('这个是OC调用JS的方法,并且通过Alert()进行显示出来!');
//在JS端调用postMessage函数时,会触发代理方法二,body的内容会传过去
window.webkit.messageHandlers.AppModel.postMessage({body: '在JS中调用JS中方法'});
}
运行程序后,触发点击事件前会依次执行以下几个代理方法,我们逐一分析下
/**
代理方法三:
请求开始前,会先调用此代理方法,与UIWebView的
- (BOOL)webView:(UIWebView *)webView
shouldStartLoadWithRequest:(NSURLRequest *)request
navigationType:(UIWebViewNavigationType)navigationType;
方法类似,此方法在上篇博客中分析过
*/
- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:
(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler {
NSString *hostname = navigationAction.request.URL.host.lowercaseString;
if (navigationAction.navigationType == WKNavigationTypeLinkActivated
&& ![hostname containsString:@".lanou.com"]) {
// 对于跨域,需要手动跳转
[[UIApplication sharedApplication] openURL:navigationAction.request.URL];
// 不允许web内跳转
decisionHandler(WKNavigationActionPolicyCancel);
} else {
self.progressView.alpha = 1.0;
decisionHandler(WKNavigationActionPolicyAllow);
}
NSLog(@"00===%s", __FUNCTION__);
}
decisionHandler(WKNavigationActionPolicyAllow)的作用是设置能否跳转,WKNavigationActionPolicyAllow是一个枚举值表示允许调转,WKNavigationActionPolicyCancel表示不允许跳转。
/**
代理方法四
开始导航跳转时会回调
*/
- (void)webView:(WKWebView *)webView didStartProvisionalNavigation:(null_unspecified WKNavigation *)navigation {
NSLog(@"22===%s", __FUNCTION__);
}
/**
代理方法五:
在响应完成时,会回调此方法如果设置为不允许响应,web内容就不会传过来
*/
- (void)webView:(WKWebView *)webView
decidePolicyForNavigationResponse:(WKNavigationResponse *)navigationResponse
decisionHandler:(void (^)(WKNavigationResponsePolicy))decisionHandler {
decisionHandler(WKNavigationResponsePolicyAllow);
NSLog(@"11===%s", __FUNCTION__);
}
/**
代理方法六:
页面内容到达main frame时回调
*/
- (void)webView:(WKWebView *)webView didCommitNavigation:(null_unspecified WKNavigation *)navigation {
NSLog(@"55===%s", __FUNCTION__);
}
/**
代理方法七:
导航完成时,会回调(也就是页面载入完成了)
*/
- (void)webView:(WKWebView *)webView didFinishNavigation:(null_unspecified WKNavigation *)navigation {
NSLog(@"66===%s", __FUNCTION__);
}
这个代理方法执行完成,网页才完整的加载出来,还有一些代理方法是在页面加载异常时调用的,我们这里统一来看一下。
// 接收到重定向时会回调
- (void)webView:(WKWebView *)webView
didReceiveServerRedirectForProvisionalNavigation:(null_unspecified WKNavigation *)navigation {
NSLog(@"33===%s", __FUNCTION__);
}
// 导航失败时会回调
- (void)webView:(WKWebView *)webView
didFailProvisionalNavigation:(null_unspecified WKNavigation *)navigation withError:(NSError *)error {
NSLog(@"44===%s", __FUNCTION__);
}
// 导航失败时会回调
- (void)webView:(WKWebView *)webView didFailNavigation:
(null_unspecified WKNavigation *)navigation withError:(NSError *)error {
NSLog(@"77===%s", __FUNCTION__);
}
// 9.0才能使用,web内容处理中断时会触发
- (void)webViewWebContentProcessDidTerminate:(WKWebView *)webView {
NSLog(@"99===%s", __FUNCTION__);
}
//关闭网页时调用
- (void)webViewDidClose:(WKWebView *)webView {
NSLog(@"99===%s", __FUNCTION__);
}
5. 触发点击事件时调用的代理方法
触发点击事件,即执行如下HTML代码
//提示弹窗
alert('这个是OC调用JS的方法,并且通过Alert()进行显示出来!');
//确认弹窗
if (confirm('confirm', 'Objective-C call js to show confirm')) {
document.getElementById('jsParamFuncSpan').innerHTML
= 'true';
} else {
document.getElementById('jsParamFuncSpan').innerHTML
= 'false';
}
//输入弹窗
var response = prompt('Hello', '请输入你的名字:');
document.getElementById('jsParamFuncSpan').innerHTML = response;
执行上述代码后,iOS会执行下面的代理方法调用这几个代理方法后,iOS会执行相应的弹窗操作。
/** 代理方法一 */
// 在JS端调用alert函数时,会触发此代理方法。
// JS端调用alert时所传的数据可以通过message拿到
// 在原生得到结果后,需要回调JS,是通过completionHandler回调
- (void)webView:(WKWebView *)webView runJavaScriptAlertPanelWithMessage:(NSString *)message
initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(void))completionHandler {
NSLog(@"100===%s", __FUNCTION__);
UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"alert" message:@"JS调用alert"
preferredStyle:UIAlertControllerStyleAlert];
[alert addAction:[UIAlertAction actionWithTitle:@"确定" style:
UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
completionHandler();
}]];
[self presentViewController:alert animated:YES completion:NULL];
NSLog(@"------>%@", message);
}
// JS端调用confirm函数时,会触发此方法
// 通过message可以拿到JS端所传的数据
// 在iOS端显示原生alert得到YES/NO后
// 通过completionHandler回调给JS端
- (void)webView:(WKWebView *)webView
runJavaScriptConfirmPanelWithMessage:(NSString *)message
initiatedByFrame:(WKFrameInfo *)frame
completionHandler:(void (^)(BOOL result))completionHandler {
NSLog(@"101===%s", __FUNCTION__);
UIAlertController *alert = [UIAlertController alertControllerWithTitle:
@"confirm" message:@"JS调用confirm"
preferredStyle:UIAlertControllerStyleAlert];
[alert addAction:[UIAlertAction actionWithTitle:@"确定"
style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action){
completionHandler(YES);
}]];
[alert addAction:[UIAlertAction actionWithTitle:@"取消"
style:UIAlertActionStyleCancel handler:^(UIAlertAction * _Nonnull action) {
completionHandler(NO);
}]];
[self presentViewController:alert animated:YES completion:NULL];
NSLog(@"%@", message);
}
// JS端调用prompt函数时,会触发此方法
// 要求输入一段文本
// 在原生输入得到文本内容后,通过completionHandler回调给JS
- (void)webView:(WKWebView *)webView
runJavaScriptTextInputPanelWithPrompt:(NSString *)prompt
defaultText:(nullable NSString *)defaultText
initiatedByFrame:(WKFrameInfo *)frame
completionHandler:(void (^)(NSString * __nullable result))completionHandler {
NSLog(@"102===%s", __FUNCTION__);
NSLog(@"%@", prompt);
UIAlertController *alert = [UIAlertController alertControllerWithTitle:
@"textinput" message:@"JS调用输入框"
preferredStyle:UIAlertControllerStyleAlert];
[alert addTextFieldWithConfigurationHandler:^(UITextField * _Nonnull textField) {
textField.textColor = [UIColor redColor];
}];
[alert addAction:[UIAlertAction actionWithTitle:@"确定"
style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
completionHandler([[alert.textFields lastObject] text]);
}]];
[self presentViewController:alert animated:YES completion:NULL];
}
6. 执行跳转控制器的代理方法
HTML代码中执行如下代码会调用iOS中的代理方法
// AppModel是我们所注入的对象
window.webkit.messageHandlers.AppModel.postMessage({body: response});
执行上述代码会调用如下代理方法
/** 代理方法二 */
- (void)userContentController:(WKUserContentController *)userContentController
didReceiveScriptMessage:(WKScriptMessage *)message {
if ([message.name isEqualToString:@"AppModel"]) {
// 打印所传过来的参数,只支持NSNumber, NSString, NSDate, NSArray,
// NSDictionary, and NSNull类型
NSLog(@"9999===%@", message.body);
//跳转到指定控制器中
JumpViewController *root = [[JumpViewController alloc]init];
root.str = message.body;
[self.navigationController pushViewController:root animated:YES];
}
}
到这里终于实现了控制器的跳转
7. 其他
在加载网页时往往还会用KVO监听是否正在加载、当前进度等状态,这里不再赘述。
8. 详细代码请下载源码自行研究
地址:http://download.youkuaiyun.com/detail/qq_34101611/9553494