iOS集成Weex页面的总结

由于项目的需求和未来业务发展的需要,在app版本迭代中,尝试将某个模块的功能用weex前端代码来实现,然后集成到原生app中,来减少原生业务的代码量.具体的集成过程,其实官方也已经提供了相应的文档,主要还是移动端和前端要约定好对应的方法和传参方式.保证之间的交互和数据传递是畅通的.下面我简单地描述一下整个的集成和对接过程.

一、集成WeexSDK到项目中,直接使用Cocoapods导入WeexSDK即可,这一步基本上没什么问题;

二、初始化Weex环境:     在AppDelegate导入,然后初始化:

    [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:@"//"]) {

        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 

#import 

@interface WXimgLoader : NSObject

注意:.h文件,一定要遵循这个WXImgLoaderProtocol协议


#import "WXimgLoader.h"

#import 

#import 

#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:@"//"]) {

        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) self;


    }else{

        return (id)  [[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的方法里,问题得到了解决.但是具体原因是否是猜想的那样,还有待验证,如果有知道的或者有更好方法的小伙伴,可以提出来让大家参考一下.

你可能感兴趣的:(iOS集成Weex页面的总结)