1. 程式人生 > IOS開發 >iOS網路(二) WebView和WKWebview與JS的互動

iOS網路(二) WebView和WKWebview與JS的互動

一、WebView的相關簡單操作

		self.webView = [[UIWebView alloc] initWithFrame:self.view.bounds];
    self.webView.delegate = self;
    [self.view addSubview:self.webView];
    
    NSURL *url = [[NSBundle mainBundle] URLForResource:@"index.html" withExtension:nil];
    NSURLRequest *reqeust = [NSURLRequest requestWithURL:url];
    [self.webView loadRequest:reqeust];
複製程式碼
#pragma mark - UIWebViewDelegate

// 載入所有請求資料,以及控制是否載入
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType
{
  	NSLog(@"request = %@",request);
    // 路由能力 --
    NSLog(@"URL = %@",request.URL);
    NSLog(@"scheme = %@",request.URL.scheme);
  
  	NSString *scheme = request.URL.scheme;
    if ([scheme isEqualToString:@"heheorg"]) 
    {
        return NO;//可以根據要跳轉的scheme設定不往下面下載,在此攔截住一個路由
    }
  	// 說明在此可以為所欲為,只要我有一套自己制定的方案
  
  	// request.URL.pathComponents 得到 oc 的方法和引數
  	/*例子:
  			NSArray *array = request.URL.pathComponents;
        // 此處虛擬碼沒有做 非空處理 - 實際若沒有非空處理則很容易出bug
        NSString *methodName = array[1];
        if ([methodName isEqualToString:@"getSum"]) 
        {
            // array[2],array[3]
            NSLog(@"%@-%@",array[2],array[3]);
        }
        
        [self performSelector:NSSelectorFromString(methodName) withObject:array afterDelay:0];
  	*/
  
  	return YES;
}
// 開始載入
- (void)webViewDidStartLoad:(UIWebView *)webView
{
    //  進度條
    NSLog(@"****************分界線****************");
    NSLog(@"開始載入");
}

// 載入完成
- (void)webViewDidFinishLoad:(UIWebView *)webView
{
    // tittle
    NSString *tittle = [webView stringByEvaluatingJavaScriptFromString:@"document.title"];
    self.title = tittle;
    
    NSLog(@"****************分界線****************");
    NSLog(@"載入完成");
}

// 載入失敗
- (void)webView:(UIWebView *)webView didFailLoadWithError:(NSError *)error
{
    NSLog(@"****************分界線****************");
    NSLog(@"載入失敗:%@",error);
}

// oc呼叫js方法
- (IBAction)didClickRightItemClick:(id)sender 
{
    NSLog(@"響應按鈕");
  	
    // 可用於重新整理頁面等功能
    // 客戶端資訊傳入token
  
    // js 需要加密等處理 -> 返回data(有可能是非同步,所以可以返回一個函式)
    // 非同步 用於OC加密 ,oc加密完成後 JS才可以展示 (此處無例子)
  
    // 混合開發
    // 重定向 - H5 3次
 
   NSString *str = [self.webView stringByEvaluatingJavaScriptFromString:@"showAlert('呵呵')('哈哈')"];
    NSLog(@"%@",str);// js方法返回的資料
  /*
  js 程式碼:
  <script>
        function showAlert(name)
        {
            alert(name);
            return function handle(str)
            {
                alert('我是一個彈窗'+name+str);
                return name + '返回給OC';
            }
        }
    </script>
  */
}
複製程式碼

二、JavaScriptCore

需匯入JavaScriptCore.framework

1、三個關鍵類

  • JSContext.h

    提供了全域性的上下文

  • JSValue.h

    主要是一些型別的強轉(jsvalue轉化成oc型別)

  • JSExport.h

    主要用在js響應oc物件,通過實現協議達到的對映來使用

2、初級使用

- (void)viewDidLoad 
{
    [super viewDidLoad];

    self.webView = [[UIWebView alloc] initWithFrame:self.view.bounds];
    self.webView.delegate = self;
    [self.view addSubview:self.webView];
    
    NSURL *url = [[NSBundle mainBundle] URLForResource:@"index.html" withExtension:nil];;
    NSURLRequest *request = [[NSURLRequest alloc] initWithURL:url];
    [self.webView loadRequest:request];
}

#pragma mark - UIWebViewDelegate
// 載入完成
- (void)webViewDidFinishLoad:(UIWebView *)webView
{
    NSString *titlt = [webView stringByEvaluatingJavaScriptFromString:@"document.title"];
    self.title = titlt;
  
    //JSContext就為其提供著執行環境 H5上下文
    JSContext *jsContext = [self.webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
    [jsContext evaluateScript:@"var arr = ['doomfist','hehe','呵呵']"];// 定義全域性變數
  	//  evaluateScript 可執行一段指令碼
    
    // 清晰 --
  
  	// js程式碼中沒有showMessage函式,但js呼叫了showMessage函式
  	// 類似於kvc的形式,key為showMessage,value為^{}
  	// 所以此處可以定義一個showMessage函式,一旦註冊就會覆蓋js程式碼中的同名方法
  	// 儲存一段程式碼塊
    jsContext[@"showMessage"] = ^{
        NSLog(@"來了");
        // 引數 (JS 帶過來的)
        NSArray *args = [JSContext currentArguments];
        NSLog(@"args = %@",args);
    };
  
  
  	/*
  	js程式碼:
  	function showAlert()
  			{
            alert("hello");
            showMessage("點選了彈框按鈕...",arr);
        }
  	*/
}

複製程式碼

3、JavaScriptCore - 用於解釋oc和js,是oc和js的橋樑

Js的windows中的值通過JavaScriptCore傳入oc的self.windows的runloop中變成jsValue型別

- (void)setupJS
{
  	// JS-OC
    self.jsContext[@"showMessage"] = ^{
        NSLog(@"來了");
        // 引數 (JS 帶過來的)
        NSArray *args = [JSContext currentArguments];
        NSLog(@"args = %@",args);
        NSLog(@"currentThis   = %@",[JSContext currentThis]);
      	// 能夠監控當前Windows資訊 - 沒什麼用
        NSLog(@"currentCallee = %@",[JSContext currentCallee]);
      	// 當前的閉包的資料 - 用處不大
        
        // OC-JS
        NSDictionary *dict = @{@"name":@"呵呵",@"age":@18};
        [[JSContext currentContext][@"ocCalljs"] callWithArguments:@[dict,@"呵呵噠"]];
      	// 調方法並傳參
    };

    //JS-OC
    self.jsContext[@"showDict"] = ^{
        NSLog(@"來了");
        // 引數 (JS 帶過來的)
        NSArray *args = [JSContext currentArguments];
        NSLog(@"args = %@",args);
    };
}

/* js程式碼
  
  			function ocCalljs(dict,str)
  			{
            var name = dict['name'];
            var age  = dict['age'];
            alert(name + age + str);
            // 傳回去
            showDict(dict)
        }
  */
複製程式碼

4、JS操作物件

// JS 操作物件
    JSObject *Object = [[JSObject alloc] init];
    self.jsContext[@"Object"] = Object;
    NSLog(@"Object == %d",[Object getSum:20 num2:40]);

/* js程式碼
				function openAlbumImage()
				{
            getImage();
            Object.letShowImage();
            alert(Object.getS(10,30));
        }
*/

//  ---------
<JSExport>

- (void)letShowImage;
// 協議 - 協議方法 
JSExportAs(getS,-(int)getSum:(int)num1 num2:(int)num2);

//  ---------
- (void)letShowImage
{
    NSLog(@"開啟相簿,上傳圖片");
}

- (int)getSum:(int)num1 num2:(int)num2
{
    NSLog(@"來了");
    // int (nil) - jsvalue (0)
    return num1+num2;
}
//  ---------
複製程式碼

三、WKWebview基本用法

//載入

		WKWebViewConfiguration *configuration = [[WKWebViewConfiguration alloc] init];
		// 在這裡插入下面的部分↓
    self.webView = [[WKWebView alloc] initWithFrame:self.view.frame configuration:configuration];

		//  兩個重要的代理 下面細講
    self.webView.navigationDelegate = self;
    self.webView.UIDelegate = self;

    [self.view addSubview:self.webView];
    
    NSString *urlStr = [[NSBundle mainBundle] pathForResource:@"index.html" ofType:nil];
    NSURL *fileURL = [NSURL fileURLWithPath:urlStr];
    [self.webView loadFileURL:fileURL allowingReadAccessToURL:fileURL];
複製程式碼
// 這裡的內容插入上面↑ 如不插入則不會顯示手機應該顯示的正常頁面
NSString *jScript = @"var meta = document.createElement('meta'); meta.setAttribute('name','viewport'); meta.setAttribute('content','width=device-width'); document.getElementsByTagName('head')[0].appendChild(meta);";
    WKUserScript *wkUScript = [[WKUserScript alloc] initWithSource:jScript injectionTime:WKUserScriptInjectionTimeAtDocumentEnd forMainFrameOnly:YES];
    WKUserContentController *wkUController = [[WKUserContentController alloc] init];
    [wkUController addUserScript:wkUScript];// 新增指令碼
    configuration.userContentController = wkUController;
複製程式碼
// 互動

- (IBAction)didClickRightItemAction:(id)sender 
{
   	// 調方法,傳引數
    NSString *jsStr = [NSString stringWithFormat:@"showAlert('%@')",@"登陸成功"];
  	// oc呼叫js 直接執行指令碼 
    [self.webView evaluateJavaScript:jsStr completionHandler:^(id _Nullable result,NSError * _Nullable error) {
        NSLog(@"%@----%@",result,error);
    }];

}
複製程式碼
// 代理

#pragma mark - WKUIDelegate
- (void)webView:(WKWebView *)webView runJavaScriptAlertPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(void))completionHandler
{
  	// 可以對彈窗執行重定向,改成你需要的型別
    UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"提醒" message:message preferredStyle:UIAlertControllerStyleAlert];
    [alert addAction:[UIAlertAction actionWithTitle:@"知道了" style:UIAlertActionStyleCancel handler:^(UIAlertAction * _Nonnull action) {
        completionHandler();
    }]];
    
    [self presentViewController:alert animated:YES completion:nil];
}


#pragma mark - WKNavigationDelegate

// 載入完成的代理
- (void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation
{
    self.title = webView.title;
    NSString *jsStr2 = @"var arr = [3,'abc']; ";
    [self.webView evaluateJavaScript:jsStr2 completionHandler:^(id _Nullable result,error);
    }];
}

//  攔截
- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler
{
    NSURL *URL = navigationAction.request.URL;
    NSString *scheme = [URL scheme];
    if ([scheme isEqualToString:@"hehe"]) 
    {
        NSString *host = [URL host];
        if ([host isEqualToString:@"jsCallOC"]) 
        {
            NSMutableDictionary *temDict = [self decoderUrl:URL];
            NSString *username = [temDict objectForKey:@"username"];
            NSString *password = [temDict objectForKey:@"password"];
            NSLog(@"%@---%@",username,password);
        }
      	else
        {
            NSLog(@"不明地址 %@",host);
        }
        decisionHandler(WKNavigationActionPolicyCancel);
        return;
    }
    decisionHandler(WKNavigationActionPolicyAllow);
}
複製程式碼
// 另一種oc呼叫js
// 一定要remove,不然會迴圈引用
// self - webView - configuration - userContentController - self
- (void)viewWillAppear:(BOOL)animated
{
    [super viewWillAppear:animated];
    [self.webView.configuration.userContentController addScriptMessageHandler:self name:@"messgaeOC"];
  	// addScriptMessageHandler此處註冊WKScriptMessageHandler代理
}

- (void)viewWillDisappear:(BOOL)animated
{
    [super viewWillDisappear:animated];
    [self.webView.configuration.userContentController removeScriptMessageHandlerForName:@"messgaeOC"];
}

#pragma mark - WKScriptMessageHandler

- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message
{
    if (message.name) 
    {
        // OC 層面的訊息
    }
    NSLog(@"message == %@ --- %@",message.name,message.body);
}

/* js程式碼
				function messgaeHandle()
				{
            window.webkit.messageHandlers.messgaeOC.postMessage("hehe 訊息");
        }
*/
複製程式碼
附:
  wkwebview的代理的常用方法
  
//#pragma mark - WKNavigationDelegate
////請求之前,決定是否要跳轉:使用者點選網頁上的連結,需要開啟新頁面時,將先呼叫這個方法。
//- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler;
////接收到相應資料後,決定是否跳轉
//- (void)webView:(WKWebView *)webView decidePolicyForNavigationResponse:(WKNavigationResponse *)navigationResponse decisionHandler:(void (^)(WKNavigationResponsePolicy))decisionHandler;
////頁面開始載入時呼叫
//- (void)webView:(WKWebView *)webView didStartProvisionalNavigation:(null_unspecified WKNavigation *)navigation;
//// 主機地址被重定向時呼叫
//- (void)webView:(WKWebView *)webView didReceiveServerRedirectForProvisionalNavigation:(null_unspecified WKNavigation *)navigation;
//// 頁面載入失敗時呼叫
//- (void)webView:(WKWebView *)webView didFailProvisionalNavigation:(null_unspecified WKNavigation *)navigation withError:(NSError *)error;
//// 當內容開始返回時呼叫
//- (void)webView:(WKWebView *)webView didCommitNavigation:(null_unspecified WKNavigation *)navigation;
//// 頁面載入完畢時呼叫
//- (void)webView:(WKWebView *)webView didFinishNavigation:(null_unspecified WKNavigation *)navigation;
////跳轉失敗時呼叫
//- (void)webView:(WKWebView *)webView didFailNavigation:(null_unspecified WKNavigation *)navigation withError:(NSError *)error;
//// 如果需要證書驗證,與使用AFN進行HTTPS證書驗證是一樣的
//- (void)webView:(WKWebView *)webView didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition,NSURLCredential *__nullable credential))completionHandler;
////9.0才能使用,web內容處理中斷時會觸發
//- (void)webViewWebContentProcessDidTerminate:(WKWebView *)webView NS_AVAILABLE(10_11,9_0);

複製程式碼

四、WebViewJavaScriptBridge

			self.wjb = [WebViewJavascriptBridge bridgeForWebView:self.webView];
    	// 如果你要在VC中實現 UIWebView的代理方法 就實現下面的程式碼(否則省略)
     	[self.wjb setWebViewDelegate:self];

			//  需要前端(js)適配
			[self.wjb registerHandler:@"jsCallsOC" handler:^(id data,WVJBResponseCallback responseCallback) {
        NSLog(@"currentThread == %@",[NSThread currentThread]);
        NSLog(@"data == %@ -- %@",data,responseCallback);
    }];
複製程式碼
//  內部邏輯
+ (instancetype)bridge:(id)webView 
{
#if defined supportsWKWebView
    if ([webView isKindOfClass:[WKWebView class]]) 
    {
        return (WebViewJavascriptBridge*) [WKWebViewJavascriptBridge bridgeForWebView:webView];
    }
#endif
    if ([webView isKindOfClass:[WVJB_WEBVIEW_TYPE class]]) 
    {
        WebViewJavascriptBridge* bridge = [[self alloc] init];
        [bridge _platformSpecificSetup:webView];
        return bridge;
    }
    [NSException raise:@"BadWebViewType" format:@"Unknown web view type."];
    return nil;
}
複製程式碼
- (IBAction)didClickLeftAction:(id)sender 
{
    dispatch_async(dispatch_get_global_queue(0,0),^{
        [self.wjb callHandler:@"OCCallJSFunction" data:@"oc呼叫JS咯" responseCallback:^(id responseData) {
            NSLog(@"currentThread == %@",[NSThread currentThread]);
            NSLog(@"呼叫完JS後的回撥:%@",responseData);
        }];
    });
}
複製程式碼