1. 程式人生 > >iOS 開發 Object-C和JavaScript互動詳解之OC與JS互動在WKWebView中使用

iOS 開發 Object-C和JavaScript互動詳解之OC與JS互動在WKWebView中使用

1.OC與JS互動在UIWebView中使用

2. WKWebView的使用詳解

3.OC與JS互動在WKWebView中使用

這裡寫圖片描述

//
//  ViewController.m
//  oc與js互動WKWebView
//
//  Created by zhouyu on 15/6/17.
//  Copyright © 2015年 zhouyu. All rights reserved.
//

#import "ViewController.h"
#import <WebKit/WebKit.h>
#import "TestViewController.h"

@interface ViewController
() <WKNavigationDelegate,WKUIDelegate,WKScriptMessageHandler>
/** * webView */ @property (nonatomic, strong) WKWebView *webView; /** * 進度條 */ @property (nonatomic, strong) UIProgressView *progress; @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; self
.navigationItem.title = @"oc與js互動"; WKWebView *webView = [[WKWebView alloc] initWithFrame:CGRectMake(0, 10, [UIScreen mainScreen].bounds.size.width, [UIScreen mainScreen].bounds.size.height-64)]; [self.view addSubview:webView]; self.webView = webView; // 展示進度 self.progress = [[UIProgressView alloc] init]; self
.progress.frame = CGRectMake(0, 64, [UIScreen mainScreen].bounds.size.width, 10); [self.view addSubview:self.progress]; self.progress.progress = 0; webView.navigationDelegate = self; // 新增監聽 [webView addObserver:self forKeyPath:@"estimatedProgress" options:NSKeyValueObservingOptionNew context:nil]; NSURL *URL = [NSURL URLWithString:@"http://m.dianping.com/tuan/deal/5501525"]; NSURLRequest *request = [NSURLRequest requestWithURL:URL]; [webView loadRequest:request]; } #pragma mark - 監聽進度 // 計算wkWebView進度條 - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { if (object == self.webView && [keyPath isEqualToString:@"estimatedProgress"]) { CGFloat newprogress = [[change objectForKey:NSKeyValueChangeNewKey] doubleValue]; NSLog(@"進度 %f",newprogress); if (newprogress != 1.000000) { // 網頁載入時就展示進度 self.progress.hidden = NO; self.progress.progress = newprogress; self.webView.frame = CGRectMake(0, 0, [UIScreen mainScreen].bounds.size.width, [UIScreen mainScreen].bounds.size.height); } else { // 網頁載入完成就進度 self.progress.hidden = YES; } } } // 記得取消監聽 - (void)dealloc { [self.webView removeObserver:self forKeyPath:@"estimatedProgress"]; } #pragma mark - WKNavigationDelegate // 在傳送請求之前,決定是否跳轉,是否決定繼續載入這個網頁 - (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler { NSLog(@"在傳送請求之前,決定是否跳轉 decidePolicyForNavigationAction"); NSString *URLString = navigationAction.request.URL.absoluteString; // NSLog(@"監測到的WKWebView上的請求 %@",URLString); NSRange range = [URLString rangeOfString:@"hm://"]; if (range.length > 0) { // 控制器的跳轉 [self.navigationController pushViewController:[[TestViewController alloc] init] animated:YES]; // 不允許跳轉,即不載入這個連結對應的內容 decisionHandler(WKNavigationActionPolicyCancel); } else { // 允許跳轉,即載入這個連結對應的內容 decisionHandler(WKNavigationActionPolicyAllow); } } // 頁面開始載入時呼叫 - (void)webView:(WKWebView *)webView didStartProvisionalNavigation:(WKNavigation *)navigation { NSLog(@"頁面開始載入時呼叫 didStartProvisionalNavigation"); } // 在收到響應後,決定是否跳轉 - (void)webView:(WKWebView *)webView decidePolicyForNavigationResponse:(WKNavigationResponse *)navigationResponse decisionHandler:(void (^)(WKNavigationResponsePolicy))decisionHandler { NSLog(@"在收到響應後,決定是否跳轉 decidePolicyForNavigationResponse"); // 允許跳轉,即繼續載入這個連結對應的內容 decisionHandler(WKNavigationResponsePolicyAllow); // 不允許跳轉,即不再繼續載入這個連結對應的內容 // decisionHandler(WKNavigationResponsePolicyCancel); } // 當內容開始返回時呼叫 - (void)webView:(WKWebView *)webView didCommitNavigation:(WKNavigation *)navigation { NSLog(@"當內容開始返回時呼叫 didCommitNavigation"); } // 頁面載入完成之後呼叫 - (void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation { // 拼接JS的程式碼 NSMutableString *JSStringM = [NSMutableString string]; // 刪除導航 [JSStringM appendString:@"var headerTag = document.getElementsByTagName('header')[0];headerTag.parentNode.removeChild(headerTag);"]; // 刪除底部懸停按鈕 [JSStringM appendString:@"var footerBtnTag = document.getElementsByClassName('footer-btn-fix')[0]; footerBtnTag.parentNode.removeChild(footerBtnTag);"]; // 刪除底部佈局 [JSStringM appendString:@"var footerTag = document.getElementsByClassName('footer')[0]; footerTag.parentNode.removeChild(footerTag);"]; // 給標籤新增點選事件 [JSStringM appendString:@"var figureTag = document.getElementsByTagName('figure')[0].children[0]; figureTag.onclick = function(){window.location.href = 'hm://?src='+this.src};"]; // [JSStringM appendString:@"var figureTag = document.getElementsByTagName('figure')[0].children[0]; figureTag.onclick = function(){window.location.href = 'headerimageclick://www.yaowoya.com};"]; // OC呼叫JS程式碼 [webView evaluateJavaScript:JSStringM completionHandler:nil]; } // 頁面載入失敗時呼叫 - (void)webView:(WKWebView *)webView didFailProvisionalNavigation:(WKNavigation *)navigation { NSLog(@"頁面載入失敗時呼叫 didFailProvisionalNavigation"); }

4.使用拓展

配置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內容互動
WKWebViewConfiguration *config = [[WKWebViewConfiguration alloc] init];

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);
  }
}

5.配置代理

如果需要處理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;
    }];
  }
}

6.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];
}