WKWebView是苹果在iOS 8之后推出的框架,关于它比webview的优势这里就不讲了。主要说一下与JS交互的问题,其实WKWebView已经内置了JS与OC的互调、传值等方法,使用起来也非常方便,下面就来细细的探讨一下以及自己遇到过的坑...
一、导入相关头文件、设置相关代理和属性
调用相册楼主用的是: TZImagePickerController框架, 如果你用的系统或其他的,直接替换就行
#import "WebViewController.h" #import "webkit/webkit.h" #define WS(weakSelf) __weak __typeof(&*self) weakSelf = self #define kWidth [UIScreen mainScreen].bounds.size.width #define kHeight [UIScreen mainScreen].bounds.size.height @interface WebViewController ()<WKNavigationDelegate,WKScriptMessageHandler,WKUIDelegate,TZImagePickerControllerDelegate,UIImagePickerControllerDelegate, UINavigationControllerDelegate> @property(nonatomic,strong)WKWebView *webView; @property (nonatomic, strong) UIProgressView *progressView; //进度条加载 @property (nonatomic, strong) NSMutableArray *imgArray;// 图片数组 @property (nonatomic,strong)WKWebViewConfiguration *configuration; @end
二、WKWebView初始化
- (void)viewDidLoad { [super viewDidLoad]; // 配置网页的配置文件 WKWebViewConfiguration *configuration = [[WKWebViewConfiguration alloc] init]; WKPreferences *preference = [[WKPreferences alloc]init]; configuration.preferences = preference; configuration.selectionGranularity = YES; //允许与网页交互 // webView初始化 self.webView = [[WKWebView alloc] initWithFrame:CGRectMake(0, 0, kWidth, kHeight) configuration:configuration]; self.view.backgroundColor = [UIColor whiteColor]; self.webView.UIDelegate = self; self.webView.navigationDelegate = self; _webView.allowsBackForwardNavigationGestures = YES; //二级网页是否可以左划返回 // 楼主这里隐藏了原生导航栏,加载的 H5导航栏,下拉不允许导航栏跟着下拉,设置弹簧效果为NO即可 _webView.scrollView.bounces = NO; [self.webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:self.urlStr]]]; [self.view addSubview:self.webView]; }
接下来才是重点!!!
一、JS调OC,JS给OC传值
// JS调OC,需要 H5端统一如下写法,方法名就是交互的名称,数据就是JS给OC传的值
window.webkit.messageHandlers.<方法名>.postMessage(<数据>)
注意:楼主遇到的第一个坑:如果JS给OC传值为空,必须写成: postMessage(null),如果什么都不写,方法是调不通的。
1、在viewWillAppear中配置, addScriptMessageHandler name: "这里就是JS的方法,方法名必须统一"
-(void)viewWillAppear:(BOOL)animated{ [super viewWillAppear:animated]; #pragma mark ======= JS事件 ======== //因为楼主的导航栏是隐藏了的,显示的是H5的导航栏,所以要调用返回按钮到主页面 [self.webView.configuration.userContentController addScriptMessageHandler:self name:@"back"]; //拍照 [self.webView.configuration.userContentController addScriptMessageHandler:self name:@"camera"]; //从相册选取 [self.webView.configuration.userContentController addScriptMessageHandler:self name:@"album"]; //H5请求接口时,调用原生的指示器加载 [self.webView.configuration.userContentController addScriptMessageHandler:self name:@"loadIndicator"]; //接口请求完成,隐藏指示器 [self.webView.configuration.userContentController addScriptMessageHandler:self name:@"hiddenIndicator"]; }
楼主遇到的第二个坑:配置完后必须在viewWillDisappear
中 remove,否则会造成循环引用,导致crash
- (void)viewWillDisappear:(BOOL)animated { [super viewWillDisappear:animated]; // 这里要记得移除handlers [self.webView.configuration.userContentController removeScriptMessageHandlerForName:@"back"]; [self.webView.configuration.userContentController removeScriptMessageHandlerForName:@"camera"]; [self.webView.configuration.userContentController removeScriptMessageHandlerForName:@"album"]; [self.webView.configuration.userContentController removeScriptMessageHandlerForName:@"loadIndicator"]; [self.webView.configuration.userContentController removeScriptMessageHandlerForName:@"hiddenIndicator"]; }
2、实现 WKScriptMessageHandler 协议
//WKScriptMessageHandler协议方法 - (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message { // message.body 即为JS向OC传的值 id body = message.body; NSLog(@"=== %@", body); if ([message.name isEqualToString:@"back"]) { //返回到首页 [self.navigationController popViewControllerAnimated:YES]; }if ([message.name isEqualToString:@"camera"]) { //拍照 [self takePhotos]; } if ([message.name isEqualToString:@"album"]) { //从相册选取 [self localPhotos]; }if ([message.name isEqualToString:@"loadIndicator"]) { //加载指示器 [SVProgressHUD show]; }if ([message.name isEqualToString:@"hiddenIndicator"]) { //隐藏指示器 [SVProgressHUD dismiss]; } }
以上就是JS调OC,JS向OC传值...
二、OC调JS,OC向JS传值
实现该方法即可:[webView evaluateJavaScript:<> completionHandler:^(id _Nullable response, NSError * _Nullable error){}];
楼主这里举三个例子:
1: webview加载完成前,将用户信息传给js
2: webview加载完成,将相关信息传给js
3: 调用相册或相机时,将选择的图片请求后台接口,后台返回图片地址,将该地址回传给H5,H5将图片显示到页面上
第一个例子: webView加载完成前传值
因为 evaluateJavaScript 方法默认是在加载完成后调用,所以直接在页面开始加载中调用是传不过去的,这个时候怎么办呢?我们可以让js端写两个方法, 第一个方法是js端开始向oc端发起信息需求的方法名,当oc端收到该方法名的时候,就去调用js端第二个获取传值的方法,把信息传递过去。
先让JS端写个方法调OC,OC实现方法后在这个方法内部给JS传值
-(void)viewWillAppear:(BOOL)animated{ [super viewWillAppear:animated]; //发起信息需求 [self.webView.configuration.userContentController addScriptMessageHandler:self name:@"getUserInfo"]; }
在WKScriptMessageHandler协议中,实现该方法,然后在方法内部给JS传值
- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message { id body = message.body; NSLog(@"=== %@", body); if ([message.name isEqualToString:@"getUserInfo"]) { NSLog(@"getUserInfo"); //在这里给JS传值 NSDictionary *dict = @{@"id":@"123", @"name":@"lisi"}; //转为json NSData *data = [NSJSONSerialization dataWithJSONObject:dict options:(NSJSONWritingPrettyPrinted) error:nil]; NSString *jsonStr = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; NSLog(@"jsonStr == %@",jsonStr); //给js传值,获取用户信息 NSString *inputValueJS = [NSString stringWithFormat:@"getCurrentUser(‘%@‘)", jsonStr]; [webView evaluateJavaScript:inputValueJS completionHandler:^(id _Nullable response, NSError * _Nullable error) { //打印如果error为null,表示已调通 NSLog(@"value: %@ error: %@", response, error); }]; } }
注意:以上就是在Webview加载完成前传值,如果打印没报错,证明传参成功,如果web端没收到,让他把获取到值的方法写到页面中即可。
第二个例子: webView加载完成,传值给js
- (void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation{ NSDictionary *dict = @{@"id":@"123", @"name":@"lisi"}; //转为json NSData *data = [NSJSONSerialization dataWithJSONObject:dict options:(NSJSONWritingPrettyPrinted) error:nil]; NSString *jsonStr = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; NSLog(@"jsonStr == %@",jsonStr); //给js传值,获取用户信息 NSString *inputValueJS = [NSString stringWithFormat:@"getCurrentUser(‘%@‘)", jsonStr]; [webView evaluateJavaScript:inputValueJS completionHandler:^(id _Nullable response, NSError * _Nullable error) { //打印如果error都为null,表示已调通 NSLog(@"value: %@ error: %@", response, error); }]; }
第三个例子: 传图片地址给js,js拿到后显示图片
1:拍照事件
//拍照 - (void)takePhotos{ UIImagePickerControllerSourceType sourceType = UIImagePickerControllerSourceTypeCamera; if ([UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypeCamera]) { UIImagePickerController *picker = [[UIImagePickerController alloc]init]; picker.delegate = self; picker.allowsEditing = YES; picker.sourceType = sourceType; [self.navigationController presentViewController:picker animated:YES completion:^{ NSLog(@"OK"); }]; } else { NSLog(@"模拟其中无法打开照相机,请在真机中使用"); } }
1.1:将拍的照片请求上传图片接口,成功返回图片地址,并传值给H5
// 相机 - (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info{ [picker dismissViewControllerAnimated:YES completion:^{}]; [self.imgArray removeAllObjects]; UIImage *image = [info objectForKey:UIImagePickerControllerOriginalImage]; [self.imgArray addObject:image]; if (self.imgArray.count) { //这里开始写请求上传图片接口的代码 //请求成功,获取返回的图片地址,如果是数组,将数组转换为字符串 NSString *urlStr = [[数组] componentsJoinedByString:@""]; // 然后向js传图片地址: NSString *inputValue = [NSString stringWithFormat:@"getPhotoCallback(‘%@‘)",urlStr]; [self.webView evaluateJavaScript:inputValue completionHandler:^(id _Nullable response, NSError * _Nullable error) { NSLog(@"value图片: %@ error: %@", response, error); }]; } }
2: 从相册中选取照片
#pragma mark TZImagePickerControllerDelegate #pragma mark -- 从相册中选择照片 -(void)localPhotos{ TZImagePickerController *imagePickerVc = [[TZImagePickerController alloc] initWithMaxImagesCount:1 delegate:self]; [self.navigationController presentViewController:imagePickerVc animated:YES completion:nil]; } /// 用户点击了取消 - (void)imagePickerControllerDidCancel:(TZImagePickerController *)picker { [self dismissViewControllerAnimated:YES completion:nil]; }
2.2:将相册中选取的照片请求上传图片接口,成功返回图片地址,并传值给H5
// 相册 /// 用户选择好了图片,如果assets非空,则用户选择了原图。 - (void)imagePickerController:(TZImagePickerController *)picker didFinishPickingPhotos:(NSArray *)photos sourceAssets:(NSArray *)assets{ [self.imgArray removeAllObjects]; for (int i = 0; i < photos.count; i++) { UIImage *image = photos[i]; [self.imgArray addObject:image]; } if (self.imgArray.count) { //这里开始写请求上传图片接口的代码 //请求成功,获取返回的图片地址,如果是数组,将数组转换为字符串 NSString *urlStr = [[数组] componentsJoinedByString:@""]; // 然后向js传图片地址: NSString *inputValue = [NSString stringWithFormat:@"getPhotoCallback(‘%@‘)",urlStr]; [self.webView evaluateJavaScript:inputValue completionHandler:^(id _Nullable response, NSError * _Nullable error) { NSLog(@"value图片: %@ error: %@", response, error); }]; } }
注意: getPhotoCallback即为调用的方法名,后面传值格式必须为:(‘‘), 这里遇到了第三个坑,如果方法名写为: 名称.名称 (例如:hello. getPhotoCallback),这种是调不通的,可以写成hello_getPhotoCallback的形式,一般的话最好还是定义一个完整的名称。刚开始这个问题卡了比较久,一直调不通,在此记录一下.....
以上就是OC调JS,OC给JS传值的分享!!!
最后分享一下:
WKWebView进度条加载....
在 viewDidLoad 中注册进度条监听
- (void)viewDidLoad { [super viewDidLoad]; //添加进度条监听 [self.webView addObserver:self forKeyPath:@"estimatedProgress" options:NSKeyValueObservingOptionNew context:nil]; }
开始加载网页
- (void)webView:(WKWebView *)webView didStartProvisionalNavigation:(WKNavigation *)navigation { //显示 self.progressView.hidden = NO; self.progressView.transform = CGAffineTransformMakeScale(1.0f, 1.5f); [self.view bringSubviewToFront:self.progressView]; }
加载完成
- (void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation{ //隐藏 self.progressView.hidden = YES; }
加载失败
// 页面加载失败时调用 - (void)webView:(WKWebView *)webView didFailProvisionalNavigation:(WKNavigation *)navigation withError:(NSError *)error { self.progressView.hidden = YES; }
页面跳转失败
//页面跳转失败时调用 - (void)webView:(WKWebView *)webView didFailNavigation:(WKNavigation *)navigation withError:(NSError *)error { if(error.code==NSURLErrorCancelled) { [self webView:webView didFinishNavigation:navigation]; }else{ self.progressView.hidden = YES; } }
progressView懒加载
- (UIProgressView *)progressView { if (!_progressView) { _progressView = [[UIProgressView alloc] initWithFrame:CGRectMake(0, Height_StatusBar, kWidth, 2)]; _progressView.backgroundColor = [UIColor blueColor]; _progressView.transform = CGAffineTransformMakeScale(1.0f, 1.5f); _progressView.progressTintColor = [UIColor blueColor]; [self.view addSubview:self.progressView]; } return _progressView; }
添加监听观察者
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context { if ([keyPath isEqualToString:@"estimatedProgress"]) { self.progressView.progress = self.webView.estimatedProgress; if (self.progressView.progress == 1) { WS(weakSelf); [UIView animateWithDuration:0.25f delay:0.3f options:UIViewAnimationOptionCurveEaseOut animations:^ { weakSelf.progressView.transform = CGAffineTransformMakeScale(1.0f, 1.4f); } completion:^(BOOL finished) { weakSelf.progressView.hidden = YES; }]; } } }
最后别忘记 removeObserver
-(void)dealloc{ [self.webView removeObserver:self forKeyPath:@"estimatedProgress"]; }
结语:
以上就是有关 WKWebView与JS交互、进度条加载的分享
如有问题请下方留言指正!
如有帮助请?支持一下 ?
Demo地址: https://github.com/zhwIdea/WKWebViewAndJS
————————————————
原文链接:https://blog.csdn.net/super_man_ww/article/details/101370345