iOS WKWebView与H5交互,JS调OC传值、OC调JS传值、进度条加载等(干货满满)

WKWebView是苹果在iOS 8之后推出的框架,关于它比webview的优势这里就不讲了。主要说一下与JS交互的问题,其实WKWebView已经内置了JS与OC的互调、传值等方法,使用起来也非常方便,下面就来细细的探讨一下以及自己遇到过的坑...

首先来看下WKWebView的初始化相关设置:

一、导入相关头文件、设置相关代理和属性

调用相册楼主用的是: 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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 160,108评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,699评论 1 296
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,812评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,236评论 0 213
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,583评论 3 288
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,739评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,957评论 2 315
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,704评论 0 204
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,447评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,643评论 2 249
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,133评论 1 261
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,486评论 3 256
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,151评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,108评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,889评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,782评论 2 277
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,681评论 2 272

推荐阅读更多精彩内容