使用WKWebView的时候,如果想要实现JS调用OC方法,除了拦截URL之外,还有一种简单的方式。那就是利用WKWebView的新特性MessageHandler来实现JS调用原生方法。
MessageHandler 是什么?
WKWebView 初始化时,有一个参数叫configuration,它是WKWebViewConfiguration
类型的参数,而WKWebViewConfiguration
有一个属性叫userContentController
,它又是WKUserContentController
类型的参数。WKUserContentController
对象有一个方法- addScriptMessageHandler:name:
,我把这个功能简称为MessageHandler。
- addScriptMessageHandler:name:
有两个参数,第一个参数是userContentController的代理对象,第二个参数是JS里发送postMessage的对象。
WKScriptMessageHandler
协议。我们在该API的描述里可以看到在JS中的使用方法: window.webkit.messageHandlers..postMessage( )//其中 ,就是上面方法里的第二个参数`name`。//例如我们调用API的时候第二个参数填@"Share",那么在JS里就是://window.webkit.messageHandlers.Share.postMessage( )// 是一个键值对,键是body,值可以有多种类型的参数。// 在`WKScriptMessageHandler`协议中,我们可以看到mssage是`WKScriptMessage`类型,有一个属性叫body。// 而注释里写明了body 的类型:Allowed types are NSNumber, NSString, NSDate, NSArray, NSDictionary, and NSNull.
怎么使用MessageHandler?
1.创建WKWebViewConfiguration
对象,配置各个API对应的MessageHandler。
WKUserContentController对象可以添加多个scriptMessageHandler。
// 这是创建configuration 的过程 WKWebViewConfiguration *configuration = [[WKWebViewConfiguration alloc] init]; WKPreferences *preferences = [WKPreferences new]; preferences.javaScriptCanOpenWindowsAutomatically = YES; preferences.minimumFontSize = 40.0; configuration.preferences = preferences;
- (void)viewDidLoad { [super viewDidLoad]; // Do any additional setup after loading the view. self.webView.UIDelegate = self; self.webView.navigationDelegate = self; NSString *path = [[NSBundle mainBundle] pathForResource:@"test" ofType:@"html"]; NSURL *url = [NSURL fileURLWithPath:path]; // [NSURL URLWithString:@"http://www.baidu.com"]; // NSURLRequest *request = [NSURLRequest requestWithURL:url]; [self.webView loadRequest:request]; // addScriptMessageHandler 很容易导致循环引用 // 控制器 强引用了WKWebView,WKWebView copy(强引用了)configuration, configuration copy (强引用了)userContentController // userContentController 强引用了 self (控制器) [self.webView.configuration.userContentController addScriptMessageHandler:self name:@"clickYou"]; }
需要注意的是addScriptMessageHandler
很容易引起循环引用,导致控制器无法被释放,所以需要加入以下这段:
- (void)viewWillDisappear:(BOOL)animated{ [super viewWillDisappear:animated]; // 因此这里要记得移除handlers [self.webView.configuration.userContentController removeScriptMessageHandlerForName:@"clickYou"];}
2.实现协议方法。
我这里实现了两个协议<WKUIDelegate,WKScriptMessageHandler>
,WKUIDelegate
是因为我在JS中弹出了alert 。WKScriptMessageHandler
是因为我们要处理JS调用OC方法的请求。
- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message{ NSLog(@"%@:%@",message.name,message.body); if([message.name isEqualToString:@"clickYou"]){ [self clickYou]; }}
WKScriptMessage
有两个关键属性name
和 body
。
3.处理HTML中JS调用。
function btnClick(){ //JS调用 window.webkit.messageHandlers.clickYou.postMessage({ "Acer": 500, "Dell": 600 }); alert(1);}
注意其中的clickYou,根据OC中注册的name一样.
4.OC调用JS
NSString *title = @"主题"; NSString *content = @"内容"; NSString *url = @"http://www.baidu.com"; NSString *jsStr = [NSString stringWithFormat:@"shareResult('%@','%@','%@')",title,content,url]; [self.webView evaluateJavaScript:jsStr completionHandler:^(id _Nullable result, NSError * _Nullable error) { }];
function shareResult(title,content,url){ alert("title:"+title+"content:"+content+"url:"+url);}
5.实现WKUIDelegate
如果JS中需要alert,那么实现WKUIDelegate三个代理方法,如下:
// alert框/** alert框 @param webView WKWebView @param message 消息文本内容 @param frame frame主窗口 @param completionHandler 窗口消失回调 */- (void)webView:(WKWebView *)webView runJavaScriptAlertPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(void))completionHandler{ kFunLog UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@"提示" message:message?:@"" preferredStyle:UIAlertControllerStyleAlert]; [alertController addAction:([UIAlertAction actionWithTitle:@"确认" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) { completionHandler(); }])]; [self presentViewController:alertController animated:YES completion:nil];}// 确认框- (void)webView:(WKWebView *)webView runJavaScriptConfirmPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(BOOL result))completionHandler{ kFunLog UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@"提示" message:message?:@"" preferredStyle:UIAlertControllerStyleAlert]; [alertController addAction:([UIAlertAction actionWithTitle:@"取消" style:UIAlertActionStyleCancel handler:^(UIAlertAction * _Nonnull action) { completionHandler(NO); }])]; [alertController addAction:([UIAlertAction actionWithTitle:@"确认" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) { completionHandler(YES); }])]; [self presentViewController:alertController animated:YES completion:nil];}// 文本输入框- (void)webView:(WKWebView *)webView runJavaScriptTextInputPanelWithPrompt:(NSString *)prompt defaultText:(nullable NSString *)defaultText initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(NSString * _Nullable result))completionHandler{ kFunLog UIAlertController *alertController = [UIAlertController alertControllerWithTitle:prompt message:@"" preferredStyle:UIAlertControllerStyleAlert]; [alertController addTextFieldWithConfigurationHandler:^(UITextField * _Nonnull textField) { textField.text = defaultText; }]; [alertController addAction:([UIAlertAction actionWithTitle:@"完成" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) { completionHandler(alertController.textFields[0].text?:@""); }])]; [self presentViewController:alertController animated:YES completion:nil];}- (BOOL)webView:(WKWebView *)webView shouldPreviewElement:(WKPreviewElementInfo *)elementInfo API_AVAILABLE(ios(10.0)){ return YES;}