WKWebView 与 JS 的交互

279 阅读6分钟

一.WKWebView Framework

WKWebView的14个类与3个协议

==WKBackForwardList==: 之前访问过的 web 页面的列表,可以通过后退和前进动作来访问到。

==WKBackForwardListItem==: webview 中后退列表里的某一个网页。

==WKFrameInfo==: 包含一个网页的布局信息。

==WKNavigation==: 包含一个网页的加载进度信息。

==WKNavigationAction==: 包含可能让网页导航变化的信息,用于判断是否做出导航变化。

==WKNavigationResponse==: 包含可能让网页导航变化的返回内容信息,用于判断是否做出导航变化。

==WKPreferences==: 概括一个 webview 的偏好设置。

==WKProcessPool==: 表示一个 web 内容加载池。

==WKUserContentController==: 提供使用 JavaScript post 信息和注射 script 的方法。

==WKScriptMessage==: 包含网页发出的信息。

==WKUserScript==: 表示可以被网页接受的用户脚本。

==WKWebViewConfiguration==: 初始化 webview 的设置。

==WKWindowFeatures==: 指定加载新网页时的窗口属性。

==WKWebsiteDataStore==: 包含网页数据存储和查找。

==WKNavigationDelegate==: 提供了追踪主窗口网页加载过程和判断主窗口和子窗口是否进行页面加载新页面的相关方法。

==WKUIDelegate==: 提供用原生控件显示网页的方法回调。

==WKScriptMessageHandler==: 提供从网页中收消息的回调方法。

二.WKWebView 中的三个代理

  1. WKNavigationDelegate 该代理提供的方法,可以用来追踪加载过程(页面开始加载、加载完成、加载失败)、决定是否执行跳转。
// 页面开始加载时调用
- (void)webView:(WKWebView *)webView didStartProvisionalNavigation:(WKNavigation *)navigation;
// 当内容开始返回时调用
- (void)webView:(WKWebView *)webView didCommitNavigation:(WKNavigation *)navigation;
// 页面加载完成之后调用
- (void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation;
// 页面加载失败时调用
- (void)webView:(WKWebView *)webView didFailProvisionalNavigation:(WKNavigation *)navigation;

页面跳转的代理方法有三种,分为(收到跳转与决定是否跳转两种)

// 接收到服务器跳转请求之后调用
- (void)webView:(WKWebView *)webView didReceiveServerRedirectForProvisionalNavigation:(WKNavigation *)navigation;
// 在收到响应后,决定是否跳转
- (void)webView:(WKWebView *)webView decidePolicyForNavigationResponse:(WKNavigationResponse *)navigationResponse decisionHandler:(void (^)(WKNavigationResponsePolicy))decisionHandler;
// 在发送请求之前,决定是否跳转
- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler;

  1. WKUIDelegate
// 创建一个新的WebView
- (WKWebView *)webView:(WKWebView *)webView createWebViewWithConfiguration:(WKWebViewConfiguration *)configuration forNavigationAction:(WKNavigationAction *)navigationAction windowFeatures:(WKWindowFeatures *)windowFeatures;

剩下三个代理方法全都是与界面弹出提示框相关的,针对于web界面的三种提示框(警告框、确认框、输入框)分别对应三种代理方法。

// 界面弹出警告框
- (void)webView:(WKWebView *)webView runJavaScriptAlertPanelWithMessage:(NSString *)message initiatedByFrame:(void (^)())completionHandler;
// 界面弹出确认框
- (void)webView:(WKWebView *)webView runJavaScriptConfirmPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(BOOL result))completionHandler;
// 界面弹出输入框
- (void)webView:(WKWebView *)webView runJavaScriptTextInputPanelWithPrompt:(NSString *)prompt defaultText:(nullable NSString *)defaultText initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(NSString * __nullable result))completionHandler;
  1. WKScriptMessageHandle
    这个协议中包含一个必须实现的方法,这个方法是native与web端交互的关键,它可以直接将接收到的JS脚本转为OC或Swift对象。
// 从web界面中接收到一个脚本时调用
- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message;

三、使用WKWebView重写

这里我们和之前的界面做了一点改动,之前OC调用JS的时候是进行弹框处理,这里我在写的时候,很郁闷,方法可以调用过去,但是唯独js的alert方法调用没有效果,所以这里采用了输出到div的形式,并增加了一个clear按钮
WKWebView不支持nib文件,所以这里需要使用代码初始化并加载WebView

WKWebViewConfiguration *config = [[WKWebViewConfiguration alloc] init];
config.preferences.minimumFontSize = 18;
 
self.wkWebView = [[WKWebView alloc] initWithFrame:CGRectMake(0, 0, self.view.bounds.size.width, self.view.bounds.size.height/2) configuration:config];
[self.view addSubview:self.wkWebView];
 
 
NSString *filePath = [[NSBundle mainBundle] pathForResource:@"index" ofType:@"html"];
NSURL *baseURL = [[NSBundle mainBundle] bundleURL];
[self.wkWebView loadHTMLString:[NSString stringWithContentsOfFile:filePath encoding:NSUTF8StringEncoding error:nil] baseURL:baseURL];

OC端:

//1. JS调用OC 添加处理脚本
[userCC addScriptMessageHandler:self name:@"showMobile"];
[userCC addScriptMessageHandler:self name:@"showName"];
[userCC addScriptMessageHandler:self name:@"showSendMsg"];
 
// 在代理方法中处理对应事件
- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message {
    NSLog(@"%@",NSStringFromSelector(_cmd));
    NSLog(@"%@",message.body);
 
    if ([message.name isEqualToString:@"showMobile"]) {
        [self showMsg:@"我是下面的小红 手机号是:18870707070"];
    }
     
    if ([message.name isEqualToString:@"showName"]) {
        NSString *info = [NSString stringWithFormat:@"你好 %@, 很高兴见到你",message.body];
        [self showMsg:info];
    }
     
    if ([message.name isEqualToString:@"showSendMsg"]) {
        NSArray *array = message.body;
        NSString *info = [NSString stringWithFormat:@"这是我的手机号: %@, %@ !!",array.firstObject,array.lastObject];
        [self showMsg:info];
    }
}
 
// 2. native调用js
- (IBAction)btnClick:(UIButton *)sender {
    if (!self.wkWebView.loading) {
        if (sender.tag == 123) {
            [self.wkWebView evaluateJavaScript:@"alertMobile()" completionHandler:^(id _Nullable response, NSError * _Nullable error) {
                //TODO
                NSLog(@"%@ %@",response,error);
            }];
        }
         
        if (sender.tag == 234) {
            [self.wkWebView evaluateJavaScript:@"alertName('小红')" completionHandler:nil];
        }
         
        if (sender.tag == 345) {
            [self.wkWebView evaluateJavaScript:@"alertSendMsg('18870707070','周末爬山真是件愉快的事情')" completionHandler:nil];
        }
 
    } else {
        NSLog(@"the view is currently loading content");
    }
}

JS端:

function clear() {
    document.getElementById('mobile').innerHTML = ''
    document.getElementById('name').innerHTML = ''
    document.getElementById('msg').innerHTML = ''
}
 
//OC调用JS的方法列表
function alertMobile() {
    //这里已经调用过来了 但是搞不明白为什么alert方法没有响应
    //alert('我是上面的小黄 手机号是:13300001111')
    document.getElementById('mobile').innerHTML = '我是上面的小黄 手机号是:13300001111'
}
 
function alertName(msg) {
    //alert('你好 ' + msg + ', 我也很高兴见到你')
    document.getElementById('name').innerHTML = '你好 ' + msg + ', 我也很高兴见到你'
}
 
function alertSendMsg(num,msg) {
    //window.alert('这是我的手机号:' + num + ',' + msg + '!!')
    document.getElementById('msg').innerHTML = '这是我的手机号:' + num + ',' + msg + '!!'
}
 
//JS响应方法列表
function btnClick1() {
    window.webkit.messageHandlers.showMobile.postMessage(null)
}
 
function btnClick2() {
    window.webkit.messageHandlers.showName.postMessage('xiao黄')
}
 
function btnClick3() {
    window.webkit.messageHandlers.showSendMsg.postMessage(['13300001111', 'Go Climbing This Weekend !!!'])
}

彩虹项目中的具体实现过程

WKWebView与JS的交互

  • 代理 <WKUIDelegate,WKNavigationDelegate,WKScriptMessageHandler>
  • @property(nonatomic,strong)WKWebView *webView;

创建WebView

//懒加载 webView
- (WKWebView *)webView
{
    if (!_webView) {

        WKWebViewConfiguration *config = [[WKWebViewConfiguration alloc] init];
        
        //设置偏好设置
        config.preferences = [[WKPreferences alloc]init];
        
        //默认为0
        config.preferences.minimumFontSize = 0;
        
        //默认为YES
        config.preferences.javaScriptEnabled = YES;
        
        // iOS上默认为NO,表示不能自动通过窗口打开
        config.preferences.javaScriptCanOpenWindowsAutomatically = YES;
        
        
        //UserContentController提供javaScript向webView发送消息的方法
        WKUserContentController *userContent = [[WKUserContentController alloc] init];
        //添加消息处理
        [userContent addScriptMessageHandler:self name:@"longTapToSaveImage"];
        //将UserContentController设置到配置文件中
        config.userContentController = userContent;
        
        
        _webView = [[WKWebView alloc] initWithFrame:self.view.bounds configuration:config];
        _webView.allowsBackForwardNavigationGestures = NO;//打开左划回退功能
        
        _webView.UIDelegate = self;
        
        _webView.navigationDelegate = self;
        
//        _webView.configuration.processPool;
        
    }
    return _webView;
}
#pragma mark - WKNavigationDelegate
//开始加载时调用
// 1. 在发送请求之前,决定是否跳转
- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler{
//    CCLOG(@"goto:%@",webView.URL);

    decisionHandler(WKNavigationActionPolicyAllow);

}

-(void)webView:(WKWebView *)webView didStartProvisionalNavigation:(WKNavigation *)navigation
{
    // 可以在这里做正在加载的提示动画 然后在加载完成代理方法里移除动画
    [self.view showMaskProgressHUD] ;
}
//当内容开始返回时调用
-(void)webView:(WKWebView *)webView didCommitNavigation:(WKNavigation *)navigation
{
    
}
//页面加载完成之后调用
-(void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation
{
//    WKCookieSyncManager *cookiesManager = [WKCookieSyncManager sharedWKCookieSyncManager];
//    [cookiesManager setCookie];
    [self.view hiddenMaskProgressHUD] ;
    
//    NSHTTPCookieStorage *cookieStorage = [NSHTTPCookieStorage sharedHTTPCookieStorage];
//    self.cookies = [NSArray arrayWithArray:cookieStorage.cookies];
//
//    NSHTTPCookie *lostCookie = [cookieStorage.cookies lastObject];
//    self.lostCookie = lostCookie;
//
//    for (NSHTTPCookie *cookie in cookieStorage.cookies) {
//        NSLog(@"name = %@ value = %@",cookie.name,cookie.value);
//    }
//    //js函数
//    NSString *JSFuncString =
//    @"function setCookie(name,value,expires)\
//    {\
//    var oDate=new Date();\
//    oDate.setDate(oDate.getDate()+expires);\
//    document.cookie=name+'='+value+';expires='+oDate;\
//    }\
//    function getCookie(name)\
//    {\
//    var arr = document.cookie.match(new RegExp('(^| )'+name+'=([^;]*)(;|$)'));\
//    if(arr != null) return unescape(arr[2]); return null;\
//    }\
//    function delCookie(name)\
//    {\
//    var exp = new Date();\
//    exp.setTime(exp.getTime() - 1);\
//    var cval=getCookie(name);\
//    if(cval!=null) document.cookie= name + '='+cval+';expires='+exp.toGMTString();\
//    }";
//
//    //拼凑js字符串
//    NSMutableString *JSCookieString = JSFuncString.mutableCopy;
//    for (NSHTTPCookie *cookie in cookieStorage.cookies) {
//        NSString *excuteJSString = [NSString stringWithFormat:@"setCookie('%@', '%@', 1);", cookie.name, cookie.value];
//        [JSCookieString appendString:excuteJSString];
//    }
//    self.JSCookieString = JSCookieString;
    
}
// 页面加载失败时调用
- (void)webView:(WKWebView *)webView didFailProvisionalNavigation:(WKNavigation *)navigation withError:(nonnull NSError *)error
{
    [self.view hiddenMaskProgressHUD] ;
    if ([error.description containsString:@"https://back/"]) {
        [self.navigationController popViewControllerAnimated:YES];
    }else{
        [CC_NoticeView showError:error.description];
    }
    
}
// https 证书的通过
- (void)webView:(WKWebView *)webView didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential * _Nullable credential))completionHandler
{
    //    if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) {
    NSURLCredential *cred = [[NSURLCredential alloc]initWithTrust:challenge.protectionSpace.serverTrust];
    completionHandler(NSURLSessionAuthChallengeUseCredential, cred);
    //    }
}
//js调用OC我们可以根据name和body,进行处理。
- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message{
    
    NSLog(@"%@",NSStringFromSelector(_cmd));
    NSLog(@"%@",message);
    NSLog(@"%@",message.name);// 方法名
    NSLog(@"%@",message.body);// 传递的数据
    
    NSString *messageName = message.name;
    if ([@"longTapToSaveImage" isEqualToString:messageName]) {
        id messageBody = message.body;
        NSLog(@"%@",messageBody);
        //在这里写OC中要执行的方法
        [self saveImageUrl:(NSString *)messageBody];
    }
}
//移除响应
- (void)dealloc{
    [self.webView.configuration.userContentController removeScriptMessageHandlerForName:@"longTapToSaveImage"];
}
- (void)saveImageUrl:(NSString *)imageUrlStr{
    
    UIAlertController *alertController = [UIAlertController alertControllerWithTitle:nil message:@"是否保存这张图片" preferredStyle:UIAlertControllerStyleAlert];
    UIAlertAction *cancelAction = [UIAlertAction actionWithTitle:@"取消" style:UIAlertActionStyleCancel handler:^(UIAlertAction * _Nonnull action) {
        NSLog(@"取消按钮被点击");
    }];
    [alertController addAction:cancelAction];
    
    UIAlertAction *defaultAction = [UIAlertAction actionWithTitle:@"确定" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
        NSLog(@"确定按钮被点击");
        //开启一个https 请求图片的
//        NSURL *url = [NSURL URLWithString:imageUrlStr];
//        [NSURLConnection connectionWithRequest:[NSURLRequest requestWithURL:url] delegate:self];
//        [self requestImageData:imageUrlStr];
        
        NSData *data = [NSData dataWithContentsOfURL:[NSURL URLWithString:imageUrlStr]];
        UIImage *image = [UIImage imageWithData:data];
        
        if (image != nil) {
            [self imageSavedToPhotosAlbum:image];
        }
    }];
    
    [alertController addAction:defaultAction];
    
    [self presentViewController:alertController animated:YES completion:nil];
}

- (void)imageSavedToPhotosAlbum:(UIImage *)image{
    [[RBCustomPhotoAlbum shareInstance]saveImageIntoAlbum:image withNeedReminder:YES];
}

cookie的相关拓展