由于項目的需求和未來業務發展的需要,在app版本迭代中,嘗試將某個模塊的功能用weex前端代碼來實現,然后集成到原生app中,來減少原生業務的代碼量.具體的集成過程,其實官方也已經提供了相應的文檔,主要還是移動端和前端要約定好對應的方法和傳參方式.保證之間的交互和數據傳遞是暢通的.下面我簡單地描述一下整個的集成和對接過程.
一、集成WeexSDK到項目中,直接使用Cocoapods導入WeexSDK即可,這一步基本上沒什么問題;
二、初始化Weex環境: ? ? 在AppDelegate導入<WeexSDK.h>,然后初始化:
????[WXAppConfiguration setAppGroup:@"WXApp"];
? ? [WXAppConfiguration setAppName:@"Merchants"];
? ? [WXAppConfiguration setAppVersion:[ToolManager getVersionStr]];
? ? [WXSDKEngine initSDKEnvironment];
? ? [WXSDKEngine registerModule:@"CommonModule" withClass:[CommonModule class]];
? ? [WXSDKEngine registerHandler:[WXimgLoader new] withProtocol:@protocol(WXImgLoaderProtocol)];
? ? [WXLog setLogLevel: WXLogLevelError];
注釋:AppGroup可以自己自定義填寫;AppName填自己的項目名稱;AppVersion填寫自己的項目版本號;initSDKEnvironment這個方法就是在初始化WeexSDK的運行環境,registModule方法和registerHandler方法是重點要講的兩個點.
三、創建Weex頁面容器控制器:
1.繼承自己的根視圖控制器,我這里自己項目中寫了一個BaseViewController,繼承自UIViewController,里面自定義了控制器的一些方法,項目中其他所有的控制器都繼承自BaseViewController.所以這里繼承BaseViewController,新建了一個QuickOrderController容器,其中初始化容器的代碼如下,網上有很多關于這一塊的代碼,基本上都是大同小異的.
#pragma mark-隱藏導航欄
-(void)viewWillAppear:(BOOL)animated{
? ? [super viewWillAppear:animated];
? ? self.navigationController.navigationBarHidden = YES;
}
-(void)viewDidAppear:(BOOL)animated{
????[super viewDidAppear:animated];
? ? [self updateInstanceState:WeexInstanceAppear];
}
#pragma mark-顯示導航欄
-(void)viewWillDisappear:(BOOL)animated{
????[super viewWillDisappear:animated];
? ? self.navigationController.navigationBarHidden = NO;
? ? [self updateInstanceState:WeexInstanceDisappear];
}
- (void)viewDidLoad {
?????[super viewDidLoad];
? ? self.view.backgroundColor = [UIColor whiteColor];
? ? [self render];
}
#pragma mark-初始化weex容器
-(void)render{
?? ?_instance = [[WXSDKInstance alloc] init];
? ? _instance.viewController = self;
? ? _instance.frame = CGRectMake(0, kStatusBarHeight, self.view.frame.size.width, ScreenHeight-kStatusBarHeight);
? ? /**默認請求參數*/
? ? [self.params setValue:@"ios" forKey:@"platform"];
? ? [self.params setValue:[ToolManager getUUID] forKey:@"deviceID"];
? ? [self.params setValue:[ToolManager getVersionStr] forKey:@"version"];
? ? [self.params setValue:[UserAccountModel loadAccount].siteuserid forKey:@"opId"];
? ? [self.params setValue:[UserAccountModel loadAccount].password forKey:@"password"];
? ? [self.params setValue:[ZYUserStand objectForKey:@"shopId"] forKey:@"shopId"];
? ? [self.params setValue:[ZYUserStand objectForKey:@"bossPurchaseOrderType"] forKey:@"bossPurchaseOrderType"];
? ? [_instance renderWithURL:self.url options:@{@"params":self.params} data:nil];
????__weaktypeof(self) weakSelf =self;
? ? _instance.onCreate= ^(UIView*view) {
? ? ? ? [weakSelf.weexViewremoveFromSuperview];
? ? ? ? weakSelf.weexView= view;
? ? ? ? [weakSelf.viewaddSubview:weakSelf.weexView];
? ? ? ? UIAccessibilityPostNotification(UIAccessibilityScreenChangedNotification, weakSelf.weexView);
? ? };
? ? /**失敗*/
? ? _instance.onFailed= ^(NSError*error) {
?};
? ? /**完成*/
? ? _instance.renderFinish = ^ (UIView *view) {
?};
/**更新狀態*/
? ? _instance.updateFinish = ^(UIView *view) {
? ? };
}
#pragma mark-更新視圖的state
-(void)updateInstanceState:(WXState)state {
? ? if(_instance&&_instance.state!= state) {
? ? ? ? _instance.state= state;
? ? ? ? if(state ==WeexInstanceAppear) {
? ? ? ? ? ? [[WXSDKManager bridgeMgr] fireEvent:_instance.instanceId ref:WX_SDK_ROOT_REF type:@"viewappear" params:nil domChanges:nil];
? ? ? ? }else if(state ==WeexInstanceDisappear) {
? ? ? ? ? ? [[WXSDKManager bridgeMgr] fireEvent:_instance.instanceId ref:WX_SDK_ROOT_REF type:@"viewdisappear" params:nil domChanges:nil];
? ? ? ? }
? ? }
}
#pragma mark-銷毀
-(void)dealloc{
?? [_instance destroyInstance];
}
注釋:viewAppear里隱藏導航欄是因為weex頁面自己寫好了導航欄的內容,所以我這里將導航欄隱藏,將weexView(也就是weexInstance的frame設置為從狀態欄下開始,避免頁面頂到狀態欄);params是一個字典,里面包含了請求時所需的默認參數,在這里,通過option傳值,以鍵值對的形式將這些參數傳遞給weex,以供weex頁面中的服務請求使用,當然這里是我和服務端溝通好的傳值方式,如果有更好的方法,也可以按照自己的方法來.其他的代碼可以完全拷貝使用.
四、自定義Module(registerModule方法)
1.繼承NSObject創建一個自定義Module類,Module類名稱可以自定義,最后在AppDelegate中注冊Module時名稱要保持一致即可;
2.暴露Module方法以供weex調用,這里我附上自己的部分代碼來說明:
@implementation CommonModule
@synthesize weexInstance;
#pragma mark-暴露給weex調用的方法
/**吐司*/
WX_EXPORT_METHOD(@selector(showToast:))
/**js頁面跳轉方法*/
WX_EXPORT_METHOD(@selector(openPage:params:))
/**掃碼*/
WX_EXPORT_METHOD(@selector(startScan:))
/**打印*/
WX_EXPORT_METHOD(@selector(print:content:))
/**撥號*/
WX_EXPORT_METHOD(@selector(call:))
這里我定義了幾個供weex調用的方法,通過WX_EXPORT_METHOD將方法名暴露給js,當然,這里可以參考的方法是頁面跳轉的方法,其他的方法按自己的業務需求進行自定義即可,下面附上頁面跳轉的方法實現:
#pragma mark-跳轉頁面,帶參,參數可傳空
-(void)openPage:(NSString*)url params:(NSDictionary*)params{
????NSString*newURL = url;
? ? /**判斷url是通過服務器下載還是本地js文件*/
? ? if([urlhasPrefix:@"http://"]) {
? ? ? ? newURL = [NSStringstringWithFormat:@"http:%@", url];
? ? }elseif(![urlhasPrefix:@"http"]) {
? ? ? ? newURL = [NSURL URLWithString:url relativeToURL:weexInstance.scriptURL].absoluteString;
? ? }
? ? QuickOrderController *controller = [[QuickOrderController alloc] init];
? ? controller.url= [NSURLURLWithString:newURL];
? ? controller.params = [NSMutableDictionary dictionaryWithDictionary:params];
? ? [[weexInstance.viewController navigationController] pushViewController:controller animated:YES];
}
注釋:這里的QuickOrderController是第三步創建的容器控制器,我們會發現這里在跳轉時傳遞了兩個參數,一個url和一個params,這個url就是第三步中?[_instance renderWithURL:self.url options:@{@"params":self.params} data:nil]方法中的url,params就是self.params,除了默認的參數,可以在這里添加其他的參數.視自己的需求而定.既然到這里,就再補充說一下原生和weex頁面間的相互傳值吧.
通過module里自定義的帶參方法,我們可以將原生的一些參數傳遞給weex頁面,同樣,weex頁面可以通過調用這些帶參方法,將需要的參數傳遞給原生頁面.參照我的代碼具體說明如下:
1.weex傳遞到原生頁面:在我的代碼里有一個封裝的打印方法,攜帶了type和content兩個參數,weex頁面在點擊打印按鈕時,會將當前訂單的類型和需要打印的信息分別傳遞給這兩個參數,而我們在module里實現這個方法時,就可以拿到weex頁面傳遞過來的信息,去打印對應的訂單小票.
2.原生傳遞到weex:我現在還有一個這樣的需求,weex頁面中有一個核銷的按鈕,點擊后,會調用原生的掃碼頁面,掃碼完成,需要將掃描到的結果傳遞給weex頁面,然后weex頁面進行對應的核銷接口調用.這個掃碼的方法就是上面代碼里自定義Module中的掃碼方法,這里使用了WXModuleCallback的回調機制.具體的實現代碼如下:
#pragma mark-掃描,回調方法傳遞掃描結果
-(void)startScan:(WXModuleCallback)callBack{
? ? QuickScanCodeController *scanVC = [[QuickScanCodeController alloc] init];
? ? scanVC.title=@"掃描驗貨碼";
? ? scanVC.callBack= callBack;
? ? [[weexInstance.viewController navigationController] pushViewController:scanVC animated:YES];
}
注釋:這個QuickScanCodeController就是我這邊原生的掃碼頁面,將callBack回調傳遞過去,同樣,在掃碼頁面的掃碼成功方法里,給這個callBack賦值,這里賦值的方式和具體傳參的字段,需要和weex端統一好.我這里是以鍵值對的形式,將這個掃碼結果傳遞.代碼如下:
#pragma mark - 掃碼完成后代理QRViewDelegate
- (void)qrView:(QRView*)view ScanResult:(NSString*)result{
? ? [view stopScan];/**掃描成功后先停止掃描*/
? ? NSLog(@"code=%@", result);
? ? ?/**掃碼驗貨成功后的回調函數*/
? ? ?self.callBack(@{@"scanResult":result});
????[self.navigationController popViewControllerAnimated:YES];
}
到這里,基本集成就差不多了,有些人會發現,在集成好的weex頁面已經可以正常跳轉顯示了,但是為什么weex頁面上的圖片全部都加載不出來.這是因為想要加載出圖片還得實現一個圖片加載器,回到AppDelegate里,可以發現,不僅有我注冊的CommonModule,還有一個WXimgLoader,這個WXImgLoader就是用來加載圖片的類.具體的代碼實現如下,使用時可以直接拷貝到項目中,想深究的朋友可以自行去探討.記住,一定要在AppDelegate中進行注冊.
#import?<Foundation/Foundation.h>
#import?<WeexSDK/WeexSDK.h>
@interface WXimgLoader : NSObject<WXImgLoaderProtocol, WXImageOperationProtocol>
注意:.h文件,一定要遵循這個WXImgLoaderProtocol協議
#import "WXimgLoader.h"
#import?<SDWebImage/UIImageView+WebCache.h>
#import?<SDWebImageDownloader.h>
#define MIN_IMAGE_WIDTH 36
#define MIN_IMAGE_HEIGHT 36
#if OS_OBJECT_USE_OBJC
#undef? WXDispatchQueueRelease
#undef? WXDispatchQueueSetterSementics
#define WXDispatchQueueRelease(q)
#define WXDispatchQueueSetterSementics strong
#else
#undef? WXDispatchQueueRelease
#undef? WXDispatchQueueSetterSementics
#define WXDispatchQueueRelease(q) (dispatch_release(q))
#define WXDispatchQueueSetterSementics assign
#endif
@interface WXimgLoader ()
/**隊列*/
@property (WXDispatchQueueSetterSementics, nonatomic) dispatch_queue_t ioQueue;
@end
@implementation WXimgLoader
#pragma mark WXImgLoaderProtocol
- (id)downloadImageWithURL:(NSString*)urlimageFrame:(CGRect)imageFrameuserInfo:(NSDictionary*)optionscompleted:(void(^)(UIImage*,NSError*,BOOL))completedBlock{
? ? if([urlhasPrefix:@"http://"]) {
? ? ? ? url = [@"http:" stringByAppendingString:url];
? ? }
? ? // 加載本地圖片
? ? if([urlhasPrefix:@"file://"]) {
? ? ? ? NSString *newUrl = [url stringByReplacingOccurrencesOfString:@"/images/" withString:@"/"];
? ? ? ? UIImage *image = [UIImage imageNamed:[newUrl substringFromIndex:7]];
? ? ? ? completedBlock(image,nil,YES);
? ? ? ? return (id<WXImageOperationProtocol>) self;
? ? }else{
? ? ? ? return (id<WXImageOperationProtocol>)? [[SDWebImageDownloader sharedDownloader]downloadImageWithURL:[NSURL URLWithString:url] options:0 progress:^(NSInteger receivedSize, NSInteger expectedSize, NSURL * _Nullable targetURL) {
? ? ? ? ? }completed:^(UIImage*_Nullableimage,NSData*_Nullabledata,NSError*_Nullableerror,BOOLfinished) {
? ? ? ? ? ? ? if(completedBlock){
????????????????????????completedBlock(image,error,finished);
? ? ? ? ? ? ? }
? ? ? ? ? }];
? ? }
}
#pragma mark-取消加載圖片方法
- (void)cancel{
? ? [[SDWebImageManager sharedManager]cancelAll];
}
@end
注釋:SDWebImage版本不宜過高,否則會報錯,提示找不到cancel的方法.
整個集成過程中遇到的問題:
1.在weex容器頁面加載的過程中,首次加載weex頁面時,weex頁面的網絡請求始終無法響應,這里按照weex官方集成文檔上所說的,viewappear方法對應ios端viewWillAppear方法,我將updateInstanceState的方法寫在viewWillAppear方法里,weex首次加載時端始終無法捕捉到該事件,導致第一次進入頁面數據未加載的情況.后來考慮是不是ios的頁面尚未加載完成導致weex容器頁面尚未加載,weex頁面無法捕捉到該事件.于是嘗試將updateInstanceState的方法寫在了viewDIdLoad的方法里,問題得到了解決.但是具體原因是否是猜想的那樣,還有待驗證,如果有知道的或者有更好方法的小伙伴,可以提出來讓大家參考一下.