開發App的過程當中,經常會遇到在App內部加載網頁,一般用
UIWebView
加載。這個自iOS2開始使用的網頁加載器一直是開發的心病
:加載速度慢,佔用內存多,優化困難。若是加載網頁多,還可能由於過量佔用內存而給系統kill
掉。各類優化的方法效果也不那麼明顯(點擊查看經常使用優化方法)。html
iOS8之後,蘋果推出了新框架Webkit
,提供了替換UIWebView
的組件WKWebView
。各類UIWebView
的問題沒有了,速度更快了,佔用內存少了,一句話,WKWebView
是App內部加載網頁的最佳選擇!java
先看下 WKWebView
的特性:git
而後從如下幾個方面說下WKWebView
的基本用法:github
WKUIDelegate
協議JS
代碼JS
代碼JS
調用App註冊過的方法加載網頁或HTML代碼的方式與UIWebView
相同,代碼示例以下:web
WKWebView *webView = [[WKWebView alloc] initWithFrame:self.view.bounds]; [webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"http://www.baidu.com"]]]; [self.view addSubview:webView];
用來追蹤加載過程(頁面開始加載、加載完成、加載失敗)的方法:數組
// 頁面開始加載時調用 - (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;
WKUIDelegate
協議這個協議主要用於WKWebView
處理web界面的三種提示框(警告框、確認框、輸入框),下面是警告框的例子:app
/** * web界面中有彈出警告框時調用 * * @param webView 實現該代理的webview * @param message 警告框中的內容 * @param frame 主窗口 * @param completionHandler 警告框消失調用 */ - (void)webView:(WKWebView *)webView runJavaScriptAlertPanelWithMessage:(NSString *)message initiatedByFrame:(void (^)())completionHandler;
JS
代碼用於在客戶端內部加入JS
代碼,並執行,示例以下:框架
// 圖片縮放的js代碼 NSString *js = @"var count = document.images.length;for (var i = 0; i < count; i++) {var image = document.images[i];image.style.width=320;};window.alert('找到' + count + '張圖');"; // 根據JS字符串初始化WKUserScript對象 WKUserScript *script = [[WKUserScript alloc] initWithSource:js injectionTime:WKUserScriptInjectionTimeAtDocumentEnd forMainFrameOnly:YES]; // 根據生成的WKUserScript對象,初始化WKWebViewConfiguration WKWebViewConfiguration *config = [[WKWebViewConfiguration alloc] init]; [config.userContentController addUserScript:script]; _webView = [[WKWebView alloc] initWithFrame:self.view.bounds configuration:config]; [_webView loadHTMLString:@"<head></head><imgea src='http://www.nsu.edu.cn/v/2014v3/img/background/3.jpg' />"baseURL:nil]; [self.view addSubview:_webView];
JS
代碼用戶調用用JS
寫過的代碼,通常指服務端開發的:異步
//javaScriptString是JS方法名,completionHandler是異步回調block [self.webView evaluateJavaScript:javaScriptString completionHandler:completionHandler];
JS
調用App註冊過的方法再WKWebView
裏面註冊供JS調用的方法,是經過WKUserContentController
類下面的方法:
- (void)addScriptMessageHandler:(id <WKScriptMessageHandler>)scriptMessageHandler name:(NSString *)name;
scriptMessageHandler
是代理回調,JS
調用name
方法後,OC
會調用scriptMessageHandler
指定的對象。
JS
在調用OC
註冊方法的時候要用下面的方式:
window.webkit.messageHandlers.<name>.postMessage(<messageBody>)
注意,name(方法名)是放在中間的,messageBody只能是一個對象,若是要傳多個值,須要封裝成數組,或者字典。整個示例以下:
//OC註冊供JS調用的方法 [[_webView configuration].userContentController addScriptMessageHandler:self name:@"closeMe"]; //OC在JS調用方法作的處理 - (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message { NSLog(@"JS 調用了 %@ 方法,傳回參數 %@",message.name,message.body); } //JS調用 window.webkit.messageHandlers.closeMe.postMessage(null);
若是你在self
的dealloc
打個斷點,會發現self
沒有釋放!這顯然是不行的!谷歌後看到一種解決方法,以下:
@interface WeakScriptMessageDelegate : NSObject<WKScriptMessageHandler> @property (nonatomic, weak) id<WKScriptMessageHandler> scriptDelegate; - (instancetype)initWithDelegate:(id<WKScriptMessageHandler>)scriptDelegate; @end @implementation WeakScriptMessageDelegate - (instancetype)initWithDelegate:(id<WKScriptMessageHandler>)scriptDelegate { self = [super init]; if (self) { _scriptDelegate = scriptDelegate; } return self; } - (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message { [self.scriptDelegate userContentController:userContentController didReceiveScriptMessage:message]; } @end
思路是另外建立一個代理對象,而後經過代理對象回調指定的self
,
WKUserContentController *userContentController = [[WKUserContentController alloc] init]; [userContentController addScriptMessageHandler:[[WeakScriptMessageDelegate alloc] initWithDelegate:self] name:@"closeMe"];
運行代碼,self
釋放了,WeakScriptMessageDelegate
卻沒有釋放啊啊啊!
還需在self
的dealloc
裏面 添加這樣一句代碼:
[[_webView configuration].userContentController removeScriptMessageHandlerForName:@"closeMe"];
OK,圓滿解決問題!
目前,大多數App須要支持iOS7以上的版本,而WKWebView
只在iOS8後才能用,因此須要一個兼容性方案,既iOS7下用UIWebView
,iOS8後用WKWebView
。這個庫提供了這種兼容性方案:https://github.com/wangyangcc/IMYWebView
1 // 2 // DetailViewController.m 3 // TreeNavigation 4 // 5 // Created by wky on 16/10/2017. 6 // Copyright © 2017 vector. All rights reserved. 7 // 8 9 #import "DetailViewController.h" 10 #import <WebKit/WebKit.h> 11 12 @interface DetailViewController ()<WKNavigationDelegate> 13 14 @property(nonatomic,strong) WKWebView* webView; 15 16 @end 17 18 @implementation DetailViewController 19 20 - (void)viewDidLoad { 21 [super viewDidLoad]; 22 // Do any additional setup after loading the view. 23 //添加WKWebView 24 self.webView = [[WKWebView alloc]initWithFrame:self.view.frame]; 25 [self.view addSubview:self.webView]; 26 self.webView.navigationDelegate = self; 27 28 29 //最開始加載不出來,兩個緣由,一是本身等待的時間過短。二是哈爾濱的百度百科url含有中文, 30 // 爲此將URL編碼格式改成UTL-8,用stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding這個函數轉換一下 31 32 NSString *urlString =[_url stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]; 33 NSURL* url = [NSURL URLWithString:urlString]; 34 NSURLRequest* request = [NSURLRequest requestWithURL:url]; 35 [self.webView loadRequest:request]; 36 37 // 38 // WKWebView *webView = [[WKWebView alloc] initWithFrame:self.view.bounds]; 39 // [webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"http://www.baidu.com"]]]; 40 // [self.view addSubview:webView]; 41 42 43 44 45 46 } 47 48 - (void)didReceiveMemoryWarning { 49 [super didReceiveMemoryWarning]; 50 // Dispose of any resources that can be recreated. 51 52 53 54 } 55 56 /* 57 #pragma mark - Navigation 58 59 // In a storyboard-based application, you will often want to do a little preparation before navigation 60 - (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender { 61 // Get the new view controller using [segue destinationViewController]. 62 // Pass the selected object to the new view controller. 63 } 64 */ 65 66 @end