Xcode8發佈以後,編譯器開始不支持IOS7,所以很多應用在適配IOS10之後都不在適配IOS7了,其中包括了很多大公司,網易新聞,滴滴出行等。因此,我們公司的應用也打算淘汰IOS7。
支持到IOS8,第一個要改的自然是用WKWebView替換原來的UIWebView。WKWebView有很多明顯優勢:
1,更多的支持HTML5的特性
2,官方宣稱的高達60fps的滾動刷新率以及內置手勢
3,將UIWebViewDelegate與UIWebView拆分成了14類與3個協議,以前很多不方便實現的功能得以實現。官方文檔
4,Safari相同的JavaScript引擎
5,佔用更少的內存
UIWebView
WKWebView
基本使用方法
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 ()<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代碼同上。
WebViewJavascriptBridge
一般來說,一個好的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下來使用。