iOS Objective-C与Javascript交互总结
2017-01-09 本文已影响0人
倾世圣伊
一、在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