1. 程式人生 > >iOS 開發中OC 與 JS的互動

iOS 開發中OC 與 JS的互動

iOS原生應用和web頁面的互動有iOS7之後的JavaScriptCore、攔截協議、第三方框架WebViewJavaScriptBridge、iOS8之後的WKWebView幾種方法,這一章我們主要講解JavaScriptCore和攔截協議這兩種辦法。WebViewJavaScriptBridge是基於攔截協議進行的封裝,使用也不如JavaScriptCore方便本文不做細講。WKWebView是iOS8之後推出的,還沒有成為主流使用,所以本篇文章也不做詳細敘述。

Objective-C呼叫JavaScript程式碼

// UIWebView的方法
- (nullable NSString *)stringByEvaluatingJavaScriptFromString
:(NSString *)script; // JavaScriptCore中JSContext的方法 - (JSValue *)evaluateScript:(NSString *)script; - (JSValue *)evaluateScript:(NSString *)script withSourceURL:(NSURL *)sourceURL

用這些方法執行復雜的一大段js程式碼也是沒有必要的,在一些場景下還是比較實用的,比如

// 獲取當前頁面的title
NSString *title = [webview stringByEvaluatingJavaScriptFromString:
@"document.title"]; // 獲取當前頁面的url NSString *url = [webview stringByEvaluatingJavaScriptFromString:@"document.location.href"];

JavaScriptCore概述
JavaScriptCore這個框架是iOS7之後蘋果推出的,方便了開發者的使用,讓web頁面和iOS本地原生應用互動起來更加簡單。

web前端
在與前端互動過程中,需要與前端開發人員溝通好傳值、方法名等,然後移動端做適配。這裡以傳值、呼叫本地alert、分享為例來講解,用webView載入HTML檔案,這裡用的是本地HTML名字為JavaScriptCore.html,程式碼如下:

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title> 來自html中的jsCallOC標題</title>
</head>
<body>
<div style="margin-top: 20px">
<h2>JavaScript與OC的互動</h2>
<input type="button" value="Native傳值" onclick="Native.callme('jS開始呼叫OC本地Native咯')">
</div>
<div>
<input type="button" value="oc原生Alert" onclick="deliverValue('來自HTML中的Alert資訊')">
</div>
<div>
<input type="button" value="Share"   onclick="callShare()">
</div>
<script>
var alertShowIn = function(str) {

    alert(str);
}
var callShare = function() {
var shareUrl = "http://image.baidu.com/search/detail?ct=503316480&z=&tn=baiduimagedetail&ipn=d&ie=utf-8&in=24401&cl=2&lm=-1&st=-1&step_word=&rn=1&cs=&ln=1998&fmq=1402900904181_R&ic=0&s=&se=1&sme=0&tab=&width=&height=&face=0&is=&istype=2&ist=&jit=&fr=ala&ala=1&alatpl=others&pos=1&pn=1&word=圖片%20動漫卡通&di=0&os=1199087710,2399135616&pi=0&objurl=http%3A%2F%2Fv.flash.beijingww.com%2Fcomic%2Fwallpaper%2Fjiqimao%2F15.jpg"
Native.share(shareUrl);
}
var shareCallBack = function(){
alert('回撥js分享success');
}
</script>
</body>
</html>

JavaScriptCore.html程式碼解釋如下:

Native是iOS本地要注入的一個物件,也就是web頁面與原生應用的一個橋接。頁面上定義了Native傳值、oc原生Alert、Share三個按鈕,點選Native傳值首先通過Native這個橋樑呼叫本地的方法- (void)callme:(NSString )string並傳入引數;點選oc原生Alert按鈕通過 self.context[@"deliverValue"] = ^(NSString message) 的block形式直接呼叫;點選Share按鈕會先呼叫html本地檔案中的JavaScrip的function方法callShare,這裡將分享的url引數傳給share方法,然後再通過Native橋樑去呼叫原生應用的本地方法- (void)share:(NSString *)shareUrl,而shareCallBack為分享成功的回撥方法,也就是原生方法呼叫後js的回撥方法。

iOS移動端
JavaScriptCore中web頁面呼叫原生應用的方法可以用Delegate或Block兩種方法。

JavaScriptCore中類及協議:

JSContext:給JavaScript提供執行的上下文環境
JSValue:JavaScript和Objective-C資料和方法的橋樑
JSExport:協議,如果採用協議的方法互動,自己定義的協議必須遵守此協議

ViewController中的程式碼

#import <JavaScriptCore/JavaScriptCore.h>
@protocol JSObjectDelegate <JSExport>
-(void)callme:(NSString *)string;
-(void)share:(NSString *)shareUrl;
@end

@interface JSCallOCViewController ()   <UIWebViewDelegate, JSObjectDelegate>
@property(nonatomic, strong) UIWebView *webView;
@property(nonatomic, strong) JSContext *context;
@end

@implementation JSCallOCViewController
-(void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view from its nib.
self.title = @"js call oc";
self.view.backgroundColor = [UIColor whiteColor];

self.webView = [[UIWebView alloc] initWithFrame:CGRectMake(0, 0, kScreenWidth, kScreenHeight)];
[self.view addSubview:self.webView];

NSString *path = [[NSBundle mainBundle] pathForResource:@"JavaScriptCore" ofType:@"html"];

NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL fileURLWithPath:path]];

self.webView.delegate = self;
[self.webView loadRequest:request];
}

#pragma mark - UIWebViewDelegate

-(void)webViewDidFinishLoad:(UIWebView *)webView

{
//獲取html title設定導航欄 title
self.title = [webView stringByEvaluatingJavaScriptFromString:@"document.title"];
self.context = [webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
//捕捉異常回調
self.context.exceptionHandler = ^(JSContext context, JSValue exceptionValue) {
context.exception = exceptionValue;
NSLog(@"異常資訊: %@",exceptionValue);
};

//通過JSExport協議關聯Native的方法
self.context[@"Native"] = self;

//通過block形式關聯JavaScript中的函式
__weak typeof(self) weakSelf = self;

self.context[@"deliverValue"] = ^(NSString *message) {

    __strong typeof(self) strongSelf = weakSelf;

    dispatch_async(dispatch_get_main_queue(), ^{

        UIAlertController *alertControl = [UIAlertController alertControllerWithTitle:@"this is a message" message:message preferredStyle:UIAlertControllerStyleActionSheet];
        UIAlertAction *cancelAction = [UIAlertAction actionWithTitle:@"確定" style:UIAlertActionStyleCancel handler:^(UIAlertAction * _Nonnull action) {

        }];
        [alertControl addAction:cancelAction];
        [strongSelf.navigationController presentViewController:alertControl animated:YES completion:nil];
    });


};

}

-(void)webView:(UIWebView *)webView didFailLoadWithError:(NSError *)error
{
NSLog(@"error == %@",error);
}

#pragma mark - JSExport Methods
-(void)callme:(NSString *)string
{
NSLog(@"%@",string);
}

-(void)share:(NSString *)shareUrl
{
NSLog(@"分享的url=%@",shareUrl);
JSValue *shareCallBack = self.context[@"shareCallBack"];
[shareCallBack callWithArguments:nil];
}

程式碼解釋:
自定義JSObjectDelegate協議的時候必須遵守JSExport這個協議,這些自定義的協議中的方法是留給web頁面的介面方法。在webView載入完畢的時候獲取JavaScript執行的上下文環境,然後注入橋接的物件Native,物件self就是此控制器,控制器遵守此自定義協議實現協議中的相對應的方法。當JavaStript呼叫完原生本地應用的方法後,再回調JavaScript中對應的方法,從而實現了Web頁面和原生本地應用之前的通訊。

OC呼叫JS效果圖:


螢幕快照 2016-09-22 下午3.17.26.png


JS呼叫OC程式碼

-(void)caculateButtonAction:(id)sender
{
NSNumber *inputNumber = [NSNumber numberWithInteger:[textField.text integerValue]];
JSValue *function = [self.context objectForKeyedSubscript:@"factorial"];
JSValue *result = [function callWithArguments:@[inputNumber]];

resultL.text = [NSString stringWithFormat:@"%@",[result toNumber]];

JS呼叫OC效果圖:


螢幕快照 2016-09-22 下午3.28.39.png

ps:注意JavaStript呼叫本地方法是在子執行緒中執行的,在回撥JavaStript方法的時候最好與剛開始呼叫此方法的執行緒保持同一個,要考慮執行緒之間的切換。

攔截協議

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
</head>
<body>
<div>
    <input type="button" value="changeWindow" onclick="callMethod()">
</div>

<script>
function callMethod() {
    window.location.href = 'wjika://changeWindow';
}
</script>
</body>
</html>

html程式碼解釋:
點選changeWindow按鈕呼叫網頁callMethod方法,方法的實現是window.location.href改變主視窗的指向,也就是發出一個連結為wjika://changeWindow的請求,從而將內容傳遞給原生應用,請求中可以傳遞原生應用需要的引數。在原生應用中我們可以攔截這個請求,根據內容去判斷JavaStript想要我們做的事情,這就實現了web頁面和原生應用本地之間的互動。

viewController中的程式碼

-(BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType
{
NSString *url = request.URL.absoluteString;
if ([url rangeOfString:@"wjika://"].location != NSNotFound) { 
    // url的協議頭是wjika
    NSLog(@"改變視窗指向");
    return YES;
}
return No;
}

在webView的shouldStartLoadWithRequest代理方法中去攔截自定義的協議wjika://如果是此協議則去做JavaStript想要移動端做的事情,呼叫原生應用的方法,注意協議的頭等欄位是前端和移動端事先約定好的。

ps:攔截協議適合一些簡單的情況,複雜的互動需要相互傳遞引數的比較麻煩,並且不能回撥JavaScript的方法。另外研究攔截協議的朋友可以看看WebViewJavaScriptBridge這個第三方,是對攔截協議的封裝。 JavaScriptCore使用起來比較簡單,方便web端和移動端的統一。iOS8推出的WKWebView會逐漸成為主流,這個功能更強大。僅供交流學習,歡迎各位同學指正。
Ps:本文專案demo https://github.com/maying1992/iOS-JavaScriptCore.git