美文网首页
IOS themeableBrowser 调用Cordova原生

IOS themeableBrowser 调用Cordova原生

作者: 爱写诗的程序员zxp | 来源:发表于2019-07-25 10:36 被阅读0次

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

    相关文章

      网友评论

          本文标题:IOS themeableBrowser 调用Cordova原生

          本文链接:https://www.haomeiwen.com/subject/czucrctx.html