iOS集成Weex頁面的總結

由于項目的需求和未來業務發展的需要,在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的方法里,問題得到了解決.但是具體原因是否是猜想的那樣,還有待驗證,如果有知道的或者有更好方法的小伙伴,可以提出來讓大家參考一下.

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。