注:本篇文章themeableBrowser緊能調(diào)用本地調(diào)用本地Cordova,如需遠程調(diào)用本地Cordova,請看IOS themeableBrowser 調(diào)用Cordova原生插件(二)http://www.itdecent.cn/p/a6d95ccf341a
cordova themeableBrowser插件中調(diào)用原生插件,比如相機、指紋等。這里采用themeableBrowser調(diào)用OC,然后OC把結(jié)果通過js回調(diào)給themeableBrowser的方式,實現(xiàn)如下:
1、首先添加 javaScriptCore.framework,用于js調(diào)用OC。
2、在OC代碼中添加 js 要調(diào)用的方法。找到themeableBrowser插件下的CDVThemeableBrowser.m,修改代碼中webViewDidFinishLoad方法:
- (void)webViewDidFinishLoad:(UIWebView*)theWebView
{
// update url, stop spinner, update back/forward
self.addressLabel.text = [self.currentURL absoluteString];
[self updateButton:theWebView];
if (self.titleLabel && _browserOptions.title
&& !_browserOptions.title[kThemeableBrowserPropStaticText]
&& [self getBoolFromDict:_browserOptions.title withKey:kThemeableBrowserPropShowPageTitle]) {
// Update title text to page title when title is shown and we are not
// required to show a static text.
self.titleLabel.text = [self.webView stringByEvaluatingJavaScriptFromString:@"document.title"];
}
[self.spinner stopAnimating];
// Work around a bug where the first time a PDF is opened, all UIWebViews
// reload their User-Agent from NSUserDefaults.
// This work-around makes the following assumptions:
// 1. The app has only a single Cordova Webview. If not, then the app should
// take it upon themselves to load a PDF in the background as a part of
// their start-up flow.
// 2. That the PDF does not require any additional network requests. We change
// the user-agent here back to that of the CDVViewController, so requests
// from it must pass through its white-list. This *does* break PDFs that
// contain links to other remote PDF/websites.
// More info at https://issues.apache.org/jira/browse/CB-2225
BOOL isPDF = [@"true" isEqualToString :[theWebView stringByEvaluatingJavaScriptFromString:@"document.body==null"]];
if (isPDF) {
[CDVUserAgentUtil setUserAgent:_prevUserAgent lockToken:_userAgentLockToken];
}
JSContext* content = [self.webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"]; //self do
content[@"getMessage"] = ^(NSArray* jsonEntry) {
// 獲取到根控制器MainViewContoller,因為這個控制器初始化了Cordova插件,需要用這個控制器來調(diào)用插件
AppDelegate *appdelegate = (AppDelegate*)[UIApplication sharedApplication].delegate;
UIViewController *rootViewController = appdelegate.window.rootViewController;
CDVViewController *vc = (CDVViewController *) rootViewController;
// 解析調(diào)用插件所需要的參數(shù)
CDVInvokedUrlCommand* command = [CDVInvokedUrlCommand commandFromJson:jsonEntry];
// 用根控制器上的commandQueue方法,調(diào)用插件
if (![vc.commandQueue execute:command]) {
#ifdef DEBUG
NSError* error = nil;
NSString* commandJson = nil;
NSData* jsonData = [NSJSONSerialization dataWithJSONObject:jsonEntry
options:0
error:&error];
if (error == nil) {
commandJson = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];
}
static NSUInteger maxLogLength = 1024;
NSString* commandString = ([commandJson length] > maxLogLength) ?
[NSString stringWithFormat : @"%@[...]", [commandJson substringToIndex:maxLogLength]] :
commandJson;
NSLog(@"FAILED pluginJSON = %@", commandString);
#endif
}
};
[self.navigationDelegate webViewDidFinishLoad:theWebView];
}
3、第二步中通過最上層根控制器去調(diào)用方法,而不是themeableBrowser中的,所以重寫present方法,獲取當(dāng)前最上層控制器進行present。新建present類。
#import "UIViewController+Present.h"
#import <objc/runtime.h>
@implementation UIViewController (Present)
+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Method presentM = class_getInstanceMethod(self.class, @selector(presentViewController:animated:completion:));
Method presentSwizzlingM = class_getInstanceMethod(self.class, @selector(dy_presentViewController:animated:completion:));
method_exchangeImplementations(presentM, presentSwizzlingM);
});
}
- (void)dy_presentViewController:(UIViewController *)viewControllerToPresent animated:(BOOL)flag completion:(void (^)(void))completion {
UIViewController *currentVc = [self topViewController];
if ([currentVc isKindOfClass:[UIAlertController class]]) {
[self dy_presentViewController:viewControllerToPresent animated:flag completion:completion];
} else {
[[self topViewController] dy_presentViewController:viewControllerToPresent animated:flag completion:completion];
}
}
- (UIViewController *)topViewController {
UIViewController *topVC;
topVC = [self getTopViewController:[[UIApplication sharedApplication].keyWindow rootViewController]];
while (topVC.presentedViewController) {
topVC = [self getTopViewController:topVC.presentedViewController];
}
return topVC;
}
- (UIViewController *)getTopViewController:(UIViewController *)vc {
if (![vc isKindOfClass:[UIViewController class]]) {
return nil;
}
if ([vc isKindOfClass:[UINavigationController class]]) {
return [self getTopViewController:[(UINavigationController *)vc topViewController]];
} else if ([vc isKindOfClass:[UITabBarController class]]) {
return [self getTopViewController:[(UITabBarController *)vc selectedViewController]];
} else {
return vc;
}
}
@end
4、將themeableBrowser設(shè)置為全局,方便調(diào)用回調(diào),修改themeableBrowser.js。
exports.open = function(strUrl, strWindowName, strWindowFeatures, callbacks) {
// Don't catch calls that write to existing frames (e.g. named iframes).
if (window.frames && window.frames[strWindowName]) {
var origOpenFunc = modulemapper.getOriginalSymbol(window, 'open');
return origOpenFunc.apply(window, arguments);
}
strUrl = urlutil.makeAbsolute(strUrl);
var iab = new ThemeableBrowser();
callbacks = callbacks || {};
for (var callbackName in callbacks) {
iab.addEventListener(callbackName, callbacks[callbackName]);
}
var cb = function(eventname) {
iab._eventHandler(eventname);
};
strWindowFeatures = strWindowFeatures && JSON.stringify(strWindowFeatures);
// Slightly delay the actual native call to give the user a chance to
// register event listeners first, otherwise some warnings or errors may be missed.
setTimeout(function() {
exec(cb, cb, 'ThemeableBrowser', 'open', [strUrl, strWindowName, strWindowFeatures || '']);
}, 0);
// 聲明全局變量__globalThemeableBrowser,表示當(dāng)前界面開啟了ThemeableBrowser //self do
window.__globalThemeableBrowser = iab;
return iab;
};
5、由于原生是調(diào)用根控制器上的插件返回callback,是和ThemeableBrowser不同層級的webview,所以需要做一層轉(zhuǎn)發(fā),判斷當(dāng)前webview的callback數(shù)組,是否含有接收到的callbackid,如果不在在數(shù)組中,則說明不是該webview調(diào)用的插件,則調(diào)用ThemeableBrowser里的js回傳方法,回傳開ThemeableBrowser的webview接收callback。修改cordova.js:
callbackFromNative: function(callbackId, isSuccess, status, args, keepCallback) {
try {
var callback = cordova.callbacks[callbackId];
if (callback) {
if (isSuccess && status == cordova.callbackStatus.OK) {
callback.success && callback.success.apply(null, args);
} else if (!isSuccess) {
callback.fail && callback.fail.apply(null, args);
}
/*
else
Note, this case is intentionally not caught.
this can happen if isSuccess is true, but callbackStatus is NO_RESULT
which is used to remove a callback from the list without calling the callbacks
typically keepCallback is false in this case
*/
// Clear callback if not expecting any more results
if (!keepCallback) {
delete cordova.callbacks[callbackId];
}
}
else { //self do
if(window.__globalThemeableBrowser) {
var message = 'cordova.callbackFromNative("'+callbackId+'",'+isSuccess+',' + status +',' +JSON.stringify(args) + ',' + keepCallback + ')';
//調(diào)用hemeableBrowser插件里的js回傳方法
window.__globalThemeableBrowser.executeScript({code: message});
}
}
}
catch (err) {
var msg = "Error in " + (isSuccess ? "Success" : "Error") + " callbackId: " + callbackId + " : " + err;
console && console.log && console.log(msg);
cordova.fireWindowEvent("cordovacallbackerror", { 'message': msg });
throw err;
}
}
6、編寫測試用例。注意調(diào)用getMessage方法調(diào)用OC,四個參數(shù),第一個是回調(diào),也就是callbackId,第二個和第三個是插件名和OC方法名,結(jié)合插件js中cordova.exec方法可以快速找到,第四個是插件參數(shù)。
function camera() {
var callbackId = getCallbackId(
"Camera",
function (ret) {
alert("successs=============")
},
function (ret) {
alert("fail=============")
});
getMessage([callbackId,
"Camera",
"takePicture",
[50,Camera.DestinationType.DATA_URL]
]);
}
function getCallbackId(className,successBack,failBack) {
var callbackId = className + cordova.callbackId++;
cordova.callbacks[callbackId] = {success: successBack, fail: failBack};
return callbackId;
}
最后初入cordova,多多關(guān)照,多多指點。