IOS原生與HTML交互

跟原生開發(fā)相比,H5的開發(fā)相對來一個成熟的框架和團隊來講在開發(fā)速度和開發(fā)效率上有著比原生很大的優(yōu)勢,至少不用等待審核。那么問題來了,H5與本地原生代碼勢必要有交互的,比如本地上傳一些信息,H5打開本地的頁面,打開本地進行微信等第三方分享等,今天就簡單講一下iOS版中本地的UIWebView,WKWebView與H5的交互。

DEMO地址:點擊下載

一個UIWebView的交互

stringByEvaluatingJavaScriptFromString的使用

UIWebView中在2.0時代就有的類,一直到現(xiàn)在(目前的9.x)都可以使用的WEB容器,它的方法很簡單,在iOS7.0之前JS交互的方法只有一個stringByEvaluatingJavaScriptFromString:

-(nullable NSString*)stringByEvaluatingJavaScriptFromString:(NSString *)script;

使用stringByEvaluatingJavaScriptFromString方法,需要等一個UIWebView中的頁面加載完成之后去調(diào)用。

以下是簡單的使用場景:

1,獲取當前頁面的URL。

-?(void)webViewDidFinishLoad:(UIWebView*)webView?{NSString*currentURL?=?[webView?stringByEvaluatingJavaScriptFromString:@"document.location.href"];}

2,獲取頁面標題:

-?(void)webViewDidFinishLoad:(UIWebView*)webView?{NSString*currentURL?=?[webView?stringByEvaluatingJavaScriptFromString:@"document.location.href"];NSString*title?=?[webview?stringByEvaluatingJavaScriptFromString:@"document.title"];}

3,修改界面元素的值。

NSString?*js_result?=?[webView?stringByEvaluatingJavaScriptFromString:@"document.getElementsByName('q')[0].value='iOS';"];

4,表單提交:

NSString*js_result2?=?[webView?stringByEvaluatingJavaScriptFromString:@"document.forms[0].submit();?"];

這樣就實現(xiàn)了在google搜索關鍵字:“iOS”的功能

5,插入js代碼

上面的功能我們可以封裝到一個js函數(shù)中,將這個函數(shù)插入到頁面上執(zhí)行,代碼如下:

"script.type?=?'text/javascript';""script.text?=?\"function?myFunction()?{?""var?field?=?document.getElementsByName('q')[0];""field.value='iOS';""document.forms[0].submit();""}\";""document.getElementsByTagName('head')[0].appendChild(script);"];[webView?stringByEvaluatingJavaScriptFromString:@"myFunction();"];

看上面的代碼:

a,首先通過js創(chuàng)建一個腳本的標簽,鍵入為'text / javascript'。b

,然后在這個標簽中插入一段字符串,這段字符串就是一個函數(shù):myFunction,這個函數(shù)實現(xiàn)google自動搜索關鍵字的功能,

c,然后使用stringByEvaluatingJavaScriptFromString執(zhí)行myFunction函數(shù)。

如圖6所示,直接調(diào)用JS函數(shù)

上面的函數(shù)調(diào)用是本地注入到H5中,然后本地調(diào)用的,那么如果H5中就有原生的JS函數(shù):myFunction();,那么我們就可以直接執(zhí)行:

[webView?stringByEvaluatingJavaScriptFromString:@"myFunction();"];

JavaScriptCore的框架的使用

會我們發(fā)現(xiàn)stringByEvaluatingJavaScriptFromString的方法調(diào)用太笨拙,在iOS7.0中蘋果公司增加了JS利器JavaScriptCore框架,框架讓目標C和JavaScript的代碼直接的交互變得更加的簡單方便。該框架其實只是基于webkit的中以C / C ++實現(xiàn)的JavaScriptCore的一個包裝。其本身是可以單獨作為一個開發(fā)庫來使用,框架中有完整的數(shù)據(jù)計算邏輯,今天只講H5與本地交互,所以不作涉及,有興趣可以參考:iOS7新JavaScriptCore框架入門介紹。

JavaScriptCore的提供了很多靈活的本地OC與JS的交互方式,通過JSContext狀語從句:JSValue來完成的,JSContext是一個web視圖中的js代碼運行環(huán)境,所有的JS都要交互通過- (JSValue *)evaluateScript:(NSString *)script;方法就可以執(zhí)行JavaScript的一段腳本。

JSValue則可以說是JavaScript的和對象-C之間互換的橋梁,它提供了多種方法可以方便地把JavaScript的數(shù)據(jù)類型轉換成Objective-C的。

具體如何交互我們先來看一段H5代碼:

測試iOS與JS之前的互調(diào)*{font-size:40px;}functionshowAppAlertMsg(message){? ? ? ? ? ? ? ? alert(message);? ? ? ? ? ? }

Test how to use objective-c call js

我們可以看出其中H5實現(xiàn)的JS代碼如下:

functionshowAppAlertMsg(message){? ? ? ? alert(message);? ? }

函數(shù)showAppAlertMsg的英文H5實現(xiàn)可以由我們本地主動調(diào)用的那么相對應的H5主動調(diào)用的方法是:

其中callSystemCamera()的英文H5調(diào)用的函數(shù),我們可以看到在HTML代碼中是找不到這個函數(shù)的實現(xiàn)的,因為這個函數(shù)是需要我們本地去實現(xiàn)。

接下來我們就講一下我們本地如何調(diào)用由H5實現(xiàn)的函數(shù)showAppAlertMsg,本地如何實現(xiàn)能夠右H5端調(diào)用的方法:

1.在頁面加載完成之后獲取JS運行環(huán)境JSContext

- (void)webViewDidFinishLoad:(UIWebView*)webView {JSContext *jsContext = [webView? ? valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];}

2.利用evaluateScript聲明函數(shù)并傳遞參數(shù)執(zhí)行代碼

JSValue *jsValue = [jsContext evaluateScript:@"showAppAlertMsg"];[jsValue callWithArguments:@[@"這是app本地交互文案"]];

第一行代碼是聲明一個函數(shù)showAppAlertMsg,第二行是傳遞參數(shù)并執(zhí)行代碼callWithArguments。

以上是主動調(diào)用H5實現(xiàn)的函數(shù),也可以稱之為傳遞數(shù)據(jù)給H5。

本地實現(xiàn)能夠讓H5調(diào)用的函數(shù):

// 也可以通過下標的方式獲取到方法self.jsContext[@"callSystemCamera"] = ^(){NSLog(@"callSystemCamera");};self.jsContext[@"showAlertMsg"] = ^(NSString*title,NSString*message){NSLog(@"callSystemCamera");};self.jsContext[@"callWithDict"] = ^(idjsonDic){NSLog(@"callWithDict%@",jsonDic);};

一目了然,通過模塊傳遞參數(shù)和方法。

上面是利用的JavaScriptCore的最基本的方法實現(xiàn)JS調(diào)用,另外還有一種方案利用JSExport協(xié)議進行交互。其實可以理解為通過JSExport協(xié)議實現(xiàn)一種把本地的實例綁定為H5中的一個對象,通過這個對象調(diào)用。實例本地方法的一種交互設計

該設計在H5端與上面的是不一樣的:

我們會發(fā)現(xiàn)與之前的H5代碼相比多了一個OCModel,這個在JS中可以理解為一個對象,點擊按鈕之后調(diào)用對象OCModel中的函數(shù)OCModel.callSystemCamera,那么那個對象如何由本地綁定呢。

1.首先聲明一個JSExport協(xié)議,該協(xié)議需要聲明剛才H5中的函數(shù):

#import@protocolJavaScriptObjectiveCDelegate// JS調(diào)用此方法來調(diào)用OC的系統(tǒng)相冊方法- (void)callSystemCamera;// 在JS中調(diào)用時,函數(shù)名應該為showAlertMsg(arg1, arg2)// 這里是只兩個參數(shù)的。- (void)showAlert:(NSString*)title msg:(NSString*)msg;// 通過JSON傳過來- (void)callWithDict:(NSDictionary*)params;// JS調(diào)用Oc,然后在OC中通過調(diào)用JS方法來傳值給JS。- (void)jsCallObjcAndObjcCallJsWithDict:(NSDictionary*)params;@end

2.新建一個類,該類實現(xiàn)上面的協(xié)議

@interfaceNLJsObjCModel:NSObject@property(nonatomic,weak) JSContext *jsContext;@property(nonatomic,weak)UIWebView*webView;@end

實現(xiàn)文件:

#import"NLJsObjCModel.h"

@implementationNLJsObjCModel

- (void)callWithDict:(NSDictionary*)params {

NSLog(@"Js調(diào)用了OC的方法,參數(shù)為:%@", params);

}

// Js調(diào)用了callSystemCamera

- (void)callSystemCamera {

NSLog(@"JS調(diào)用了OC的方法,調(diào)起系統(tǒng)相冊");

// JS調(diào)用后OC后,又通過OC調(diào)用JS,但是這個是沒有傳參數(shù)的

JSValue *jsFunc =self.jsContext[@"jsFunc"];[jsFunc callWithArguments:nil];}

- (void)jsCallObjcAndObjcCallJsWithDict:(NSDictionary*)params {

NSLog(@"jsCallObjcAndObjcCallJsWithDict was called, params is %@", params);

// 調(diào)用JS的方法

JSValue *jsParamFunc =self.jsContext[@"jsParamFunc"];[jsParamFunc callWithArguments:@[@{@"age": @10,@"name":@"lili",@"height": @158}]];}- (void)showAlert:(NSString*)title msg:(NSString*)msg {dispatch_async(dispatch_get_main_queue(), ^{UIAlertView *a = [[UIAlertView alloc] initWithTitle:title message:msg delegate:nilcancelButtonTitle:@"Ok"otherButtonTitles:nil,nil];[a show];});}@end

3.將NLJsObjCModel實例對象綁定到JS中:

#pragma mark - UIWebViewDelegate

- (void)webViewDidFinishLoad:(UIWebView*)webView {

self.jsContext= [webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];

// 通過模型調(diào)用方法,這種方式更好些。

NLJsObjCModel *model? = [[NLJsObjCModel alloc] init];

self.jsContext[@"OCModel"] = model;model.jsContext=self.jsContext;model.webView=self.webView;self.jsContext.exceptionHandler= ^(JSContext *context, JSValue *exceptionValue) {context.exception= exceptionValue;NSLog(@"異常信息:%@", exceptionValue);};}

到此結束,H5按鈕點擊之后就可以通過JSExport協(xié)議傳遞跟本地對象,本地對象就能收到相應。

WKWebView的交互使用

iOS8上以后,蘋果推出了新框架Wekkit,提供了替換的UIWebView的組件WKWebView。

先看下WKWebView的特性:

1.在性能,穩(wěn)定性,功能方面有很大提升(最直觀的體現(xiàn)就是加載網(wǎng)頁是占用的內(nèi)存,模擬器加載百度與開源中國網(wǎng)站時間,WKWebView占用23M,而UIWebView占用

85M);

2.允許JavaScript的Nitro庫加載并使用(UIWebView中限制);

3.支持了更多的HTML5特性;

4.高達60fps的滾動刷新率以及內(nèi)置手勢;

5.將UIWebViewDelegate與UIWebView重構成了14類與3個協(xié)議(查看下載蘋果官方文檔);

WKWebView的使用呢與UIWebView相同,不同的是回調(diào)和代理方法。這里簡單介紹一下。

## WKNavigationDelegate

// 頁面開始加載時調(diào)用

- (void)webView:(WKWebView *)webView didStartProvisionalNavigation:(WKNavigation *)navigation;

// 當內(nèi)容開始返回時調(diào)用

- (void)webView:(WKWebView *)webView didCommitNavigation:(WKNavigation *)navigation;

// 頁面加載完成之后調(diào)用

- (void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation;

// 頁面加載失敗時調(diào)用

- (void)webView:(WKWebView *)webView didFailProvisionalNavigation:(WKNavigation *)navigation;

頁面跳轉的代理方法:

// 接收到服務器跳轉請求之后調(diào)用

- (void)webView:(WKWebView *)webView didReceiveServerRedirectForProvisionalNavigation:(WKNavigation *)navigation;

// 在收到響應后,決定是否跳轉

- (void)webView:(WKWebView *)webView decidePolicyForNavigationResponse:(WKNavigationResponse *)navigationResponse decisionHandler:(void(^)(WKNavigationResponsePolicy))decisionHandler;

// 在發(fā)送請求之前,決定是否跳轉

- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void(^)(WKNavigationActionPolicy))decisionHandler;

新的WKUIDelegate協(xié)議

這個協(xié)議主要用于WKWebView處理網(wǎng)頁界面的三種提示框(警告框,確認框,輸入框),下面是警告框的例子:

/** *? web界面中有彈出警告框時調(diào)用 * *@paramwebView? ? ? ? ? 實現(xiàn)該代理的webview *@parammessage? ? ? ? ? 警告框中的內(nèi)容 *@paramframe? ? ? ? ? ? 主窗口 *@paramcompletionHandler 警告框消失調(diào)用 */- (void)webView:(WKWebView *)webView runJavaScriptAlertPanelWithMessage:(NSString *)message initiatedByFrame:(void(^)())completionHandler;

動態(tài)加載并運行JS代碼(WKUserScript)

用于在客戶端內(nèi)部加入JS代碼,并執(zhí)行,示例如下

// 圖片縮放的js代碼NSString*js =@"var count = document.images.length;for (var i = 0; i < count; i++) {var image = document.images[i];image.style.width=320;};window.alert('找到' + count + '張圖');";

// 根據(jù)JS字符串初始化WKUserScript對象WKUserScript *script = [[WKUserScript alloc] initWithSource:js injectionTime:WKUserScriptInjectionTimeAtDocumentEnd forMainFrameOnly:YES];

// 根據(jù)生成的WKUserScript對象,初始化WKWebViewConfigurationWKWebViewConfiguration *config = [[WKWebViewConfiguration alloc] init];

[config.userContentControlleraddUserScript:script];

_webView = [[WKWebView alloc] initWithFrame:self.view.boundsconfiguration:config];[_webView loadHTMLString:@""baseURL:nil];[self.viewaddSubview:_webView];

五,webView執(zhí)行JS代碼

用戶調(diào)用用JS寫過的代碼,一般指服務端開發(fā)的

//javaScriptString是JS方法名,completionHandler是異步回調(diào)block[self.webView evaluateJavaScript:javaScriptString completionHandler:completionHandler];

六,JS調(diào)用應用過注冊的方法

在WKWebView里面注冊供JS調(diào)用的方法,是通過WKUserContentController類下面的方法:

- (void)addScriptMessageHandler:(id)scriptMessageHandlername:(NSString *)name;

scriptMessageHandler是代理回調(diào),JS調(diào)用的名稱方法后,OC會調(diào)用scriptMessageHandler指定的對象。

JS在調(diào)用OC注冊方法的時候要用下面的方式:

window.webkit.messageHandlers..postMessage()

注意,名稱(方法名)是放在中間的,在這里個人理解該名稱可以理解為一個標識,該標識將OC本地的實例對象綁定到JS中,將消息體只能是一個對象,如果要傳多個值,需要封裝成數(shù)組,或者字典整個示例如下:

//OC注冊供JS調(diào)用的方法[[_webView configuration].userContentController addScriptMessageHandler:self name:@"app"];//OC在JS調(diào)用方法做的處理- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message{NSLog(@"JS 調(diào)用了 %@ 方法,傳回參數(shù) %@",message.name,message.body);}//JS調(diào)用window.webkit.messageHandlers.app.postMessage(null);

最后:

簡單做一個總結,在iOS7以前我們只能使用UIWebView,所以在js交互中只能通過stringByEvaluatingJavaScriptFromString進行本地調(diào)用H5,而H5端調(diào)用本地多采用URLSchemes方式(解耦---混合H5跨平臺性思考) ,

iOS7之后使用UIWebView則可以使用JavaScriptCore框架,通過JSContext進行JS交互。iOS8

之后可以使用WKWebView本身的框架,畢竟WKWebView本身已經(jīng)優(yōu)化很多,并且提供了更多的方法和協(xié)議,不過注意一點的是在WKWebView中鑒于一些線程和進程的問題是無法獲取JSContext的。

最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容