iOS oc与js交互详解 之 WKWebView

Xcode8发布以后,编译器开始不支持iOS7,所以很多应用在适配iOS10之后都不在适配iOS7了,其中包括了很多大公司,网易新闻,滴滴出行等。

 

支持到iOS8,第一个要改的自然是用WKWebView替换原来的UIWebView。WKWebView有很多明显优势:

   1、更多的支持HTML5的特性

   2、官方宣称的高达60fps的滚动刷新率以及内置手势

   3、将UIWebViewDelegate与UIWebView拆分成了14类与3个协议,以前很多不方便实现的功能得以实现。文档

   4、Safari相同的JavaScript引进

   5、占用更少的内存

 

UIWebView

 

 

WKWebView

 

 

因此,使用WkWebview替换UIWebView还是很有必要的。

 

基本使用方法

WKWebView有两个delegate, WKUIDelegate 和 WKNavigationDelegate。WKNavigationDelegate主要处理一些跳转、加载处理操作,WKUIDelegate主要处理JS脚本,确认框,警告框等。因此 WKNavigationDelegate 更加常用。

 

比较常用的方法:

#pragma mark - lifeCircle
- (void)viewDidLoad {
    [super viewDidLoad];
    webView = [[WKWebView alloc]init];
    [self.view addSubview:webView];
    [webView mas_makeConstraints:^(MASConstraintMaker *make) {
        make.left.equalTo(self.view);
        make.right.equalTo(self.view);
        make.top.equalTo(self.view);
        make.bottom.equalTo(self.view);
    }];
    webView.UIDelegate = self;
    webView.navigationDelegate = self;
    [webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"http://www.baidu.com"]]];
 
}
 
 
#pragma mark - 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{
 
    NSLog(@"%@",navigationResponse.response.URL.absoluteString);
    //允许跳转
    decisionHandler(WKNavigationResponsePolicyAllow);
    //不允许跳转
    //decisionHandler(WKNavigationResponsePolicyCancel);
}

 

// 在发送请求之前,决定是否跳转
- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler{
 
     NSLog(@"%@",navigationAction.request.URL.absoluteString);
    //允许跳转
    decisionHandler(WKNavigationActionPolicyAllow);
    //不允许跳转
    //decisionHandler(WKNavigationActionPolicyCancel);
}
 
#pragma mark - WKUIDelegate
// 创建一个新的WebView
- (WKWebView *)webView:(WKWebView *)webView createWebViewWithConfiguration:(WKWebViewConfiguration *)configuration forNavigationAction:(WKNavigationAction *)navigationAction windowFeatures:(WKWindowFeatures *)windowFeatures{
    return [[WKWebView alloc]init];
}
 
// 输入框
- (void)webView:(WKWebView *)webView runJavaScriptTextInputPanelWithPrompt:(NSString *)prompt defaultText:(nullable NSString *)defaultText initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(NSString * __nullable result))completionHandler{
    completionHandler(@"http");
}
 
// 确认框
- (void)webView:(WKWebView *)webView runJavaScriptConfirmPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(BOOL result))completionHandler{
    completionHandler(YES);
}
 
// 警告框
- (void)webView:(WKWebView *)webView runJavaScriptAlertPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(void))completionHandler{
    NSLog(@"%@",message);
    completionHandler();
}
 

OC与JS交互

WKWebview提供了API实现js交互,不需要借助JavaScriptCore或者webJavaScriptBridge。使用WKUserContentController实现js  native 交互。简单的说就是先注册约定好的方法,然后再调用。

 

js调用oc方法

oc代码(有误,内存不释放):

 

@interface ViewController (){

    WKWebView * webView;
    WKUserContentController* userContentController;
}

@end
@implementation ViewController
#pragma mark - lifeCircle - (void)viewDidLoad {     [super viewDidLoad];     //配置环境     WKWebViewConfiguration * configuration = [[WKWebViewConfiguration alloc]init];     userContentController =[[WKUserContentController alloc]init];     configuration.userContentController = userContentController;     webView = [[WKWebView alloc]initWithFrame:CGRectMake(0, 0, 100, 100) configuration:configuration];     //注册方法     [userContentController addScriptMessageHandler:self  name:@"sayhello"];//注册一个name为sayhello的js方法     [self.view addSubview:webView];     [webView mas_makeConstraints:^(MASConstraintMaker *make) {         make.left.equalTo(self.view);         make.right.equalTo(self.view);         make.top.equalTo(self.view);         make.bottom.equalTo(self.view);     }];     webView.UIDelegate = self;     webView.navigationDelegate = self;     [webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"http://www.test.com"]]]; } - (void)dealloc{     //这里需要注意,前面增加过的方法一定要remove掉。     [userContentController removeScriptMessageHandlerForName:@"sayhello"]; } #pragma mark - WKScriptMessageHandler - (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message{     NSLog(@"name:%@\\\\n body:%@\\\\n frameInfo:%@\\\\n",message.name,message.body,message.frameInfo); } @end


上面的oc代码如果认证测试一下就会发现dealloc并不会执行,这样肯定是不行的,会造成内存泄漏。原因是
[userContentController addScriptMessageHandler:self  name:@"sayhello"]; 这句代码造成无法释放内存。(ps:试了下用weak指针还是不能释放,不知道是什么原因。)因此还需要进一步改进,正确的写法是用一个新的controller来处理,新的controller再绕用delegate绕回来。

oc(代码正确写法):

@interface ViewController (){
    WKWebView * webView;
    WKUserContentController* userContentController;
}
@end
@implementation ViewController
#pragma mark - lifeCircle
- (void)viewDidLoad {
    [super viewDidLoad];
    //配置环境
    WKWebViewConfiguration * configuration = [[WKWebViewConfiguration alloc]init];
    userContentController =[[WKUserContentController alloc]init];
    configuration.userContentController = userContentController;
    webView = [[WKWebView alloc]initWithFrame:CGRectMake(0, 0, 100, 100) configuration:configuration];
    //注册方法
    WKDelegateController * delegateController = [[WKDelegateController alloc]init];
    delegateController.delegate = self;
 
    [userContentController addScriptMessageHandler:delegateController  name:@"sayhello"];
 
    [self.view addSubview:webView];
    [webView mas_makeConstraints:^(MASConstraintMaker *make) {
        make.left.equalTo(self.view);
        make.right.equalTo(self.view);
        make.top.equalTo(self.view);
        make.bottom.equalTo(self.view);
    }];
    webView.UIDelegate = self;
    webView.navigationDelegate = self;
    [webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"http://www.test.com"]]];
}
- (void)dealloc{
    //这里需要注意,前面增加过的方法一定要remove掉。
    [userContentController removeScriptMessageHandlerForName:@"sayhello"];
}
#pragma mark - WKScriptMessageHandler
- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message{
    NSLog(@"name:%@\\\\n body:%@\\\\n frameInfo:%@\\\\n",message.name,message.body,message.frameInfo);
}
@end
 
WKDelegateController代码:
 
#import #import @protocol WKDelegate 
- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message;
 
@end
 
@interface WKDelegateController : UIViewController 
@property (weak , nonatomic) id delegate;
 
@end

.m代码:
#import "WKDelegateController.h"
 
@interface WKDelegateController ()
 
@end
 
@implementation WKDelegateController
 
- (void)viewDidLoad {
    [super viewDidLoad];
}
 
- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message{
    if ([self.delegate respondsToSelector:@selector(userContentController:didReceiveScriptMessage:)]) {
        [self.delegate userContentController:userContentController didReceiveScriptMessage:message];
    }
}
@end

h5代码:

  function say()
{
//前端需要用 window.webkit.messageHandlers.注册的方法名.postMessage({body:传输的数据} 来给native发送消息
    window.webkit.messageHandlers.sayhello.postMessage({body: 'hello world!'});
}            hello world        say hello

打印出的log:

name:sayhello
body:{
    body = "hello world!";
}
 frameInfo:<wkframeinfo: 0x7f872060ce20; ismainframe = yes; request =      { url: http: www.test.com=""  }=""></wkframeinfo: 0x7f872060ce20; ismainframe = yes; request =      { url: http:>

 



注意点:
1、addScriptMessageHandler 要和 removeScriptMessageHandlerForName 配套出现,否则会造成内存泄漏。
2、h5只能传一个参数,如果需要多个参数就需要用字典或者json组装。

oc调用js方法
代码如下:
- (void)webView:(WKWebView *)tmpWebView didFinishNavigation:(WKNavigation *)navigation{
 
    //say()是JS方法名,completionHandler是异步回调block
    [webView evaluateJavaScript:@"say()" completionHandler:^(id _Nullable result, NSError * _Nullable error) {
        NSLog(@"%@",result);
    }];
 
}

 


 


h5代码同上。


 


 

WebViewJavascriptBridge

一般来说,一个好的UI总有一个大神会开发出一个好的第三方封装框架。WebViewJavascriptBridge的作者也做了一套支持WKWebView与JS交互的第三方框架:WKWebViewJavascriptBridge。

         

 

主要方法如下:

     //初始化方法

+ (instancetype)bridgeForWebView:(WKWebView*)webView;
+ (void)enableLogging;
 
//注册函数名
- (void)registerHandler:(NSString*)handlerName handler:(WVJBHandler)handler;
 
//调用函数名
- (void)callHandler:(NSString*)handlerName;
- (void)callHandler:(NSString*)handlerName data:(id)data;
- (void)callHandler:(NSString*)handlerName data:(id)data responseCallback:(WVJBResponseCallback)responseCallback;
 
//重置
- (void)reset;
 
//设置WKNavigationDelegate
- (void)setWebViewDelegate:(id)webViewDelegate;
 
基本的实现方法和上面写的差不多,就是封装一下,有兴趣的童鞋可以自己pod下来使用。
 

补充

前不久把项目中的 UIWebView 更新到 WkWebView,解决了一大堆问题,但是也遗留了一大堆问题,比方说cookie。

 

以前 UIWebView 会自动去 NSHTTPCookieStorage 中读取 cookie,但是 WKWebView 并不会去读取,因此导致 cookie 丢失以及一系列问题,解决方式就是在 request 中手动帮其添加上。

mainWebView.UIDelegate = self;
mainWebView.navigationDelegate = self;
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:@"http://www.test.com"]];
[request addValue:[self readCurrentCookieWithDomain:@"http://www.test.com/"] forHTTPHeaderField:@"Cookie"];
[mainWebView loadRequest:request];
 
- (NSString *)readCurrentCookieWithDomain:(NSString *)domainStr{
    NSHTTPCookieStorage*cookieJar = [NSHTTPCookieStorage sharedHTTPCookieStorage];
    NSMutableString * cookieString = [[NSMutableString alloc]init];
    for (NSHTTPCookie*cookie in [cookieJar cookies]) {
        [cookieString appendFormat:@"%@=%@;",cookie.name,cookie.value];
    }
 
//删除最后一个“;”
    [cookieString deleteCharactersInRange:NSMakeRange(cookieString.length - 1, 1)];
    return cookieString;
}
 
但是这只能解决第一次进入的 cookie 问题,如果页面内跳转(a标签等)还是取不到cookie,因此还要再加代码

 

- (void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation {
 
   //取出cookie
    NSHTTPCookieStorage *cookieStorage = [NSHTTPCookieStorage sharedHTTPCookieStorage];
    //js函数
    NSString *JSFuncString =
@"function setCookie(name,value,expires)\
    {\
    var oDate=new Date();\
    oDate.setDate(oDate.getDate()+expires);\
document.cookie=name+'='+value+';expires='+oDate+';path=/'\
    }\
    function getCookie(name)\
    {\
    var arr = document.cookie.match(new RegExp('(^| )'+name+'=({FNXX==XXFN}*)(;|$)'));\
    if(arr != nullreturn 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];
    }
    //执行js
    [webView evaluateJavaScript:JSCookieString completionHandler:nil];
 
}

 

 
posted @ 2017-09-13 17:01  LiZeYuBlog  阅读(940)  评论(0编辑  收藏  举报