前言
上一篇專門講解了WKWebView相關的所有類、代理的所有API。前篇文章地址:http://blog.csdn.net/baihuaxiu123/article/details/51286109
那么本篇講些什么呢?當然是實戰了!先看看效果圖.
通過本篇文章,至少可以學習到:
OC如何給JS注入對象及JS如何給IOS發送數據
JS調用alert、confirm、prompt時,不采用JS原生提示,而是使用iOS原生來實現
如何監聽web內容加載進度、是否加載完成
如何處理去跨域問題
在創建WKWebView之前,需要先創建配置對象,用于做一些配置:
WKWebViewConfiguration *config=[[WKWebViewConfiguration alloc] init];
偏好設置也沒有必須去修改它,都使用默認的就可以了,除非你真的需要修改它:
// 設置偏好設置config.preferences= [[WKPreferences alloc] init];// 默認為0config.preferences.minimumFontSize=10;// 默認認為YESconfig.preferences.javaScriptEnabled= YES;// 在iOS上默認為NO,表示不能自動通過窗口打開config.preferences.javaScriptCanOpenWindowsAutomatically= NO;
其實我們沒有必要去創建它,因為它根本沒有屬性和方法:
// web內容處理池,由于沒有屬性可以設置,也沒有方法可以調用,不用手動創建
config.processPool=[[WKProcessPool alloc] init];
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:selfname:@"AppModel"];
當JS通過AppModel發送數據到iOS端時,會在代理中收到:
#pragmamark - 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對象),用于區分功能。
通過唯一的默認構造器來創建對象:
self.webView= [[WKWebView alloc] initWithFrame:self.view.boundsconfiguration:config];[self.viewaddSubview:self.webView];
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;
// 添加KVO監聽[self.webViewaddObserver:selfforKeyPath:@"loading"options:NSKeyValueObservingOptionNew? ? ? ? ? ? ? ? context:nil];[self.webViewaddObserver:selfforKeyPath:@"title"options:NSKeyValueObservingOptionNew? ? ? ? ? ? ? ? context:nil];[self.webViewaddObserver:selfforKeyPath:@"estimatedProgress"options:NSKeyValueObservingOptionNew? ? ? ? ? ? ? ? context:nil];
然后我們就可以實現KVO處理方法,在loading完成時,可以注入一些JS到web中。這里只是簡單地執行一段web中的JS函數:
- (void)observeValueForKeyPath:(NSString*)keyPath? ? ? ? ? ? ? ? ? ? ? ofObject:(id)object? ? ? ? ? ? ? ? ? ? ? ? change:(NSDictionary *)change? ? ? ? ? ? ? ? ? ? ? context:(void*)context {if([keyPath isEqualToString:@"loading"]) {NSLog(@"loading");? }elseif([keyPath isEqualToString:@"title"]) {self.title=self.webView.title;? }elseif([keyPath isEqualToString:@"estimatedProgress"]) {NSLog(@"progress: %f",self.webView.estimatedProgress);self.progressView.progress=self.webView.estimatedProgress;? }// 加載完成if(!self.webView.loading) {// 手動調用JS代碼// 每次頁面完成都彈出來,大家可以在測試時再打開NSString*js = @"callJsAlert()";? ? [self.webViewevaluateJavaScript:js completionHandler:^(id_Nullable response,NSError* _Nullable error) {NSLog(@"response: %@ error: %@", response, error);NSLog(@"call js alert by native");? ? }];? ? [UIViewanimateWithDuration:0.5animations:^{self.progressView.alpha=0;? ? }];? }}
與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();? }]];? [selfpresentViewController:alert animated:YEScompletion: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(^)(BOOLresult))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);? }]];? [selfpresentViewController:alert animated:YEScompletion:NULL];NSLog(@"%@", message);}// JS端調用prompt函數時,會觸發此方法// 要求輸入一段文本// 在原生輸入得到文本內容后,通過completionHandler回調給JS- (void)webView:(WKWebView *)webView runJavaScriptTextInputPanelWithPrompt:(NSString*)prompt defaultText:(nullableNSString*)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= [UIColorredColor];? }];? [alert addAction:[UIAlertAction actionWithTitle:@"確定"style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {? ? completionHandler([[alert.textFieldslastObject] text]);? }]];? [selfpresentViewController:alert animated:YEScompletion:NULL];}
如果需要處理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"]) {// 對于跨域,需要手動跳轉[[UIApplicationsharedApplication] 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__);}
iOS and Js*{font-size:40px;}
Test how to use objective-c call js
functioncallJsAlert(){alert('Objective-C call js to show alert');? ? ? ? window.webkit.messageHandlers.AppModel.postMessage({body:'call js alert in js'});? ? ? }functioncallJsConfirm(){if(confirm('confirm','Objective-C call js to show confirm')) {? ? ? ? document.getElementById('jsParamFuncSpan').innerHTML? ? ? ? ='true';? ? ? }else{? ? ? ? document.getElementById('jsParamFuncSpan').innerHTML? ? ? ? ='false';? ? ? }// AppModel是我們所注入的對象window.webkit.messageHandlers.AppModel.postMessage({body:'call js confirm in js'});? ? }functioncallJsInput(){varresponse = prompt('Hello','Please input your name:');? ? ? document.getElementById('jsParamFuncSpan').innerHTML = response;// AppModel是我們所注入的對象window.webkit.messageHandlers.AppModel.postMessage({body: response});? ? }
備注:備注:內容來自CSDN-劉玉剛--AI-技術研究院,原文博客網址:http://blog.csdn.net/baihuaxiu123/article/details/51287367