iOS InAppBrowser 調用原生插件

需求:當前APP許多頁面是由InAppBrowser打開的新的webview展示頁面李茫,但是同時需求需要調用原生的選圖/看圖插件等。

環(huán)境:InAppBrowser插件由原來的uiwebivew升級為wkwebiview内边,基于wkwebivew開發(fā)此功能男摧。

步驟:

1.創(chuàng)建wkwebivew的時候,向js注入cordva方法

    WKUserContentController* userContentController = [[WKUserContentController alloc] init];
    WKWebViewConfiguration* configuration = [[WKWebViewConfiguration alloc] init];
    configuration.userContentController = userContentController;
    configuration.processPool = [[CDVWKProcessPoolFactory sharedFactory] sharedProcessPool];
    
    [configuration.userContentController addScriptMessageHandler:self name:IAB_BRIDGE_NAME];
    [configuration.userContentController addScriptMessageHandler:self name:@"cordova"];

    self.webView = [[WKWebView alloc] initWithFrame:self.view.frame configuration:configuration];

2.h5調用原生插件

// name填入cordova messageBody為插件的參數
// 原生解析的時候斤贰,只需攔截messageBody.name 等于cordova的消息
window.webkit.messageHandlers.<name>.postMessage(<messageBody>)

3.原生調用cordva插件

#pragma mark WKScriptMessageHandler delegate
- (void)userContentController:(nonnull WKUserContentController *)userContentController didReceiveScriptMessage:(nonnull WKScriptMessage *)message {
 if ([message.name isEqualToString:@"cordova"]) {
      
      // 獲取到根控制器MainViewContoller,因為這個控制器初始化了Cordova插件次询,需要用這個控制器來調用插件
      AppDelegate *appdelegate = (AppDelegate *)[UIApplication sharedApplication].delegate;
      UIViewController *rootViewController = appdelegate.window.rootViewController;
      CDVViewController *vc = (CDVViewController *) rootViewController;

      // 解析調用插件所需要的參數
      NSArray *jsonEntry = message.body;
      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
      }
  }
}

#pragma mark WKScriptMessageHandler delegate
// didReceiveScriptMessage代理方法接受到消息后,此方方法會接受到回調屯吊,也需處理
- (void)userContentController:(nonnull WKUserContentController *)userContentController didReceiveScriptMessage:(nonnull WKScriptMessage *)message {
  if ([message.name isEqualToString:@"cordova"]) {
      return;
  }
}

這里存在一個問題送巡,我們是通過根控制器調用的插件,如果需要present一個新的控制器顯示盒卸,這時候是顯示在根控制器上骗爆,但是在InAppBrowser之下,這里還需要處理一次世落,重寫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.插件回傳callback給h5

4.1修改原生本地corodva.js文件
由于原生是調用根控制器上的插件返回callback糟需,是和InAppBrowser不同層級的webview屉佳,所以需要做一層轉發(fā),判斷當前webview的callback數組洲押,是否含有接收到的callbackid,如果不在在數組中武花,則說明不是該webview調用的插件,則調用InAppBrowser里的js回傳方法,回傳開InAppBrowser的webview接收callback

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 {
         // __globalBrowser為表示當前界面開啟了InAppBrowser
         if(window.__globalBrowser) {
            var message = 'cordova.callbackFromNative("'+callbackId+'",'+isSuccess+',' + status +',' +JSON.stringify(args) + ',' + keepCallback + ')';
           // 調用InAppBrowser插件里的js回傳方法
            window.__globalBrowser.executeScript({code: message});
         }
       }

4.2 修改inappbrowser.js

 module.exports = 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 InAppBrowser();

        callbacks = callbacks || {};
        for (var callbackName in callbacks) {
            iab.addEventListener(callbackName, callbacks[callbackName]);
        }

        var cb = function(eventname) {
           iab._eventHandler(eventname);
        };

        strWindowFeatures = strWindowFeatures || "";
    
        exec(cb, cb, "InAppBrowser", "open", [strUrl, strWindowName, strWindowFeatures]);
        // 聲明全局變量__globalBrowser杈帐,表示當前界面開啟了InAppBrowser
        window.__globalBrowser = iab;
        return iab;

5.如果調用了選擇圖片插件体箕,h5回顯本地圖片需要原生攔截scheme請求

5.1 創(chuàng)建wkwebivew专钉,注入js方法,用于h5調用插件

 WKWebViewConfiguration* configuration = [[WKWebViewConfiguration alloc] init];
    configuration.userContentController = userContentController;
    configuration.processPool = [[CDVWKProcessPoolFactory sharedFactory] sharedProcessPool];
    
    [configuration.userContentController addScriptMessageHandler:self name:IAB_BRIDGE_NAME];
    // 注冊cordova方法累铅,讓h5調用
    [configuration.userContentController addScriptMessageHandler:self name:@"cordova"];

    //self.webView = [[WKWebView alloc] initWithFrame:frame];
    self.webView = [[WKWebView alloc] initWithFrame:self.view.frame configuration:configuration];

5.2 回傳參數內需要寫入自定義scheme協(xié)議

// 是否是InAppBrowser調用的插件
// ExeInAppBrowserShow 這個值需要在InAppBrowser開啟和關閉的時候賦值
BOOL isInappBro =  [[[NSUserDefaults standardUserDefaults] objectForKey:ExeInAppBrowserShow] boolValue];
if (isInappBro) {
  [resultStrings addObject:[url.absoluteString stringByReplacingOccurrencesOfString:@"file://" withString:@"miexe.com://"]];
else {
  [resultStrings addObject:url.absoluteString];
}
[self.commandDelegate sendPluginResult:[CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsArray:resultStrings] callbackId:self.callbackId];

5.3 在MyCustomURLProtocol類中跃须,需要實現三個方法:

// 每次有一個請求的時候都會調用這個方法,在這個方法里面判斷這個請求是否需要被處理攔截娃兽,如果返回YES就代表這個request需要被處理菇民,反之就是不需要被處理。
+ (BOOL)canInitWithRequest:(NSURLRequest*)theRequest{
   if ([theRequest.URL.scheme caseInsensitiveCompare:@"miexe.com"] == NSOrderedSame) {
       return YES;
   }
   return NO;
}

// 這個方法就是返回規(guī)范的request投储,一般使用就是直接返回request第练,不做任何處理的    
+ (NSURLRequest*)canonicalRequestForRequest:(NSURLRequest*)theRequest{
   return theRequest;
}

// 這個方法作用很大,把當前請求的request攔截下來以后玛荞,在這個方法里面對這個request做各種處理娇掏,比如添加請求頭,重定向網絡勋眯,使用自定義的緩存等婴梧。
// 我們在這里將自定義協(xié)議的scheme去除掉。然后返回file協(xié)議給h5
- (void)startLoading{
   NSURLResponse *response = [[NSURLResponse alloc] initWithURL:[self.request URL]
                                                       MIMEType:@"image/png"
                                          expectedContentLength:-1
                                               textEncodingName:nil];
   NSString *imagePath = [self.request.URL.absoluteString componentsSeparatedByString:@"miexe.com://"].lastObject;
   NSData *data = [NSData dataWithContentsOfFile:imagePath];
   [[self client] URLProtocol:self didReceiveResponse:response cacheStoragePolicy:NSURLCacheStorageNotAllowed];
   [[self client] URLProtocol:self didLoadData:data];
   [[self client] URLProtocolDidFinishLoading:self];
   
}
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末客蹋,一起剝皮案震驚了整個濱河市志秃,隨后出現的幾起案子,更是在濱河造成了極大的恐慌嚼酝,老刑警劉巖浮还,帶你破解...
    沈念sama閱讀 216,651評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現場離奇詭異闽巩,居然都是意外死亡钧舌,警方通過查閱死者的電腦和手機,發(fā)現死者居然都...
    沈念sama閱讀 92,468評論 3 392
  • 文/潘曉璐 我一進店門涎跨,熙熙樓的掌柜王于貴愁眉苦臉地迎上來洼冻,“玉大人,你說我怎么就攤上這事隅很∽怖危” “怎么了?”我有些...
    開封第一講書人閱讀 162,931評論 0 353
  • 文/不壞的土叔 我叫張陵叔营,是天一觀的道長屋彪。 經常有香客問我,道長绒尊,這世上最難降的妖魔是什么畜挥? 我笑而不...
    開封第一講書人閱讀 58,218評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮婴谱,結果婚禮上蟹但,老公的妹妹穿的比我還像新娘躯泰。我一直安慰自己,他們只是感情好华糖,可當我...
    茶點故事閱讀 67,234評論 6 388
  • 文/花漫 我一把揭開白布麦向。 她就那樣靜靜地躺著,像睡著了一般客叉。 火紅的嫁衣襯著肌膚如雪磕蛇。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,198評論 1 299
  • 那天十办,我揣著相機與錄音秀撇,去河邊找鬼。 笑死向族,一個胖子當著我的面吹牛呵燕,可吹牛的內容都是我干的。 我是一名探鬼主播件相,決...
    沈念sama閱讀 40,084評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼再扭,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了夜矗?” 一聲冷哼從身側響起泛范,我...
    開封第一講書人閱讀 38,926評論 0 274
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎紊撕,沒想到半個月后罢荡,有當地人在樹林里發(fā)現了一具尸體,經...
    沈念sama閱讀 45,341評論 1 311
  • 正文 獨居荒郊野嶺守林人離奇死亡对扶,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,563評論 2 333
  • 正文 我和宋清朗相戀三年区赵,在試婚紗的時候發(fā)現自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片浪南。...
    茶點故事閱讀 39,731評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡笼才,死狀恐怖,靈堂內的尸體忽然破棺而出络凿,到底是詐尸還是另有隱情骡送,我是刑警寧澤,帶...
    沈念sama閱讀 35,430評論 5 343
  • 正文 年R本政府宣布絮记,位于F島的核電站摔踱,受9級特大地震影響,放射性物質發(fā)生泄漏到千。R本人自食惡果不足惜昌渤,卻給世界環(huán)境...
    茶點故事閱讀 41,036評論 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望憔四。 院中可真熱鬧膀息,春花似錦、人聲如沸了赵。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,676評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽柿汛。三九已至冗酿,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間络断,已是汗流浹背裁替。 一陣腳步聲響...
    開封第一講書人閱讀 32,829評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留貌笨,地道東北人弱判。 一個月前我還...
    沈念sama閱讀 47,743評論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像锥惋,于是被迫代替她去往敵國和親昌腰。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,629評論 2 354

推薦閱讀更多精彩內容