iOS Objective-C与Javascript交互总结

一、在iOS7之前,对JavaScript操作只能使用UIWebView中一个方法stringByEvaluatingJavaScriptFromString,JavaScript对Objective-C的回调都是基于URL的拦截进行操作。
- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    self.webView = [[UIWebView alloc] initWithFrame:self.view.bounds];
    self.webView.delegate = self;
    [self.view addSubview:self.webView];
    NSURL * url = [NSURL URLWithString:@"https://www.baidu.com"];
    NSURLRequest * request = [[NSURLRequest alloc] initWithURL:url];

    [self.webView loadRequest:request];
}
1.stringByEvaluatingJavaScriptFromString在UIWebView加载完成时使用,以下是简单使用场景:
#pragma mark -- UIWebViewDelegate
- (void)webViewDidFinishLoad:(UIWebView *)webView {
    //当前页面URL
    NSString * currentUrl = [webView stringByEvaluatingJavaScriptFromString:@"document.location.href"];
    //当前页面的title
    NSString * title = [webView stringByEvaluatingJavaScriptFromString:@"document.title"];
    
    //填充文本输入框的内容
    [self.webView stringByEvaluatingJavaScriptFromString:@"document.getElementById('index-kw').value = 'apple';"];
    //模拟点击按钮
    [self.webView stringByEvaluatingJavaScriptFromString:@"document.getElementById('index-bn').click();"];
}
2.JavaScript对Objective-C的回调基于URL的拦截进行操作:
#pragma mark - UIWebViewDelegate
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType {
    NSURL * url = request.URL;
    NSString * scheme = [URL scheme];
    if ([scheme isEqualToString:@"XXXX"]) {
        //do something
        return NO;
    }
    return YES;
}
二、iOS7之后苹果推出JavaScriptCore框架,该框架让Objective-C与Javascript代码的交互变得更加简单方便。
JavaScriptCore下的类和协议:
  • JSContext:JS执行的环境,同时也通过JSVirtualMachine管理着所有对象的生命周期,每个JSValue都和JSContext相关联并且强引用context。
  • JSValue:JavaScript和Object-C之间互换的桥梁。
  • JSManagedValue:JS和OC对象的内存管理辅助对象,由于JS内存管理是垃圾回收,并且JS中的对象都是强引用,而OC是引用计数。如果双方相互引用,势必会造成循环引用,而导致内存泄露。我们可以用JSManagedValue保存JSValue来避免。
  • JSVirtualMachine:JS运行的虚拟机,有独立的堆空间和垃圾回收机制,处理线程相关,使用较少。
  • JSExport:一个协议,如果JS对象想直接调用OC对象里面的方法和属性,那么这个OC对象只要实现这个JSExport协议就可以了。
1.Objective-C调用Javascript:
//Objective-C -> JavaScript
JSContext * context = [[JSContext alloc] init];
[context evaluateScript:@"function maxNum(x, y) { return x > y ? x : y}"];
JSValue * max = context[@"maxNum"];
NSLog(@"function:%@",max);
2.Javascript调用Objective-C,使用block或JSExport protocol:

使用block:

self.context = [[JSContext alloc] init];
self.context[@"maxNum"] = ^(NSInteger a, NSInteger b) {
        NSLog(@"%@",[NSNumber numberWithInteger:(a > b ? a : b)]);
};
[self.context evaluateScript:@"maxNum(11,13)"];

使用JSExport protocol:

//JSProtocolObj.h
//定义一个JSExport protocol
@protocol JSExportTest <JSExport>
//用宏转换下,将JS函数名字指定为maxNum;
JSExportAs(maxNum, - (NSInteger)maxNum:(NSInteger)x y:(NSInteger)y);
- (NSInteger)add:(NSInteger)a b:(NSInteger)b;
@property (nonatomic, assign) NSInteger sum;
@end

@interface JSProtocolObj : NSObject<JSExportTest>
@end

//JSProtocolObj.m
@implementation JSProtocolObj
@synthesize sum = _sum;
- (NSInteger)maxNum:(NSInteger)x y:(NSInteger)y {
    return x > y ? x : y;
}
//实现协议方法
- (NSInteger)add:(NSInteger)a b:(NSInteger)b {
    return a+b;
}
//重写setter方法方便打印信息,
- (void)setSum:(NSInteger)sum {
    NSLog(@"--%@", @(sum));
    _sum = sum;
}
@end

ViewController中调用:

//创建context
self.context = [[JSContext alloc] init];
//设置异常处理
self.context.exceptionHandler = ^(JSContext * context,JSValue *exception) {
      [JSContext currentContext].exception = exception;
      NSLog(@"exception:%@",exception);
};
_objModel = [JSProtocolObj new];
self.context[@"OCModel"] = self.objModel;
[self.context evaluateScript:@"OCModel.sum = OCModel.addB(2,3)"];
[self.context evaluateScript:@"OCModel.sum = OCModel.maxNum(12,18)"];
3.UIWebView中Objective-C与Javascript相互调用:
//test.html
<!DOCTYPE html>
<html lang="en">
    <meta charset="UTF-8">
    <title> my javascript </title>
</head>
    <style>
        p{
            padding-left:100px;
            color: aqua;
        }
    </style>
<body>
<h1>
    <script>
        document.write("hello world");
        document.write("<h1>这是一个标题</h1>");
        document.write("<p>这是一个段落</p>");
    </script>
</h1>

<p1 id="demo" >
    JavaScript 能改变 HTML 元素的内容。
</p1>
<button type="button" onclick="myFunction()">元素改变</button>
<button type="button" onclick="loginClick()">点击登录</button>
<button type="button" onclick="callSystemCamera()">调用系统照相机</button>
<script>
    function myFunction() {
        x=document.getElementById("demo");  // 找到元素
        x.innerHTML="Hello JavaScript!";    // 改变内容
    }
    function loginClick() {
        logIn("18200133537","ls123321");
    }
    function callSystemCamera() {
        //调用系统照相机
    }
    //
    function userInterface() {
        x=document.getElementById("demo");  // 找到元素
        x.innerHTML="oc-->js 内容改变";    // 改变内容
        //alert("what's your name?");
        //if (confirm("确定跳转")) {
            //window.open('http://www.baidu.com')
        //}
        x.innerHTML=prompt("你的名字是什么?","胡杨魂");
    }
</script>
</body>
</html>

ViewController中调用:

#import "ViewController.h"
#import <JavaScriptCore/JavaScriptCore.h>
@interface HTMLViewController ()<UIWebViewDelegate>
@property (nonatomic, strong) UIWebView * webView;
@property (nonatomic, strong) JSContext * jsContext;
- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    self.webView = [[UIWebView alloc] initWithFrame:self.view.bounds];
    self.webView.delegate = self;
    [self.view addSubview:self.webView];
    NSString* path = [[NSBundle mainBundle] pathForResource:@"onecat" ofType:@"html"];
    NSURL* url = [NSURL fileURLWithPath:path];
    NSURLRequest* request = [NSURLRequest requestWithURL:url] ;
    [self.webView loadRequest:request];
}
#pragma mark -- UIWebViewDelegate
- (void)webViewDidFinishLoad:(UIWebView *)webView {
    //只要我们能拿到 UIWebView 的实例,然后就可以直接使用 KVC 的方法来获取它的 JSContext 对象
    _jsContext = [webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
    _jsContext[@"callSystemCamera"] = ^() {
        NSLog(@"js-->oc 现在可以打开照相机");
    };
    _jsContext[@"logIn"] = ^() {
        NSLog(@"现在开始登录");
        NSArray * param = [JSContext currentArguments];
        for (JSValue * jsValue in param) {
            NSLog(@"%@",jsValue);
        }
    };
    //调用Javascript中方法 userInterface();
    [_jsContext evaluateScript:@"userInterface()"];
}

三、iOS8以后苹果推出了新框架WebKit,提供了替换UIWebView的组件WKWebView,大幅提升了性能、功能和稳定性,大大降低了加载网页时候占用的内存空间。

//test.html
<!DOCTYPE html>
<html lang="en">
    <meta charset="UTF-8">
    <title> my javascript </title>
</head>
    <style>
        p{
            padding-left:100px;
            color: aqua;
        }
    </style>
<body>
<h1>
    <script>
        document.write("hello world");
        document.write("<h1>这是一个标题</h1>");
        document.write("<p>这是一个段落</p>");
    </script>
</h1>

<p1 id="demo" >
    JavaScript 能改变 HTML 元素的内容。
</p1>
<button type="button" onclick="myFunction()">元素改变</button>
<button type="button" onclick="loginClick()">点击登录</button>
<button type="button" onclick="callSystemCamera()">调用系统照相机</button>
<script>
    function myFunction() {
        x=document.getElementById("demo");  // 找到元素
        x.innerHTML="Hello JavaScript!";    // 改变内容
    }
    function loginClick() {
        window.webkit.messageHandlers.login.postMessage(['18200133533', 'ls123321'])
    }
    function callSystemCamera() {
        //调用系统照相机
        window.webkit.messageHandlers.callCamera.postMessage(null)
    }
    //
    function userInterface() {
        x=document.getElementById("demo");  // 找到元素
        x.innerHTML="oc-->js 内容改变";    // 改变内容
        //alert("what's your name?");
        //if (confirm("确定跳转")) {
            //window.open('http://www.baidu.com')
        //}
        x.innerHTML=prompt("你的名字是什么?","胡杨魂");
    }
</script>
</body>
</html>

WKWebViewController中调用:

#import "WKWebViewController.h"
#import <WebKit/WebKit.h>
@interface WKWebViewController ()<WKScriptMessageHandler,WKUIDelegate>
@property (nonatomic, strong) WKWebView * webView;
@end

@implementation WKWebViewController
- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    //在创建WKWebView之前,需要先创建配置对象,用于做一些配置:
    WKWebViewConfiguration * config = [[WKWebViewConfiguration alloc] init];
    config.preferences.minimumFontSize = 18;
    self.webView = [[WKWebView alloc] initWithFrame:CGRectMake(0, 0, self.view.bounds.size.width, self.view.bounds.size.height) configuration:config];
    [self.view addSubview:self.webView];
    NSString* path = [[NSBundle mainBundle] pathForResource:@"onecat" ofType:@"html"];
    NSURL* url = [NSURL fileURLWithPath:path];
    NSURLRequest* request = [NSURLRequest requestWithURL:url] ;
    [self.webView loadRequest:request];
    
    //而如果需要与在JS调用alert、confirm、prompt函数时,通过JS原生来处理,而不是调用JS的alert、confirm、prompt函数,那么需要设置UIDelegate,在得到响应后可以将结果反馈到JS端:
    self.webView.UIDelegate = self;
    //WKUserContentController 用于给JS注入对象的,注入对象后,JS端就可以使用:
    WKUserContentController * cv = config.userContentController;
     //注入JS对象名称,JS调用OC 添加处理脚本
    [cv addScriptMessageHandler:self name:@"login"];
    [cv addScriptMessageHandler:self name:@"callCamera"];
    //js端 window.webkit.messageHandlers.<name>.postMessage(<messageBody>)
}
#pragma mark - WKScriptMessageHandler
- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message {
    if ([message.name isEqualToString:@"login"]) {
        NSLog(@"js-->oc 开始登录:%@",message.body);
    }
    if ([message.name isEqualToString:@"callCamera"]) {
        NSLog(@"js-->oc 现在打开系统相机");
    }
}
#pragma mark -- WKUIDelegate
#pragma mark -- JS调用alert、confirm、prompt时,不采用JS原生提示,而是使用iOS原生来实现
- (void)webView:(WKWebView *)webView runJavaScriptAlertPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(void))completionHandler {
    UIAlertController * alert = [UIAlertController alertControllerWithTitle:@"温馨提示" message:@"js调用alert" preferredStyle:UIAlertControllerStyleAlert];
    UIAlertAction * action = [UIAlertAction actionWithTitle:@"确定" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
        completionHandler();
    }];
    [alert addAction:action];
    [self presentViewController:alert animated:YES completion:nil];
}
- (void)webView:(WKWebView *)webView runJavaScriptConfirmPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(BOOL result))completionHandler {
    UIAlertController * alert = [UIAlertController alertControllerWithTitle:@"温馨提示" message:@"js调用Confirm" preferredStyle:UIAlertControllerStyleAlert];
    UIAlertAction * actionDone = [UIAlertAction actionWithTitle:@"确定" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
        completionHandler(YES);
    }];
    [alert addAction:actionDone];
    UIAlertAction * actionCancel = [UIAlertAction actionWithTitle:@"确定" style:UIAlertActionStyleCancel handler:^(UIAlertAction * _Nonnull action) {
        completionHandler(NO);
    }];
    [alert addAction:actionCancel];
    [self presentViewController:alert animated:YES completion:nil];
}
- (void)webView:(WKWebView *)webView runJavaScriptTextInputPanelWithPrompt:(NSString *)prompt defaultText:(nullable NSString *)defaultText initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(NSString * _Nullable result))completionHandler {
    
    UIAlertController * alert = [UIAlertController alertControllerWithTitle:@"温馨提示" message:@"js调用Prompt" preferredStyle:UIAlertControllerStyleAlert];
    [alert addTextFieldWithConfigurationHandler:^(UITextField * _Nonnull textField) {
        textField.textColor = [UIColor greenColor];
        textField.text = defaultText;
        textField.placeholder = @"请输入你的名字";
    }];
    UIAlertAction * actionCancel = [UIAlertAction actionWithTitle:@"确定" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
        completionHandler(alert.textFields.firstObject.text);
    }];
    [alert addAction:actionCancel];
    [self presentViewController:alert animated:YES completion:nil];
    NSLog(@"====%@",defaultText);
}
@end
四、使用第三方工具类:WebViewJavascriptBridge:略(暂未使用过)
结束
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,456评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,370评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,337评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,583评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,596评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,572评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,936评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,595评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,850评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,601评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,685评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,371评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,951评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,934评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,167评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,636评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,411评论 2 342

推荐阅读更多精彩内容