在写 JavaScript 的时候,可以使用一个叫做 window 的对象,像是我们想要从现在的网页跳到另外一个网页的时候,就会去修改 window.location.href 的位置;在我们的 Objective C 程序码中,如果我们可以取得指定的 WebView 的指标,也就可以拿到这个出现在 JavaScript 中的 window 对象,也就是 [webView windowScriptObject]。
var location = window.location.href;
用 ObjC 就可以这麽调用:
NSString *location = [[webView windowScriptObject] valueForKeyPath:@"location.href"];
如果我们要设定 window.location.href,要求开启另外一个网页,在 JS 里头:
window.location.href ='http://spring-studio.net';
Obj C:
[[webView windowScriptObject] setValue:@"http://spring-studio.net" forKeyPath:@"location.href"];
- JS 虽然是 OO,但是并没有 class,所以将 JS 对象传到 Obj C 程序里头,除了基本字串会转换成 NSString、基本数字会转成 NSNumber,像是 Array 等其他对象,在 Obj C 中,都是 WebScriptObject 这个 Class。意思就是,JS 的 Array 不会帮你转换成 NSArray。
- 从 JS 里头传一个空对象给 Obj C 程序,用的不是 Obj C 里头原本表示「没有东西」的方式,像是 NULL、nil、NSNull 等,而是专属 WebKit 使用的 WebUndefined。
所以,如果我们想要看一个 JS Array 里头有什麽东西,就要先取得这个对象里头叫做 length 的 value,然后用 webScriptValueAtIndex: 去看在该 index 位置的内容。假如我们在 JS 里头这样写:
varJSArray = {'zonble','dot','net'}; for(vari = 0; i < JSArray.length; i++) { console.log(JSArray[i]); }
Obj C 里头就会变成这样:
WebScriptObject *obj = (WebScriptObject *)JSArray; NSUIntegercount = [[obj valueForKey:@"length"] integerValue]; NSMutableArray*a = [NSMutableArrayarray]; for(NSUIntegeri = 0; i < count; i++) { NSString*item = [obj webScriptValueAtIndex:i]; NSLog(@"item:%@", item); }
用 Objective C 调用 JavaScript function
要用 Obj C 调用网页中的 JS function,大概有几种方法。第一种是直接写一段跟你在网页中会撰写的 JS 一模一样的程序,叫 windowScriptObject 用 evaluateWebScript: 执行。例如,我们想要在网页中产生一个新的 JS function,内容是:
functionx(x) { returnx + 1; }
所以在 Obj C 中可以这样写:
[[webView windowScriptObject] evaluateWebScript:@"function x(x) { return x + 1;}"];
接下来我们就可以调用 window.x():
NSNumber *result = [[webView windowScriptObject] evaluateWebScript:@"x(1)"]; NSLog(@"result:%d", [result integerValue]);// Returns 2
由于在 JS 中,每个 funciton 其实都是对象,所以我们还可以直接取得 window.x 叫这个对象执行自己。在 JS 里头如果这样写:
window.x.call(window.x, 1);
Obj C 中便是这样:
WebScriptObject *x = [[webView windowScriptObject] valueForKey:@"x"]; NSNumber*result = [x callWebScriptMethod:@"call"withArguments:[NSArrayarrayWithObjects:x, [NSNumbernumberWithInt:1],nil]];
DOM
document.querySelector('#s').focus();
Obj C:
DOMDocument *document = [[webView mainFrame] DOMDocument]; [[document querySelector:@"#s"] callWebScriptMethod:@"focus"withArguments:nil];
用 JavaScript 存取 Objective C 的 Value
- (void)webView:(WebView *)sender didClearWindowObject:(WebScriptObject *)windowObject forFrame:(WebFrame *)frame { [windowObject setValue:selfforKey:@"controller"]; }
如此一来,只要调用 window.controller,就可以调用我们的 Obj C 对象。假如我们的 Obj C Class 里头有这些成员变量:
@interface MyController :NSObject { IBOutletWebView *webView; IBOUtlet NSWindow*window; NSString*stringValue; NSIntegernumberValue; NSArray*arrayValue; NSDate*dateValue; NSDictionary*dictValue; NSRectframeValue; } @end
指定一下 Value:
stringValue =@"string"; numberValue = 24; arrayValue = [[NSArrayarrayWithObjects:@"text", [NSNumbernumberWithInt:30],nil] retain]; dateValue = [[NSDatedate] retain]; dictValue = [[NSDictionarydictionaryWithObjectsAndKeys:@"value1",@"key1",@"value2",@"key2",@"value3",@"key3",nil] retain]; frameValue = [window frame];
用 JS 读读看:
var c = window.controller; var main = document.getElementById('main'); var HTML =''; if(c) { HTML +='<p>'+ c.stringValue +'<p>'; HTML +='<p>'+ c.numberValue +'<p>'; HTML +='<p>'+ c.arrayValue +'<p>'; HTML +='<p>'+ c.dateValue +'<p>'; HTML +='<p>'+ c.dictValue +'<p>'; HTML +='<p>'+ c.frameValue +'<p>'; main.innerHTML = HTML; }
结果如下:
string24text,302010-09-09 00:01:04 +0800{ key1 = value1; key2 = value2; key3 = value3; }NSRect: {{275, 72}, {570, 657}}
不过,如果你看完上面的范例,就直接照做,应该不会直接成功出现正确的结果,而是会拿到一堆 undefined,原因是,Obj C 对象的 Value 预设被保护起来,不会让 JS 直接存取。要让 JS 可以存取 Obj C 对象的 Value,需要实作 +isKeyExcludedFromWebScript: 针对传入的 Key 一一处理,如果我们希望 JS 可以存取这个 key,就回传 NO:
+ (BOOL)isKeyExcludedFromWebScript:(constchar*)name { if(!strcmp(name,"stringValue")) { return NO; } return YES; }
用 JavaScript 调用 Objective C method
- (void)setA:(id)a b:(id)b c:(id)c;
在 JS 中就这麽调用:
controller.setA_b_c_('a','b','c');
实在有点丑。所以 WebKit 提供一个方法,可以让我们把某个 Obj C selector 变成好看一点的 JS function。我们要实作 webScriptNameForSelector:
+ (NSString*)webScriptNameForSelector:(SEL)selector { if(selector ==@selector(setA:b:c:)) { return@"setABC"; } returnnil; }
以后就可以这麽调用:
controller.setABC('a','b','c');
用 JavaScript 调用 Objective C 2.0 的 property
Javascript 中,Function 即对象的特性
console.log(controller.setABC);
我们可以从结果看到:
function setABC() { [native code] }
- (void)numberWithA:(id)a plusB:(id)b callback:(id)callback { NSIntegerresult = [a integerValue] + [b integerValue]; [callback callWebScriptMethod:@"call"withArguments:[NSArrayarrayWithObjects:callback, [NSNumbernumberWithInteger:result],nil]]; }
JS 里头就可以这样调用:
window.controller.numberWithA_plusB_callback_(1, 2,function(result) { varmain = document.getElementById('main'); main.innerText = result; });
其他平台上 WebKit 的实作
、QWebElementCollection),我们可以对 QWebFrame 还有这些 DOM 对象调用 evaluateJavaScript,执行 Javascript。
JavaScriptCore Framework
JSGlobalContextRef globalContext = [[webView mainFrame] globalContext]; JSValueRef exception = NULL; JSStringRef script = JSStringCreateWithUTF8CString("window.location.href='http://spring-studio.net'"); JSEvaluateScript(globalContext, script, NULL, NULL, 0, &exception); JSStringRelease(script);
如果我们想要让 WebView 里头的 JS,可以调用我们的 C Function:
- (void)webView:(WebView *)sender didClearWindowObject:(WebScriptObject *)windowObject forFrame:(WebFrame *)frame { JSGlobalContextRef globalContext = [frame globalContext]; JSStringRef name = JSStringCreateWithUTF8CString("myFunc"); JSObjectRef obj = JSObjectMakeFunctionWithCallback(globalContext, name, (JSObjectCallAsFunctionCallback)myFunc); JSObjectSetProperty (globalContext, [windowObject JSObject], name, obj, 0,NULL); JSStringRelease(name); }
那麽,只要 JS 调用 window.myFunc(),就可以取得们放在 myFunc 这个 C function 中回传的结果:
JSValueRef myFunc(JSContextRef ctx, JSObjectRef function, JSObjectRef thisObject,size_targumentCount,constJSValueRef arguments[], JSValueRef* exception) { returnJSValueMakeNumber(ctx, 42); }