iOS进阶 - 是时候学习WKWebView了

来源:互联网 发布:黄连上清丸河南淘宝 编辑:程序博客网 时间:2024/05/01 09:11

作者: o翻滚的牛宝宝o

地址: 
http://www.jianshu.com/p/4fa8c4eb1316


抱歉: 最近工作和生活上都比较忙, 有半个月左右没有给大家推送新文章了, 公众号后台也有很多粉丝留言询问情况, 真的非常抱歉, 现在开始每周至少会更新两篇高质量, 有实用价值的文章. 也欢迎大家投稿.


优化:以后的文章小编会尽量调整代码缩进注释颜色以及关键词颜色,虽有点费时间,但希望这样能为您带来更好的阅读体验;


1前言


Xcode8发布以后,编译器开始不支持IOS7,所以很多应用在适配IOS10之后都不在适配IOS7了,其中包括了很多大公司,网易新闻,滴滴出行等。因此,我们公司的应用也打算淘汰IOS7。


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

  • 更多的支持HTML5的特性

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

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

  • Safari相同的JavaScript引擎

  • 占用更少的内存

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


1基本使用方法


WKWebView有两个delegate,WKUIDelegate WKNavigationDelegateWKNavigationDelegate主要处理一些跳转、加载处理操作,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();

}


3OC与JS交互


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


JS调用OC方法


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

@interface ViewController ()<WKUIDelegate,WKNavigationDelegate,WKScriptMessageHandler>{

      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 ()<WKUIDelegate,WKNavigationDelegate,WKScriptMessageHandler>{

      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 <UIKit/UIKit.h>#import <WebKit/WebKit.h>@protocol WKDelegate <NSObject>

- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message;

@end


@interface WKDelegateController : UIViewController <WKScriptMessageHandler>

@property (weak , nonatomic) id<WKDelegate> 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代码:

<html><head>

  <script>function say()

{

//前端需要用 window.webkit.messageHandlers.注册的方法名.postMessage({body:传输的数据} 来给native发送消息

  window.webkit.messageHandlers.sayhello.postMessage({body: 'hello world!'});

}

</script></head>

      <body>

          <h1>hello world</h1>

          <button onclick="say()">say hello</button>

      </body>

</html>


打印出的log:

name:sayhello

body:{

  body = "hello world!";

}

frameInfo:<WKFrameInfo: 0x7f872060ce20; isMainFrame = YES; request = <NSMutableURLRequest: 0x618000010a30> { URL: http://www.test.com/ }>


注意点

  • addScriptMessageHandler要和removeScriptMessageHandlerForName配套出现,否则会造成内存泄漏。

  • 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代码同上。


4WebViewJavascriptBridge


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


  • cocoaPods: pod 'WebViewJavascriptBridge', '~> 5.0.5'

  • github地址:https://github.com/marcuswestin/WebViewJavascriptBridge


主要方法如下:

//初始化方法

+ (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<WKNavigationDelegate>)webViewDelegate;


基本的实现方法和上面写的差不多,就是封装了一下,有兴趣的童鞋可以自己pod下来使用。

2 0
原创粉丝点击