通過本篇文章,至少可以學習到:
OC如何給JS注入對象及JS如何給IOS發送數據
JS調用alert、confirm、prompt時,不采用JS原生提示,而是使用iOS原生來實現
如何監聽web內容加載進度、是否加載完成
如何處理去跨域問題
創建配置類
在創建WKWebView之前,需要先創建配置對象,用于做一些配置:
WKWebViewConfiguration *config = [[WKWebViewConfiguration alloc] init];
配置偏好設置
偏好設置也沒有必須去修改它,都使用默認的就可以了,除非你真的需要修改它:
// 設置偏好設置
config.preferences = [[WKPreferences alloc] init];
// 默認為0
config.preferences.minimumFontSize = 10;
// 默認認為YES
config.preferences.javaScriptEnabled = YES;
// 在iOS上默認為NO,表示不能自動通過窗口打開
config.preferences.javaScriptCanOpenWindowsAutomatically = NO;
配置web內容處理池
其實我們沒有必要去創建它,因為它根本沒有屬性和方法:
// web內容處理池,由于沒有屬性可以設置,也沒有方法可以調用,不用手動創建
config.processPool = [[WKProcessPool alloc] init];
配置Js與Web內容交互
WKUserContentController是用于給JS注入對象的,注入對象后,JS端就可以使用:
window.webkit.messageHandlers..postMessage
(來調用發送數據給iOS端,比如:window.webkit.messageHandlers.AppModel.postMessage({body: '傳數據'});
AppModel就是我們要注入的名稱,注入以后,就可以在JS端調用了,傳數據統一通過body傳,可以是多種類型,只支持NSNumber, NSString, NSDate, NSArray,NSDictionary, and NSNull類型。
下面我們配置給JS的main frame注入AppModel名稱,對于JS端可就是對象了
// 通過JS與webview內容交互
config.userContentController = [[WKUserContentController alloc] init];
// 注入JS對象名稱AppModel,當JS通過AppModel來調用時,
// 我們可以在WKScriptMessageHandler代理中接收到
[config.userContentController addScriptMessageHandler:self name:@"AppModel"];
當JS通過AppModel發送數據到iOS端時,會在代理中收到:
#pragma mark - WKScriptMessageHandler
- (void)userContentController:(WKUserContentController *)userContentController
didReceiveScriptMessage:(WKScriptMessage *)message {
if ([message.name isEqualToString:@"AppModel"]) {
// 打印所傳過來的參數,只支持NSNumber, NSString, NSDate, NSArray,
// NSDictionary, and NSNull類型
NSLog(@"%@", message.body);
}
}
所有JS調用iOS的部分,都只可以在此處使用哦。當然我們也可以注入多個名稱(JS對象),用于區分功能。
創建WKWebView
通過唯一的默認構造器來創建對象:
5self.webView = [[WKWebView alloc] initWithFrame:self.view.bounds
configuration:config];
[self.view addSubview:self.webView];
加載H5頁面
NSURL *path = [[NSBundle mainBundle] URLForResource:@"test" withExtension:@"html"];
[self.webView loadRequest:[NSURLRequest requestWithURL:path]];
配置代理
如果需要處理web導航條上的代理處理,比如鏈接是否可以跳轉或者如何跳轉,需要設置代理;而如果需要與在JS調用alert、confirm、prompt函數時,通過JS原生來處理,而不是調用JS的alert、confirm、prompt函數,那么需要設置UIDelegate,在得到響應后可以將結果反饋到JS端:
// 導航代理
self.webView.navigationDelegate = self;
// 與webview UI交互代理
self.webView.UIDelegate = self;
添加對WKWebView屬性的監聽
WKWebView有好多個支持KVO的屬性,這里只是監聽loading、title、estimatedProgress屬性,分別用于判斷是否正在加載、獲取頁面標題、當前頁面載入進度:
// 添加KVO監聽
[self.webView addObserver:self
forKeyPath:@"loading"
options:NSKeyValueObservingOptionNew
context:nil];
[self.webView addObserver:self
forKeyPath:@"title"
options:NSKeyValueObservingOptionNew
context:nil];
[self.webView addObserver:self
forKeyPath:@"estimatedProgress"
options:NSKeyValueObservingOptionNew
context:nil];
然后我們就可以實現KVO處理方法,在loading完成時,可以注入一些JS到web中。這里只是簡單地執行一段web中的JS函數:
#pragma mark - KVO
- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary *)change
context:(void *)context {
if ([keyPath isEqualToString:@"loading"]) {
NSLog(@"loading");
} else if ([keyPath isEqualToString:@"title"]) {
self.title = self.webView.title;
} else if ([keyPath isEqualToString:@"estimatedProgress"]) {
NSLog(@"progress: %f", self.webView.estimatedProgress);
self.progressView.progress = self.webView.estimatedProgress;
}
// 加載完成
if (!self.webView.loading) {
// 手動調用JS代碼
// 每次頁面完成都彈出來,大家可以在測試時再打開
NSString *js = @"callJsAlert()";
[self.webView evaluateJavaScript:js completionHandler:^(id _Nullable response, NSError * _Nullable error) {
NSLog(@"response: %@ error: %@", response, error);
NSLog(@"call js alert by native");
}];
[UIView animateWithDuration:0.5 animations:^{
self.progressView.alpha = 0;
}];
}
}
WKUIDelegate
與JS原生的alert、confirm、prompt交互,將彈出來的實際上是我們原生的窗口,而不是JS的。在得到數據后,由原生傳回到JS:
#pragma mark - WKUIDelegate
- (void)webViewDidClose:(WKWebView *)webView {
NSLog(@"%s", __FUNCTION__);
}
// 在JS端調用alert函數時,會觸發此代理方法。
// JS端調用alert時所傳的數據可以通過message拿到
// 在原生得到結果后,需要回調JS,是通過completionHandler回調
- (void)webView:(WKWebView *)webView runJavaScriptAlertPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(void))completionHandler {
NSLog(@"%s", __FUNCTION__);
UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"alert" message:@"JS調用alert" preferredStyle:UIAlertControllerStyleAlert];
[alert addAction:[UIAlertAction actionWithTitle:@"確定" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
completionHandler();
}]];
[self presentViewController:alert animated:YES completion:NULL];
NSLog(@"%@", message);
}
// JS端調用confirm函數時,會觸發此方法
// 通過message可以拿到JS端所傳的數據
// 在iOS端顯示原生alert得到YES/NO后
// 通過completionHandler回調給JS端
- (void)webView:(WKWebView *)webView runJavaScriptConfirmPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(BOOL result))completionHandler {
NSLog(@"%s", __FUNCTION__);
UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"confirm" message:@"JS調用confirm" preferredStyle:UIAlertControllerStyleAlert];
[alert addAction:[UIAlertAction actionWithTitle:@"確定" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
completionHandler(YES);
}]];
[alert addAction:[UIAlertAction actionWithTitle:@"取消" style:UIAlertActionStyleCancel handler:^(UIAlertAction * _Nonnull action) {
completionHandler(NO);
}]];
[self presentViewController:alert animated:YES completion:NULL];
NSLog(@"%@", message);
}
// JS端調用prompt函數時,會觸發此方法
// 要求輸入一段文本
// 在原生輸入得到文本內容后,通過completionHandler回調給JS
- (void)webView:(WKWebView *)webView runJavaScriptTextInputPanelWithPrompt:(NSString *)prompt defaultText:(nullable NSString *)defaultText initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(NSString * __nullable result))completionHandler {
NSLog(@"%s", __FUNCTION__);
NSLog(@"%@", prompt);
UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"textinput" message:@"JS調用輸入框" preferredStyle:UIAlertControllerStyleAlert];
[alert addTextFieldWithConfigurationHandler:^(UITextField * _Nonnull textField) {
textField.textColor = [UIColor redColor];
}];
[alert addAction:[UIAlertAction actionWithTitle:@"確定" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
completionHandler([[alert.textFields lastObject] text]);
}]];
[self presentViewController:alert animated:YES completion:NULL];
}
WKNavigationDelegate
如果需要處理web導航操作,比如鏈接跳轉、接收響應、在導航開始、成功、失敗等時要做些處理,就可以通過實現相關的代理方法:
#pragma mark - WKNavigationDelegate
// 請求開始前,會先調用此代理方法
// 與UIWebView的
// - (BOOL)webView:(UIWebView *)webView
// shouldStartLoadWithRequest:(NSURLRequest *)request
// navigationType:(UIWebViewNavigationType)navigationType;
// 類型,在請求先判斷能不能跳轉(請求)
- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler {
NSString *hostname = navigationAction.request.URL.host.lowercaseString;
if (navigationAction.navigationType == WKNavigationTypeLinkActivated
&& ![hostname containsString:@".baidu.com"]) {
// 對于跨域,需要手動跳轉
[[UIApplication sharedApplication] openURL:navigationAction.request.URL];
// 不允許web內跳轉
decisionHandler(WKNavigationActionPolicyCancel);
} else {
self.progressView.alpha = 1.0;
decisionHandler(WKNavigationActionPolicyAllow);
}
NSLog(@"%s", __FUNCTION__);
}
// 在響應完成時,會回調此方法
// 如果設置為不允許響應,web內容就不會傳過來
- (void)webView:(WKWebView *)webView decidePolicyForNavigationResponse:(WKNavigationResponse *)navigationResponse decisionHandler:(void (^)(WKNavigationResponsePolicy))decisionHandler {
decisionHandler(WKNavigationResponsePolicyAllow);
NSLog(@"%s", __FUNCTION__);
}
// 開始導航跳轉時會回調
- (void)webView:(WKWebView *)webView didStartProvisionalNavigation:(null_unspecified WKNavigation *)navigation {
NSLog(@"%s", __FUNCTION__);
}
// 接收到重定向時會回調
- (void)webView:(WKWebView *)webView didReceiveServerRedirectForProvisionalNavigation:(null_unspecified WKNavigation *)navigation {
NSLog(@"%s", __FUNCTION__);
}
// 導航失敗時會回調
- (void)webView:(WKWebView *)webView didFailProvisionalNavigation:(null_unspecified WKNavigation *)navigation withError:(NSError *)error {
NSLog(@"%s", __FUNCTION__);
}
// 頁面內容到達main frame時回調
- (void)webView:(WKWebView *)webView didCommitNavigation:(null_unspecified WKNavigation *)navigation {
NSLog(@"%s", __FUNCTION__);
}
// 導航完成時,會回調(也就是頁面載入完成了)
- (void)webView:(WKWebView *)webView didFinishNavigation:(null_unspecified WKNavigation *)navigation {
NSLog(@"%s", __FUNCTION__);
}
// 導航失敗時會回調
- (void)webView:(WKWebView *)webView didFailNavigation:(null_unspecified WKNavigation *)navigation withError:(NSError *)error {
}
// 對于HTTPS的都會觸發此代理,如果不要求驗證,傳默認就行
// 如果需要證書驗證,與使用AFN進行HTTPS證書驗證是一樣的
- (void)webView:(WKWebView *)webView didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *__nullable credential))completionHandler {
NSLog(@"%s", __FUNCTION__);
completionHandler(NSURLSessionAuthChallengePerformDefaultHandling, nil);
}
// 9.0才能使用,web內容處理中斷時會觸發
- (void)webViewWebContentProcessDidTerminate:(WKWebView *)webView {
NSLog(@"%s", __FUNCTION__);
}
JS端代碼
demo? 鏈接 https://github.com/CoderJackyHuang/WKWebViewH5ObjCDemo