注:本篇文章themeableBrowser紧能调用本地调用本地Cordova,如需远程调用本地Cordova,请看IOS themeableBrowser 调用Cordova原生插件(二)https://www.jianshu.com/p/a6d95ccf341a
cordova themeableBrowser插件中调用原生插件,比如相机、指纹等。这里采用themeableBrowser调用OC,然后OC把结果通过js回调给themeableBrowser的方式,实现如下:
1、首先添加 javaScriptCore.framework,用于js调用OC。
2、在OC代码中添加 js 要调用的方法。找到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插件,需要用这个控制器来调用插件
AppDelegate *appdelegate = (AppDelegate*)[UIApplication sharedApplication].delegate;
UIViewController *rootViewController = appdelegate.window.rootViewController;
CDVViewController *vc = (CDVViewController *) rootViewController;
// 解析调用插件所需要的参数
CDVInvokedUrlCommand* command = [CDVInvokedUrlCommand commandFromJson:jsonEntry];
// 用根控制器上的commandQueue方法,调用插件
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、第二步中通过最上层根控制器去调用方法,而不是themeableBrowser中的,所以重写present方法,获取当前最上层控制器进行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设置为全局,方便调用回调,修改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,表示当前界面开启了ThemeableBrowser //self do
window.__globalThemeableBrowser = iab;
return iab;
};
5、由于原生是调用根控制器上的插件返回callback,是和ThemeableBrowser不同层级的webview,所以需要做一层转发,判断当前webview的callback数组,是否含有接收到的callbackid,如果不在在数组中,则说明不是该webview调用的插件,则调用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 + ')';
//调用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、编写测试用例。注意调用getMessage方法调用OC,四个参数,第一个是回调,也就是callbackId,第二个和第三个是插件名和OC方法名,结合插件js中cordova.exec方法可以快速找到,第四个是插件参数。
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,多多关照,多多指点。
网友评论