IOS themeableBrowser 调用Cordova原生

2019-07-25  本文已影响0人  爱写诗的程序员zxp

注:本篇文章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,多多关照,多多指点。

上一篇下一篇

猜你喜欢

热点阅读