WKWebView和js互调方法的实现

简介: WKWebView和js互调方法的实现

由于我最近比较忙,先把代码贴出来,以后再增加说明。

本文以加载本地h5页面方式进行演示,若是想把它变成加载网址的方式。只需要把[self loadWebHTMLSring:@“AWWKTest”];换成[self loadWebHTMLSring:url];就可以,当然url为h5的地址;也可以在跳转页面直接调用loadWebHTMLSring方法记载地址。

测试的本地h5文件AWWKTest.html代码,就是把这个文件加入工程中就可以。

<!DOCTYPE html>
<html>
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=utf8">
            <script language="javascript">
            //JS执行window.webkit.messageHandlers.onMenuShareTimeline.postMessage(<messageBody>)
            function onMenuShareTimelineClick() {
              window.webkit.messageHandlers.share.postMessage({title:'测试分享的标题',content:'测试分享的内容',url:'https://github.com/mayin1992'});
            }
            
            //分享回调结果显示
            function shareResult(channel_id,share_channel,share_url){
              var content = channel_id+","+share_channel+","+share_url;
              alert(content);
              document.getElementById("returnValue").value = content;
            }
        </script>
    </head>
    <body>
      <h1>这是按钮调用</h1>
      <input type="btton" value="分享" onclick="onMenuShareTimelineClick()" />
      <h1>回调展示区</h1>
      <textarea id ="returnValue" type="value" rows="5" cols="40">
      </textarea>
    </body>
</html>

AWWKWebViewController.h文件代码:

#import <UIKit/UIKit.h>

@interface AWWKWebViewController : UIViewController

/** 是否显示Nav */
@property (nonatomic,assign) BOOL isNavHidden;

@property(nonatomic,assign) BOOL isHaveTelLoginPage;
//@property (nonatomic, copy) void(^removeTelLoginPageCallBack)(void);

/**
 加载纯外部链接网页

 @param string URL地址
 */
- (void)loadWebURLSring:(NSString *)string;

/**
 加载本地网页
 
 @param string 本地HTML文件名
 */
- (void)loadWebHTMLSring:(NSString *)string;

/**
 加载外部链接POST请求(注意检查 XFWKJSPOST.html 文件是否存在 )
 postData请求块 注意格式:@"\"username\":\"xxxx\",\"password\":\"xxxx\""
 
 @param string 需要POST的URL地址
 @param postData post请求块 
 */
- (void)POSTWebURLSring:(NSString *)string postData:(NSString *)postData;
@end

AWWKWebViewController.m文件代码。这个是js调用oc的方法,oc直接调用js的方法并传参数,也可以oc处理了一些事情后或用户点击了oc的按钮后调用js的方法,自己根据自己的需要可以自己改造。

#import "AWWKWebViewController.h"

#import <WebKit/WKWebView.h>
#import <WebKit/WebKit.h>
#import "AWJsWebEntity.h"

typedef enum{
    loadWebURLString = 0,
    loadWebHTMLString,
    POSTWebURLString,
}wkWebLoadType;

static void *WkwebBrowserContext = &WkwebBrowserContext;

@interface AWWKWebViewController ()<WKNavigationDelegate,WKUIDelegate,WKScriptMessageHandler,UINavigationControllerDelegate,UINavigationBarDelegate>

@property (nonatomic, strong) WKWebView *wkWebView;
//设置加载进度条
@property (nonatomic,strong) UIProgressView *progressView;
//仅当第一次的时候加载本地JS
@property(nonatomic,assign) BOOL needLoadJSPOST;
//网页加载的类型
@property(nonatomic,assign) wkWebLoadType loadType;
//保存的网址链接
@property (nonatomic, copy) NSString *URLString;
//保存POST请求体
@property (nonatomic, copy) NSString *postData;;
@property (nonatomic,strong) UIButton *rightButton;
@property (nonatomic,strong) UIButton *leftBtn;
@property (nonatomic,strong) NSMutableString *cookieValue;
@end

@implementation AWWKWebViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    [self loadWebHTMLSring:@"AWWKTest"];
    //加载web页面
    [self webViewloadURLType];
    
    //添加到主控制器上
    [self.view addSubview:self.wkWebView];
    
    //添加进度条
    [self.view addSubview:self.progressView];
    
    UIBarButtonItem *barButton = [[UIBarButtonItem alloc]initWithCustomView:self.rightButton];
    self.navigationItem.rightBarButtonItem = barButton;
    UIBarButtonItem *leftBarButton = [[UIBarButtonItem alloc]initWithCustomView:self.leftBtn];
    self.navigationItem.leftBarButtonItem = leftBarButton;
    
    @weakify(self);
    [RACObserve(self.wkWebView.scrollView, contentSize) subscribeNext:^(id x) {
        @strongify(self);
        NSLog(@"xxxxxx:%@",x);
        [self updateNavigationItems];
    }];
    
}

-(UIButton*)leftBtn
{
    if (!_leftBtn)
    {
        _leftBtn =[[UIButton alloc]initWithFrame:CGRectMake(10, 20, 44, 44)];
        [_leftBtn setImage:[UIImage imageNamed:@"back"] forState:UIControlStateNormal];
        [_leftBtn addTarget:self action:@selector(back) forControlEvents:UIControlEventTouchUpInside];
        _leftBtn.hidden=NO;
    }
    return _leftBtn;
}

-(void)back
{
    [self goToLastView];
}

-(void)goToLastView
{
    if ([self.wkWebView canGoBack])
    {
        [self.wkWebView goBack];
    }
    else
    {
        WKBackForwardListItem *currentItem = self.wkWebView.backForwardList.currentItem;
        NSString *url = [currentItem.URL.absoluteString stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
        if([url rangeOfString:telLoginHtml].location != NSNotFound)
        {
            [self.navigationController popViewControllerAnimated:YES];
        }
        else
        {
           [[AWNoticeView currentNotice] showErrorNotice:@"你已经在主页,不能后退了"];
        }
        //        [self.navigationController popViewControllerAnimated:YES];
    }
}

-(UIButton*)rightButton
{
    if (!_rightButton)
    {
        _rightButton =[[UIButton alloc]initWithFrame:CGRectMake(FULL_WIDTH - 10 - 44, 20, 44, 44)];
        [_rightButton setImage:[UIImage imageNamed:@"share"] forState:UIControlStateNormal];
        [_rightButton addTarget:self action:@selector(roadLoadClicked) forControlEvents:UIControlEventTouchUpInside];
        _rightButton.hidden=NO;
    }
    return _rightButton;
}

- (void)viewWillAppear:(BOOL)animated{
    [super viewWillAppear:animated];
//    if (self.removeTelLoginPageCallBack)
//    {
//        self.removeTelLoginPageCallBack();
//    }
    if (_isNavHidden == YES) {
        self.navigationController.navigationBarHidden = YES;
        //创建一个高20的假状态栏
        UIView *statusBarView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, self.view.bounds.size.width, 20)];
        //设置成绿色
        statusBarView.backgroundColor=[UIColor whiteColor];
        // 添加到 navigationBar 上
        [self.view addSubview:statusBarView];
    }else{
        self.navigationController.navigationBarHidden = NO;
    }
}


- (void)roadLoadClicked{
    [self.wkWebView reload];
}

-(void)customBackItemClicked{
    if (self.wkWebView.goBack) {
        [self.wkWebView goBack];
    }else{
        [self.navigationController popViewControllerAnimated:YES];
    }
}
-(void)closeItemClicked{
    [self.navigationController popViewControllerAnimated:YES];
}

#pragma mark ================ 加载方式 ================

- (void)webViewloadURLType{
    switch (self.loadType) {
        case loadWebURLString:{
            //创建一个NSURLRequest 的对象
//            NSURLRequest * Request_zsj = [NSURLRequest requestWithURL:[NSURL URLWithString:self.URLString] cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:10];
//            //加载网页
//            [self.wkWebView loadRequest:Request_zsj];
            
            NSMutableURLRequest * Request_zsj = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:self.URLString] cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:10];
//            if(self.cookieValue)
//            {
//                [Request_zsj addValue:self.cookieValue forHTTPHeaderField:@"Cookie"];
//            }
            
            //加载网页
            [self.wkWebView loadRequest:Request_zsj];
            
            break;
        }
        case loadWebHTMLString:{
            [self loadHostPathURL:self.URLString];
            break;
        }
        case POSTWebURLString:{
            // JS发送POST的Flag,为真的时候会调用JS的POST方法
            self.needLoadJSPOST = YES;
            //POST使用预先加载本地JS方法的html实现,请确认WKJSPOST存在
            [self loadHostPathURL:@"AWWKJSPOST.htm"];
            break;
        }
    }
}

- (void)loadHostPathURL:(NSString *)url{
    //获取JS所在的路径
    NSString *path = [[NSBundle mainBundle] pathForResource:url ofType:@"html"];
    //获得html内容
    NSString *html = [[NSString alloc] initWithContentsOfFile:path encoding:NSUTF8StringEncoding error:nil];
    //加载js
    [self.wkWebView loadHTMLString:html baseURL:[[NSBundle mainBundle] bundleURL]];
}

// 调用JS发送POST请求
- (void)postRequestWithJS {
    // 拼装成调用JavaScript的字符串
    NSString *jscript = [NSString stringWithFormat:@"post('%@',{%@});", self.URLString, self.postData];
    // 调用JS代码
    [self.wkWebView evaluateJavaScript:jscript completionHandler:^(id object, NSError * _Nullable error) {
    }];
}


- (void)loadWebURLSring:(NSString *)string{
    self.URLString = string;
    self.loadType = loadWebURLString;
}

- (void)loadWebHTMLSring:(NSString *)string{
    self.URLString = string;
    self.loadType = loadWebHTMLString;
}

- (void)POSTWebURLSring:(NSString *)string postData:(NSString *)postData{
    self.URLString = string;
    self.postData = postData;
    self.loadType = POSTWebURLString;
}

#pragma mark ================ 自定义返回/关闭按钮 ================

-(void)updateNavigationItems{
    WKBackForwardListItem *currentItem = self.wkWebView.backForwardList.currentItem;
    NSString *url = [currentItem.URL.absoluteString stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
    NSLog(@"current url:%@", url);
    self.navigationItem.leftBarButtonItem.customView.hidden = [AWGeneralFunction isHiddenWebBackButton:url];
}

#pragma mark ================ WKNavigationDelegate ================

//这个是网页加载完成,导航的变化
-(void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation{
    /*
     主意:这个方法是当网页的内容全部显示(网页内的所有图片必须都正常显示)的时候调用(不是出现的时候就调用),,否则不显示,或则部分显示时这个方法就不调用。
     */
    // 判断是否需要加载(仅在第一次加载)
    if (self.needLoadJSPOST) {
        // 调用使用JS发送POST请求的方法
        [self postRequestWithJS];
        // 将Flag置为NO(后面就不需要加载了)
        self.needLoadJSPOST = NO;
    }
    // 获取加载网页的标题
    self.title = self.wkWebView.title;
    
//    [[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:NO];
    [self updateNavigationItems];
//    NSArray*backList = self.wkWebView.backForwardList.backList;
//    NSLog(@"backList:%@, backList.count = %lld", backList, backList.count);
}

//开始加载
-(void)webView:(WKWebView *)webView didStartProvisionalNavigation:(WKNavigation *)navigation{
    //开始加载的时候,让加载进度条显示
    self.progressView.hidden = NO;
}

//内容返回时调用
-(void)webView:(WKWebView *)webView didCommitNavigation:(WKNavigation *)navigation{}

//服务器请求跳转的时候调用
-(void)webView:(WKWebView *)webView didReceiveServerRedirectForProvisionalNavigation:(WKNavigation *)navigation{}

//服务器开始请求的时候调用
- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler {
    NSString *strRequest = [navigationAction.request.URL.absoluteString stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
    NSLog(@"isHaveTelLoginPage:%d, strRequest:%@",[AWSingleObject sharedInstance].isHaveTelLoginPage, strRequest);
    if(isEmptyString(strRequest))
    {
        decisionHandler(WKNavigationActionPolicyCancel);//不允许跳转
        return;
    }
    else if([strRequest isEqualToString:@"about:blank"]) {//主页面加载内容
        decisionHandler(WKNavigationActionPolicyAllow);//允许跳转
    }

    switch (navigationAction.navigationType) {
        case WKNavigationTypeLinkActivated: {
            break;
        }
        case WKNavigationTypeFormSubmitted: {
            break;
        }
        case WKNavigationTypeBackForward: {
            break;
        }
        case WKNavigationTypeReload: {
            break;
        }
        case WKNavigationTypeFormResubmitted: {
            break;
        }
        case WKNavigationTypeOther: {
            break;
        }
        default: {
            break;
        }
    }
    [self updateNavigationItems];
    decisionHandler(WKNavigationActionPolicyAllow);
}

// 内容加载失败时候调用
-(void)webView:(WKWebView *)webView didFailProvisionalNavigation:(WKNavigation *)navigation withError:(NSError *)error{
    NSLog(@"页面加载超时");
}

//跳转失败的时候调用
-(void)webView:(WKWebView *)webView didFailNavigation:(WKNavigation *)navigation withError:(NSError *)error{}

//进度条
-(void)webViewWebContentProcessDidTerminate:(WKWebView *)webView{}

#pragma mark ================ WKUIDelegate ================

// 获取js 里面的提示
-(void)webView:(WKWebView *)webView runJavaScriptAlertPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(void))completionHandler{
    
    UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"提示" message:message preferredStyle:UIAlertControllerStyleAlert];
    [alert addAction:[UIAlertAction actionWithTitle:@"确定" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
        completionHandler();
    }]];
    
    [self presentViewController:alert animated:YES completion:NULL];
}


//KVO监听进度条
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
    
    if ([keyPath isEqualToString:NSStringFromSelector(@selector(estimatedProgress))] && object == self.wkWebView) {
        [self.progressView setAlpha:1.0f];
        BOOL animated = self.wkWebView.estimatedProgress > self.progressView.progress;
        [self.progressView setProgress:self.wkWebView.estimatedProgress animated:animated];
        
        // Once complete, fade out UIProgressView
        if(self.wkWebView.estimatedProgress >= 1.0f) {
            [UIView animateWithDuration:0.3f delay:0.3f options:UIViewAnimationOptionCurveEaseOut animations:^{
                [self.progressView setAlpha:0.0f];
            } completion:^(BOOL finished) {
                [self.progressView setProgress:0.0f animated:NO];
            }];
        }
    }
    else {
        [super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
    }
}

#pragma mark ================ WKScriptMessageHandler ================

//拦截执行网页中的JS方法
- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message{

    //服务器固定格式写法 window.webkit.messageHandlers.名字.postMessage(内容);
    //客户端写法 message.name isEqualToString:@"名字"]
    if ([message.name isEqualToString:@"share"]) {
        NSLog(@"%@", message.body);
        
        //oc调用js的shareResult方法并且传递参数
        [self.wkWebView evaluateJavaScript:@"shareResult('dd', 'aa', '56');" completionHandler:^(id _Nullable data, NSError * _Nullable error) {
            
            
        }];
    }
}

#pragma mark ================ 懒加载 ================

- (WKWebView *)wkWebView{
    if (!_wkWebView) {
        //设置网页的配置文件
        WKWebViewConfiguration * configuration = [[WKWebViewConfiguration alloc]init];

        //允许视频播放
        if (@available(iOS 9.0, *)) {
            configuration.allowsAirPlayForMediaPlayback = YES;
        } else {
            // Fallback on earlier versions
        }
        // 允许在线播放
        configuration.allowsInlineMediaPlayback = YES;
        // 允许可以与网页交互,选择视图
        configuration.selectionGranularity = YES;
        // web内容处理池
        configuration.processPool = [[WKProcessPool alloc] init];
        
        //自定义配置,一般用于 js调用oc方法(OC拦截URL中的数据做自定义操作)
        WKUserContentController * userContentController = [[WKUserContentController alloc]init];
        // 添加消息处理,注意:self指代的对象需要遵守WKScriptMessageHandler协议,结束时需要移除
        [userContentController addScriptMessageHandler:self name:@"share"];
        WKPreferences *preferences = [WKPreferences new];
        preferences.javaScriptCanOpenWindowsAutomatically = YES;
        configuration.preferences = preferences;
        
        // 是否支持记忆读取
        configuration.suppressesIncrementalRendering = YES;
        // 允许用户更改网页的设置
        configuration.userContentController = userContentController;
        _wkWebView = [[WKWebView alloc] initWithFrame:self.view.bounds configuration:configuration];
        _wkWebView.backgroundColor = [UIColor whiteColor];//[UIColor colorWithRed:240.0/255 green:240.0/255 blue:240.0/255 alpha:1.0];
        // 设置代理
        _wkWebView.navigationDelegate = self;
        _wkWebView.UIDelegate = self;
        //kvo 添加进度监控
        [_wkWebView addObserver:self forKeyPath:NSStringFromSelector(@selector(estimatedProgress)) options:0 context:WkwebBrowserContext];
        //开启手势触摸
        _wkWebView.allowsBackForwardNavigationGestures = YES;
        // 设置 可以前进 和 后退
        //适应你设定的尺寸
        [_wkWebView sizeToFit];
    }
    return _wkWebView;
}

- (UIProgressView *)progressView{
    if (!_progressView) {
        _progressView = [[UIProgressView alloc]initWithProgressViewStyle:UIProgressViewStyleDefault];
        if (_isNavHidden == YES) {
            _progressView.frame = CGRectMake(0, 20, self.view.bounds.size.width, 3);
        }else{
            _progressView.frame = CGRectMake(0, 64, self.view.bounds.size.width, 3);
        }
        // 设置进度条的色彩
        [_progressView setTrackTintColor:[UIColor colorWithRed:240.0/255 green:240.0/255 blue:240.0/255 alpha:1.0]];
        _progressView.progressTintColor = [UIColor colorWithHexString:@"0X057dff"];//[UIColor greenColor];
    }
    return _progressView;
}

-(void)viewWillDisappear:(BOOL)animated{
    [self.wkWebView.configuration.userContentController removeScriptMessageHandlerForName:@"share"];
    [self.wkWebView setNavigationDelegate:nil];
    [self.wkWebView setUIDelegate:nil];
}

//注意,观察的移除
-(void)dealloc{
    [self.wkWebView removeObserver:self forKeyPath:NSStringFromSelector(@selector(estimatedProgress))];
}

@end

iOS调用js方法:

-(void)sendJsSuccessMessageWithParams:(NSMutableDictionary *)params
{
    if(isCommonUnitEmptyDict(params))
    {
        return;
    }
    NSString *callBackStr = [NSString stringWithFormat:@"success(%@);", [params getJsonString]];
    [self callBackWithCallBackStr:callBackStr];//注意:函数里面这个要是去除换行符的json字符串
}
-(void)sendJsRechargeFailedMessage
{
    NSString *callBackStr = [NSString stringWithFormat:@"failed();"];
    [self callBackWithCallBackStr:callBackStr];
}

-(void)callBackWithCallBackStr:(NSString *)callBackStr
{
    NSLog(@"callBackStr :%@", callBackStr);
    if(isCommonUnitEmptyString(callBackStr))
    {
        return;
    }
    bg_dispatch_main_async_safe(^{
        //oc调用js的shareResult方法并且传递参数
        [self.wkWebView evaluateJavaScript:callBackStr completionHandler:^(id _Nullable data, NSError * _Nullable error) {
            
            
        }];
    });
}

这个是具体调用例子:

            NSMutableDictionary *params = [NSMutableDictionary dictionary];
            [params setValue:@"0.01" forKey:@"amount"];
            [params setValue:@"cardNameValue" forKey:@"cardName"];

            [self sendJsSuccessMessageWithParams:params];

由于最近项目正做根据微信端直接产生原生app,并保存微信app所有页面。踩了很多坑,特别是WKWebView的坑不是一般的深。我有其它文章解决:WKWebView和js互调方法参数传递及注意事项见文章《向js发送含有NSDictionary对象或NSArray对象的消息》。完美解决通过TZImagePickerController组件获取的多选图片对象及PHAsset数组,通过PHAsset直接获取到的图像被旋转问题及图片名称的获取见文章《通过PHAsset获取的图片上传后变大和图像被旋转90度问题完美解决方案》。WKWebView对图片地址的拦截及获取本地图片并显示问题见文章《WKWebView采用HybridNSURLProtocol协议拦截图片等资源预加载》。

目录
相关文章
|
3天前
|
JavaScript 前端开发
在 JavaScript 中,实现继承的方法有多种
【6月更文挑战第15天】JavaScript 继承常见方法包括:1) 原型链继承,利用原型查找,实例共享原型属性;2) 借用构造函数,避免共享,但方法不在原型上复用;3) 组合继承,结合两者优点,常用但有额外开销;4) ES6 的 class,语法糖,仍基于原型链,提供直观的面向对象编程。
12 7
|
28天前
|
前端开发 JavaScript
前端 js 经典:数组常用方法总结
前端 js 经典:数组常用方法总结
27 0
|
3天前
|
缓存 JavaScript 前端开发
js/javascript获取时间戳的5种方法
js/javascript获取时间戳的5种方法
|
3天前
|
JavaScript 前端开发 索引
深入了解JavaScript中的indexOf()方法:实现数组元素的搜索和索引获取
深入了解JavaScript中的indexOf()方法:实现数组元素的搜索和索引获取
|
3天前
|
JavaScript 前端开发
JS遍历数组和对象的方法有哪些
JS遍历数组和对象的方法有哪些
|
6天前
|
JavaScript 前端开发
深入解析JavaScript中的面向对象编程,包括对象的基本概念、创建对象的方法、继承机制以及面向对象编程的优势
【6月更文挑战第12天】本文探讨JavaScript中的面向对象编程,解释了对象的基本概念,如属性和方法,以及基于原型的结构。介绍了创建对象的四种方法:字面量、构造函数、Object.create()和ES6的class关键字。还阐述了继承机制,包括原型链和ES6的class继承,并强调了面向对象编程的代码复用和模块化优势。
17 0
|
12天前
|
JavaScript 前端开发
JavaScript删除数组中指定元素3种方法例子
JavaScript删除数组中指定元素3种方法例子
|
18天前
|
JavaScript 前端开发
js中改变this指向、动态指定函数 this 值的方法
js中改变this指向、动态指定函数 this 值的方法
|
18天前
|
JavaScript 前端开发 开发者
JavaScript 中程序异常处理的方法,提升代码运行的健壮性
JavaScript 中程序异常处理的方法,提升代码运行的健壮性
|
18天前
|
JavaScript
分享经典面试题:JS数组去重的多种方法
分享经典面试题:JS数组去重的多种方法