開發App的過程當中,經常會遇到在App內部加載網頁,一般用UIWebView加載。而這個自iOS2.0開始使用的Web容器一直是開發的心病:加載速度慢,佔用內存多,優化困難。若是加載網頁多,還可能由於過量佔用內存而給系統kill掉。各類優化的方法效果也不那麼明顯iOS8 之後,蘋果推出了新框架 WebKit,提供了替換 UIWebView 的組件 WKWebView。各類 UIWebView 的性能問題沒有了,速度更快了,佔用內存少了,體驗更好了,下面列舉一些其它的優點: 一、在性能、穩定性、功能方面有很大提高(最直觀的體現就是加載網頁是佔用的內存,模擬器加載百度與開源中國網站時,WKWebView佔用23M,而UIWebView佔用85M); 二、容許JavaScript的Nitro庫加載並使用(UIWebView中限制); 三、支持了更多的HTML5特性; 四、高達60fps的滾動刷新率以及內置手勢; 五、將UIWebViewDelegate與UIWebView重構成了14類與3個協議(查看蘋果官方文檔);html
WKBackForwardList: 以前訪問過的 web 頁面的列表,能夠經過後退和前進動做來訪問到。 WKBackForwardListItem: webview 中後退列表裏的某一個網頁。 WKFrameInfo: 包含一個網頁的佈局信息。 WKNavigation: 包含一個網頁的加載進度信息。 WKNavigationAction: 包含可能讓網頁導航變化的信息,用於判斷是否作出導航變化。 WKNavigationResponse: 包含可能讓網頁導航變化的返回內容信息,用於判斷是否作出導航變化。 WKPreferences: 歸納一個 webview 的偏好設置。 WKProcessPool: 表示一個 web 內容加載池。 WKUserContentController: 提供使用 JavaScript post 信息和注射 script 的方法。 WKScriptMessage: 包含網頁發出的信息。 WKUserScript: 表示能夠被網頁接受的用戶腳本。 WKWebViewConfiguration: 初始化 webview 的設置。 WKWindowFeatures: 指定加載新網頁時的窗口屬性。java
WKNavigationDelegate: 提供了追蹤主窗口網頁加載過程和判斷主窗口和子窗口是否進行頁面加載新頁面的相關方法。 WKUIDelegate: 提供用原生控件顯示網頁的方法回調。 WKScriptMessageHandler: 提供從網頁中收消息的回調方法。ios
//上文介紹過的偏好配置
@property (nonatomic, readonly, copy) WKWebViewConfiguration *configuration;
// 導航代理
@property (nullable, nonatomic, weak) id <WKNavigationDelegate> navigationDelegate;
// 用戶交互代理
@property (nullable, nonatomic, weak) id <WKUIDelegate> UIDelegate;
// 頁面前進、後退列表
@property (nonatomic, readonly, strong) WKBackForwardList *backForwardList;
// 默認構造器
- (instancetype)initWithFrame:(CGRect)frame configuration:(WKWebViewConfiguration *)configuration NS_DESIGNATED_INITIALIZER;
//加載請求API
- (nullable WKNavigation *)loadRequest:(NSURLRequest *)request;
// 加載URL
- (nullable WKNavigation *)loadFileURL:(NSURL *)URL allowingReadAccessToURL:(NSURL *)readAccessURL NS_AVAILABLE(10_11, 9_0);
// 直接加載HTML
- (nullable WKNavigation *)loadHTMLString:(NSString *)string baseURL:(nullable NSURL *)baseURL;
// 直接加載data
- (nullable WKNavigation *)loadData:(NSData *)data MIMEType:(NSString *)MIMEType characterEncodingName:(NSString *)characterEncodingName baseURL:(NSURL *)baseURL NS_AVAILABLE(10_11, 9_0);
// 前進或者後退到某一頁面
- (nullable WKNavigation *)goToBackForwardListItem:(WKBackForwardListItem *)item;
// 頁面的標題,支持KVO的
@property (nullable, nonatomic, readonly, copy) NSString *title;
// 當前請求的URL,支持KVO的
@property (nullable, nonatomic, readonly, copy) NSURL *URL;
// 標識當前是否正在加載內容中,支持KVO的
@property (nonatomic, readonly, getter=isLoading) BOOL loading;
// 當前加載的進度,範圍爲[0, 1]
@property (nonatomic, readonly) double estimatedProgress;
// 標識頁面中的全部資源是否經過安全加密鏈接來加載,支持KVO的
@property (nonatomic, readonly) BOOL hasOnlySecureContent;
// 當前導航的證書鏈,支持KVO
@property (nonatomic, readonly, copy) NSArray *certificateChain NS_AVAILABLE(10_11, 9_0);
// 是否能夠招待goback操做,它是支持KVO的
@property (nonatomic, readonly) BOOL canGoBack;
// 是否能夠執行gofarward操做,支持KVO
@property (nonatomic, readonly) BOOL canGoForward;
// 返回上一頁面,若是不能返回,則什麼也不幹
- (nullable WKNavigation *)goBack;
// 進入下一頁面,若是不能前進,則什麼也不幹
- (nullable WKNavigation *)goForward;
// 從新載入頁面
- (nullable WKNavigation *)reload;
// 從新從原始URL載入
- (nullable WKNavigation *)reloadFromOrigin;
// 中止加載數據
- (void)stopLoading;
// 執行JS代碼
- (void)evaluateJavaScript:(NSString *)javaScriptString completionHandler:(void (^ __nullable)(__nullable id, NSError * __nullable error))completionHandler;
// 標識是否支持左、右swipe手勢是否能夠前進、後退
@property (nonatomic) BOOL allowsBackForwardNavigationGestures;
// 自定義user agent,若是沒有則爲nil
@property (nullable, nonatomic, copy) NSString *customUserAgent NS_AVAILABLE(10_11, 9_0);
// 在iOS上默認爲NO,標識不容許連接預覽
@property (nonatomic) BOOL allowsLinkPreview NS_AVAILABLE(10_11, 9_0);
#if TARGET_OS_IPHONE
/*! @abstract The scroll view associated with the web view.
*/
@property (nonatomic, readonly, strong) UIScrollView *scrollView;
#endif
#if !TARGET_OS_IPHONE
// 標識是否支持放大手勢,默認爲NO
@property (nonatomic) BOOL allowsMagnification;
// 放大因子,默認爲1
@property (nonatomic) CGFloat magnification;
// 根據設置的縮放因子來縮放頁面,並居中顯示結果在指定的點
- (void)setMagnification:(CGFloat)magnification centeredAtPoint:(CGPoint)point;
複製代碼
#使用 ####一、首先須要先引入WebKit庫git
#import <WebKit/WebKit.h>
複製代碼
####二、兩種初始化方法github
// 默認初始化
- (instancetype)initWithFrame:(CGRect)frame;
// 根據對webview的相關配置,進行初始化
- (instancetype)initWithFrame:(CGRect)frame configuration:(WKWebViewConfiguration *)configuration NS_DESIGNATED_INITIALIZER;
複製代碼
最基礎的方法和UIWebView同樣web
NSURL *url = [NSURL URLWithString:@"www.jianshu.com"];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
[webView loadRequest:request];
複製代碼
一些其餘的加載方法安全
//加載本地URL文件
- (nullable WKNavigation *)loadFileURL:(NSURL *)URL
allowingReadAccessToURL:(NSURL *)readAccessURL
//加載本地HTML字符串
- (nullable WKNavigation *)loadHTMLString:(NSString *)string
baseURL:(nullable NSURL *)baseURL;
//加載二進制數據
- (nullable WKNavigation *)loadData:(NSData *)data MIMEType:(NSString *)MIMEType characterEncodingName:(NSString *)characterEncodingName baseURL:(NSURL *)baseURL
複製代碼
該代理提供的方法,能夠用來追蹤加載過程(頁面開始加載、加載完成、加載失敗)、決定是否執行跳轉。bash
// 頁面開始加載時調用
- (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從名稱能看出它是webView在user interface上的代理,共有5個可選類型的代理方法。它爲webView提供了原生的彈框,而不是JavaScript裏的提示框。雖然JavaScript的提示框能夠作的跟原生同樣,可是對於ios開發者來講,若是要更改提示框就不方便了。提供這個代理,可讓ios端更加靈活的修改提示框的樣式。app
// 新建WKWebView
- (nullable WKWebView *)webView:(WKWebView *)webView createWebViewWithConfiguration:(WKWebViewConfiguration *)configuration forNavigationAction:(WKNavigationAction *)navigationAction windowFeatures:(WKWindowFeatures *)windowFeatures;
// 關閉WKWebView
- (void)webViewDidClose:(WKWebView *)webView NS_AVAILABLE(10_11, 9_0);
// 對應js的Alert方法
/**
* web界面中有彈出警告框時調用
*
* @param webView 實現該代理的webview
* @param message 警告框中的內容
* @param frame 主窗口
* @param completionHandler 警告框消失調用
*/
- (void)webView:(WKWebView *)webView runJavaScriptAlertPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(void))completionHandler;
// 對應js的confirm方法
- (void)webView:(WKWebView *)webView runJavaScriptConfirmPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(BOOL result))completionHandler;
// 對應js的prompt方法
- (void)webView:(WKWebView *)webView runJavaScriptTextInputPanelWithPrompt:(NSString *)prompt defaultText:(nullable NSString *)defaultText initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(NSString * __nullable result))completionHandler;
複製代碼
這個協議中包含一個必須實現的方法,這個方法是native與web端交互的關鍵,它能夠直接將接收到的JS腳本轉爲OC或Swift對象。
// 從web界面中接收到一個腳本時調用
- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message;
複製代碼
HTML代碼
<html>
<!--描述網頁信息-->
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>title</title>
<style>
*{
font-size: 50px;
}
.btn{height:80px; width:80%; padding: 0px 30px; background-color: #0071E7; border: solid 1px #0071E7; border-radius:5px; font-size: 1em; color: white}
</style>
<script>
//OC調用JS的方法列表
function alertMobile() {
document.getElementById('mobile').innerHTML = '不帶參數'
}
function alertName(msg) {
//有一個參數
document.getElementById('name').innerHTML = '有一個參數 :' + msg
}
function alertSendMsg(num,msg) {
//有兩個參數
document.getElementById('msg').innerHTML = '有兩個參數:' + num + ',' + msg + '!!'
}
//JS響應方法列表
function btnClick1() {
window.webkit.messageHandlers.showMobile.postMessage(null)
}
function btnClick2() {
window.webkit.messageHandlers.showName.postMessage('有一個參數')
}
function btnClick3() {
window.webkit.messageHandlers.showSendMsg.postMessage(['兩個參數One', '兩個參數Two'])
}
</script>
</head>
<!--網頁具體內容-->
<body>
<br/>
<div>
<label>WKWebView&JS交互</label>
</div>
<br/>
<div id="mobile"></div>
<div>
<button class="btn" type="button" onclick="btnClick1()">不帶參數</button>
</div>
<br/>
<div id="name"></div>
<div>
<button class="btn" type="button" onclick="btnClick2()">一個參數</button>
</div>
<br/>
<div id="msg"></div>
<div>
<button class="btn" type="button" onclick="btnClick3()">兩個參數</button>
</div>
</body>
</html>
複製代碼
須要的頭文件
#import <WebKit/WebKit.h>
複製代碼
須要遵照的代理
一、設置偏好設置,以及JS調用OC 添加處理腳本,這裏的內容我寫在了viewDidLoad裏面,可是須要注意的是,須要在咱們結束的時候釋放WKUserContentController,否則會形成內存泄漏
- (void)viewDidLoad {
[super viewDidLoad];
// 設置偏好設置
WKWebViewConfiguration *config = [[WKWebViewConfiguration alloc] init];
// 默認爲0
config.preferences.minimumFontSize = 10;
//是否支持JavaScript
config.preferences.javaScriptEnabled = YES;
//不經過用戶交互,是否能夠打開窗口
config.preferences.javaScriptCanOpenWindowsAutomatically = NO;
self.webView = [[WKWebView alloc] initWithFrame:CGRectMake(0, 0, self.view.bounds.size.width, self.view.bounds.size.height/2) configuration:config];
[self.view addSubview:self.webView];
NSString *filePath = [[NSBundle mainBundle] pathForResource:@"index" ofType:@"html"];
NSURL *baseURL = [[NSBundle mainBundle] bundleURL];
[self.webView loadHTMLString:[NSString stringWithContentsOfFile:filePath encoding:NSUTF8StringEncoding error:nil] baseURL:baseURL];
WKUserContentController *userCC = config.userContentController;
//JS調用OC 添加處理腳本
[userCC addScriptMessageHandler:self name:@"showMobile"];
[userCC addScriptMessageHandler:self name:@"showName"];
[userCC addScriptMessageHandler:self name:@"showSendMsg"];
}
複製代碼
二、釋放WKUserContentController代碼
-(void)removeAllScriptMsgHandle{
WKUserContentController *controller = self.webView.configuration.userContentController;
[controller removeScriptMessageHandlerForName:@"showMobile"];
[controller removeScriptMessageHandlerForName:@"showName"];
[controller removeScriptMessageHandlerForName:@"showSendMsg"];
}
複製代碼
在JS調用OC之後會走的代理
#pragma mark - WKScriptMessageHandler
- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message {
NSLog(@"%@",NSStringFromSelector(_cmd));
NSLog(@"%@",message.body);
if ([message.name isEqualToString:@"showMobile"]) {
[self showMsg:@"沒有參數"];
}
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];
}
}
複製代碼
網頁加載完成以後調用JS代碼纔會執行,由於這個時候html頁面已經注入到webView中而且能夠響應到對應方法。OC調用JS代碼
//不帶參數
- (IBAction)NOParameter:(id)sender {
[self.webView evaluateJavaScript:@"alertMobile()" completionHandler:^(id _Nullable response, NSError * _Nullable error) {
//JS 返回結果
NSLog(@"%@ %@",response,error);
}];
}
//一個參數
- (IBAction)oneParameter:(id)sender {
/*
*alertName('奧特們打小怪獸')
*alertName JS方法名
*奧特們打小怪獸 帶的參數
*/
[self.webView evaluateJavaScript:@"alertName('奧特們打小怪獸')" completionHandler:nil];
}
//兩個參數
- (IBAction)twoParameter:(id)sender {
/*
*alertSendMsg('我是參數1','我是參數2')
*alertSendMsg JS方法名
*我是參數1 帶的參數
*我是參數2
*/
[self.webView evaluateJavaScript:@"alertSendMsg('我是參數1','我是參數2')" completionHandler:nil];
}
- (void)showMsg:(NSString *)msg {
[[[UIAlertView alloc] initWithTitle:nil message:msg delegate:nil cancelButtonTitle:nil otherButtonTitles:@"OK", nil] show];
}
複製代碼
當咱們用UIWebView POST請求傳參時通常是這樣寫的
// 建立WebView
UIWebView *webView = [[UIWebView alloc] initWithFrame:[UIScreen mainScreen].bounds];
// 設置訪問的URL
NSURL *url = [NSURL URLWithString:@"http://www.example.com"];
// 根據URL建立請求
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
// 設置請求方法爲POST
[request setHTTPMethod:@"POST"];
// 設置請求參數
[request setHTTPBody:[@"username=aaa&password=123" dataUsingEncoding:NSUTF8StringEncoding]];
// WebView加載請求
[webView loadRequest:request];
// 將WebView添加到視圖
[self.view addSubview:webView];
複製代碼
可是當咱們用WKWebView這樣加載的時候並無什麼卵用。
參考文章: http://stackoverflow.com/questions/26253133/cant-set-headers-on-my-wkwebview-post-request http://www.jianshu.com/p/403853b63537
一、將一個包含JavaScript的POST請求的HTML代碼放到工程目錄中 二、加載這個包含JavaScript的POST請求的代碼到WKWebView 三、加載完成以後,用Native調用JavaScript的POST方法並傳入參數來完成請求
<html>
<head>
<script>
//調用格式: post('URL', {"key": "value"});
function post(path, params) {
var method = "post";
var form = document.createElement("form");
form.setAttribute("method", method);
form.setAttribute("action", path);
for(var key in params) {
if(params.hasOwnProperty(key)) {
var hiddenField = document.createElement("input");
hiddenField.setAttribute("type", "hidden");
hiddenField.setAttribute("name", key);
hiddenField.setAttribute("value", params[key]);
form.appendChild(hiddenField);
}
}
document.body.appendChild(form);
form.submit();
}
</script>
</head>
<body>
</body>
</html>
複製代碼
將這段代碼拷貝下來,而後粘貼到文本編輯器中,名字能夠隨意起,比方說保存爲:JSPOST.html,而後拷貝到工程目錄中,記得選擇對應的Target和勾選Copy items if needed(默認應該是勾選的)。這時候,就能夠用這段JavaScript代碼來發送帶參數的POST請求了。
######二、將對應的JavaScript代碼經過加載本地網頁的形式加載到WKWebView
// JS發送POST的Flag,爲真的時候會調用JS的POST方法(僅當第一次的時候加載本地JS)
self.needLoadJSPOST = YES;
// 建立WKWebView
self.webView = [[WKWebView alloc] initWithFrame:[UIScreen mainScreen].bounds];
//設置代理
self.webView.navigationDelegate = self;
// 獲取JS所在的路徑
NSString *path = [[NSBundle mainBundle] pathForResource:@"JSPOST" ofType:@"html"];
// 得到html內容
NSString *html = [[NSString alloc] initWithContentsOfFile:path encoding:NSUTF8StringEncoding error:nil];
// 加載js
[self.webView loadHTMLString:html baseURL:[[NSBundle mainBundle] bundleURL]];
// 將WKWebView添加到當前View
[self.view addSubview:self.webView];
複製代碼
這裏須要用到WKWebView和JS的交互
// 加載完成的代理方法
- (void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation {
// 判斷是否須要加載(僅在第一次加載)
if (self.needLoadJSPOST) {
// 調用使用JS發送POST請求的方法
[self postRequestWithJS];
// 將Flag置爲NO(後面就不須要加載了)
self.needLoadJSPOST = NO;
}
}
// 調用JS發送POST請求
- (void)postRequestWithJS {
// 發送POST的參數
NSString *postData = @"\"username\":\"aaa\",\"password\":\"123\"";
// 請求的頁面地址
NSString *urlStr = @"http://www.postexample.com";
// 拼裝成調用JavaScript的字符串
NSString *jscript = [NSString stringWithFormat:@"post('%@', {%@});", urlStr, postData];
// NSLog(@"Javascript: %@", jscript);
// 調用JS代碼
[self.webView evaluateJavaScript:jscript completionHandler:^(id object, NSError * _Nullable error) {
}];
}
複製代碼