WKWebView與Js實戰(OC版)

小灰是蝸牛君發表於2018-01-02

前言

本篇文章教大家如何使用WKWebView去實現常用的一些API操作。當然,也會有如何與JS互動的實戰。

效果圖

wkjs.gif

通過本篇文章,至少可以學習到:

OC如何給JS注入物件及JS如何給IOS傳送資料

JS呼叫alert、confirm、prompt時,不採用JS原生提示,而是使用iOS原生來實現

如何監聽web內容載入進度、是否載入完成

如何處理去跨域問題

建立配置類

在建立WKWebView之前,需要先建立配置物件,用於做一些配置:

    WKWebViewConfiguration*config=[[WKWebViewConfigurationalloc]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.<name>.postMessage(<messageBody>) 
複製程式碼

來呼叫傳送資料給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呼叫iOS的部分,都只可以在此處使用哦。當然我們也可以注入多個名稱(JS物件),用於區分功能。

建立WKWebView

通過唯一的預設構造器來建立物件:

  self.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<NSString *,id> *)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端程式碼

  <!DOCTYPE html>
  <html>
  <head>
  <title>iOS and Js</title>
  <style type="text/css">
  * {
  font-size: 40px;
   }
    </style>
    </head>

  <body>

    <div style="margin-top: 100px">
    <h1>Test how to use objective-c call js</h1><br/>
      <div><input type="button" value="call js alert" onclick="callJsAlert()"></div>
  <br/>
   <div><input type="button" value="Call js confirm" onclick="callJsConfirm()"></div><br/>
  </div>
   <br/>
   <div>
   <div><input type="button" value="Call Js prompt " onclick="callJsInput()"></div><br/>
   <div>Click me here: <a href="http://www.baidu.com">Jump to Baidu</a></div>
   </div>

   <br/>
複製程式碼

   <script type="text/javascript">
   function callJsAlert() {
  alert('Objective-C call js to show alert');

   window.webkit.messageHandlers.AppModel.postMessage({body: 'call js alert in js'});
   }

   function callJsConfirm() {
   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'});
   }

   function callJsInput() {
    var response = prompt('Hello', 'Please input your name:');
    document.getElementById('jsParamFuncSpan').innerHTML = response;

    // AppModel是我們所注入的物件
   window.webkit.messageHandlers.AppModel.postMessage({body: response});
   }
    </script>
   </body>
  </html>
複製程式碼

原始碼

下載原始碼:WKWebViewH5ObjCDemo

相關文章