iOS 開發 Object-C和JavaScript互動詳解之OC與JS互動在WKWebView中使用
阿新 • • 發佈:2019-01-04
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];
}