weexWeex 从无到有开发一款上线应用

Weex 从无到有开发一款上线应用 1

2017-05-24  本文已影响514人  叫我小詺

iOS调试Demo
WeexDemo

初始化APPFrame

终端创建 Weex工程
weex init xxx_weex
安装IDE插件

WebStorm安装Weex语法支持和插件。由于最开始Weex文件为.we,现在Weex已经完美支持Vue,所以现在也是用Vue开发。
具体安装步骤如下


打开偏好设置.png plugins.png

同样的下载Vue.js
安装后重启WebStorm。
由于WebStorm还不能直接创建Vue文件,所以需要先添加一下Vue文件模板。操作如下

添加Vue文件模板.png

模板内容如下

<template>
    <div class="view">
    </div>
</template>

<script>
    let stream = weex.requireModule('stream')
    let modal = weex.requireModule('modal')
    let navigator = weex.requireModule('navigator')
    let globalEvent = weex.requireModule('globalEvent');
    let apiHost = ''
    export default {
        data () {
            return {

            }
        },
        methods: {

        },
        created () {

        },
        mounted()
        {

        },
        components: {
            
        }
    }
</script>

<style scoped>
    .view {
        width: 750px;
        height: 1334px;
        background-color:#fff ;
    }
</style>
新建AppFrame.Vue文件
AppFrame.png

到这里Weex开发环境已经完成。接下来我们就要开始扩展组件和模块了。

扩展原生AppFrame Component

由于Weex开发的是单个页面,也没有系统的ViewController 和 NavigationController以及TabBarController,那么我们怎么开始一个架设一个动态的应用框架呢?这就需要我们书写一个原生的AppFrame组件。
因为是从无到有开发,我们就新建一个Xcode工程。
如果是已有工程那么就创建一个新的Target或者Project,或者新建工程通过pod导入。
创建一个 WXComponent子类。
然后重写-(instancetype)initWithRef:(NSString *)ref type:(NSString *)type styles:(NSDictionary *)styles attributes:(NSDictionary *)attributes events:(NSArray *)events weexInstance:(WXSDKInstance *)weexInstance方法,
方法实现如下

-(instancetype)initWithRef:(NSString *)ref type:(NSString *)type styles:(NSDictionary *)styles attributes:(NSDictionary *)attributes events:(NSArray *)events weexInstance:(WXSDKInstance *)weexInstance
{
    if (self = [super initWithRef:ref type:type styles:styles attributes:attributes events:events weexInstance:weexInstance]) {
        
        [self firstInitAPPFrameWithAttributes:attributes];
        
    }
    return self;
}

类文件如下图


AppFrameComponent.png
这样做的思路:

tabbarItem的数据通过服务端下发的形式进行创建。
以及tabbarViewController.ViewControllers也通过服务端下发的形式进行创建。
-------------2017.05.24 明晚再写-----------------
如果App设计是最常用的TabbarUI,那么下发的tabbarItem的数据结构如下:

 {
//                            标题
                            title:'',
//                            没选中字体颜色
                            normalTitleColor:'999999',
//                            选中字体颜色
                            selectedTitleColor:'FF3C00',
//                            背景颜色
                            tintColor:'FF3C00',
//                            选中图片
                            selectedImage:'',
//                            没选中图片
                            image:''
}

当然有非常规的UI那么同样需要配置的数据有这些,其他就需要针对应用来自己设计数据结构。
导航栏也同样需要考虑系统的是否满足需求,不满足的那么就在当前页面做一个假的导航栏(这样有一定的缺陷,比如我们再通话中使用app那么假的导航栏需要处理成新的高度)。下边是针对系统导航栏的下发数据

{
//                            导航栏标题
                            title:'',
//                            透明导航栏的字体颜色
                            clearTitleColor:'ffffff',
//                            高斯模糊导航栏的字体颜色
                            blurTitleColor:'000000',
//                            左边选项按钮
                            leftItemsInfo:
                                [
                                    {
//                                        原生调取 weex方法名
                                        aciton:'',
//                                        渲染 按钮的链接  如果以http开头 会渲染网络JSBundle 反之 渲染本地JSBundle
                                        itemURL:''
                                    }
                                ],
//                            右边选项按钮
                            rightItemsInfo:
                                [
                                    {
                                        aciton:'',
                                        itemURL:host + dirctoryPath + 'DemoRefreshRightItem.js',
//                                        设计图中container的大小
                                        frame:'{{0, 0}, {32, 16}}'
                                    }
                                ],
//                            把导航栏变成透明的
                            clearNavigationBar:true,
//                            把导航栏隐藏
                            hiddenNavgitionBar:false,
//                            导航栏背景颜色
                            navigationBarBackgroundColor:'',
//                            导航栏背景图片
                            navgationBarBackgroundImage:'',
//                            自定义标题视图的JSBundle地址 如果以http开头 会渲染网络JSBundle 反之 渲染本地JSBundle
                            customTitleViewURL:'',
//                        这个是当前导航栏栈顶的JSBundleURL
                            rootViewURL:host + dirctoryPath + 'index.js',
                        }
渲染过程

根据应用的设计我们需要作出不同的AppFrame调整,这个是毋庸置疑。再次回到原生APPFrameComponent类中。这里大体说一下WXSDKInstance在iOS端的渲染过程,具体的还是要看源码,每个人读的时候都有自己的见解认识。
1.从- (void)renderWithURL:(NSURL *)url开始,[WXResourceRequest requestWithURL:url resourceType:WXResourceTypeMainBundle referrer:@"" cachePolicy:NSURLRequestUseProtocolCachePolicy]处理URLRequest,_mainBundleLoader = [[WXResourceLoader alloc] initWithRequest:request]; [_mainBundleLoader start];下载JSBundle源。不赘述如何处理的URLRequest,看一下在下载之前都处理什么(代码有缺失,详尽请看源码)。

- (void)renderWithURL:(NSURL *)url options:(NSDictionary *)options data:(id)data
{
    WXResourceRequest *request = [WXResourceRequest requestWithURL:url resourceType:WXResourceTypeMainBundle referrer:@"" cachePolicy:NSURLRequestUseProtocolCachePolicy];
    [self _renderWithRequest:request options:options data:data];
}
- (void)_renderWithRequest:(WXResourceRequest *)request options:(NSDictionary *)options data:(id)data;
{
    NSURL *url = request.URL;
    _scriptURL = url;
    _jsData = data;
    NSMutableDictionary *newOptions = [options mutableCopy] ?: [NSMutableDictionary new];
    
    if (!newOptions[bundleUrlOptionKey]) {
        newOptions[bundleUrlOptionKey] = url.absoluteString;
    }
    // compatible with some wrong type, remove this hopefully in the future.
    if ([newOptions[bundleUrlOptionKey] isKindOfClass:[NSURL class]]) {
        WXLogWarning(@"Error type in options with key:bundleUrl, should be of type NSString, not NSURL!");
        newOptions[bundleUrlOptionKey] = ((NSURL*)newOptions[bundleUrlOptionKey]).absoluteString;
    }
    _options = [newOptions copy];
  //获取网站主页地址
    if (!self.pageName || [self.pageName isEqualToString:@""]) {
        self.pageName = [WXUtility urlByDeletingParameters:url].absoluteString ? : @"";
    }
    //这里会配置一些请求信息 比如手机系统 手机型号 app名字 屏幕信息等
    request.userAgent = [WXUtility userAgent];
    //告诉监视器WXMonitor开始下载JSBundle
    WX_MONITOR_INSTANCE_PERF_START(WXPTJSDownload, self);
    __weak typeof(self) weakSelf = self;
    _mainBundleLoader = [[WXResourceLoader alloc] initWithRequest:request];;
    _mainBundleLoader.onFinished = ^(WXResourceResponse *response, NSData *data) {
        __strong typeof(weakSelf) strongSelf = weakSelf;
        //这里省略源码,此处验证请求回调数据是否有效 并告知WXMonitor请求结果
        NSString *jsBundleString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
        if (!jsBundleString) {
            WX_MONITOR_FAIL_ON_PAGE(WXMTJSDownload, WX_ERR_JSBUNDLE_STRING_CONVERT, @"data converting to string failed.", strongSelf.pageName)
            return;
        }
        WX_MONITOR_SUCCESS_ON_PAGE(WXMTJSDownload, strongSelf.pageName);
        WX_MONITOR_INSTANCE_PERF_END(WXPTJSDownload, strongSelf);
        [strongSelf _renderWithMainBundleString:jsBundleString];
    };
    _mainBundleLoader.onFailed = ^(NSError *loadError) {
//此处省略源码 处理报错信息
    };
    [_mainBundleLoader start];
}

2.开始渲染JSBundle,
首先会验证WXSDKInstance的一些信息是否有误,并打出相应Log。
接下来会创建WXRootView调用instanceonCreate(UIView * view){ } block。
重新调用一下引擎默认配置(

{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        [self _registerDefaultComponents];
        [self _registerDefaultModules];
        [self _registerDefaultHandlers];
    });
}```)
通过```WXSDKManager```取得```WXBridgeManager```实例,调用```- (void)createInstance:(NSString *)instance template:(NSString *)temp   options:(NSDictionary *)options data:(id)data
```还是会验证参数是否有效,看渲染堆```instanceIdStack```中是否含有该```instanceId```,有的话看是否有在渲染的```instanceId```,有就添加任务,没有就把该id置顶创建,让```WXBridgeContext```实例去渲染(该操作验证并必须在```WXBridgeThread```)。到此就开始了JS引擎的渲染,可想而知会生成DOM树,然后逐个解析UI Component(js call native 创建以及渲染UI)会从```WXComponent```的```initWithRefxxxxx```开始。
######应用BasicWeexViewController的实现
不多赘述,按照官方给的思路,很容易就能封装出。这是我项目中的[XMWXViewController](https://github.com/jiaowoxiaoming/XMWeex/blob/master/XMWeex/XMWeex/ViewControllers/XMWXViewController.m)
######AppFrameComponent 实现
接上,AppFrame要做的就是将下发的tabbarItem 和 navigationItem形成框架 和UI。不同UI设计对用不同的AppFrameComponent 实现。
下面是最常用最简单(我项目中的[XMWXAPPFrameComponte](https://github.com/jiaowoxiaoming/XMWeex/blob/master/XMWeex/XMWeex/WeexComponent/APPFrame/Basic/XMWXAPPFrameComponte.m))的实现:

pragma mark - private method

/**
初始化APP框架

@param attributes 返回的RenderInfo
*/
-(void)firstInitAPPFrameWithAttributes:(NSDictionary *)attributes
{
dispatch_async(dispatch_get_main_queue(), ^{
//设置APP
UIApplication * application = [UIApplication sharedApplication];
UITabBarController * tabarViewController = [[UITabBarController alloc] init];
// tabarViewController.view.alpha = 0;
UIWindow * window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
[((UIResponder *)application.delegate) setValue:window forKey:@"window"];

    window.rootViewController = tabarViewController;
    
    window.backgroundColor = [UIColor whiteColor];
    
    [window makeKeyAndVisible];
    
    [self handleTabbarViewControllers:attributes tabarController:tabarViewController];
});

}
/**
创建tabbar.items
@param attributes component 下发数据
@param tabarController [UIApplication sharedApplication].keyWindow.rootViewController
@return 创建的UITabBarItem集合
*/
-(NSMutableArray <UITabBarItem *> *)handleTabbarItems:(NSDictionary *)attributes tabarController:(UITabBarController __kindof * )tabarController
{
NSString * tabItemsDictJsonString = [WXConvert NSString:attributes[XMWXAPPFrameComponteTabbarItemsKey]];

NSArray * tabItemsInfoArray = [NSJSONSerialization JSONObjectWithData:[tabItemsDictJsonString dataUsingEncoding:NSUTF8StringEncoding] options:(NSJSONReadingAllowFragments) error:nil];

NSMutableArray * tabarItems = [NSMutableArray array];

[tabItemsInfoArray enumerateObjectsUsingBlock:^(NSDictionary *  _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
    XMWXTabbarItem * xmItem = [XMWXTabbarItem itemWithDict:obj];
    UITabBarItem * item = [[UITabBarItem alloc] init];
    
    item.title = xmItem.title;
    
    if (xmItem.tintColor.length) {
        [tabarController.tabBar setTintColor:colorWithHexString(xmItem.tintColor, 1.f)];
    }
    
    if (xmItem.normalTitleColor.length) {
        [item setTitleTextAttributes:@{NSForegroundColorAttributeName:colorWithHexString(xmItem.normalTitleColor, 1.f)} forState:(UIControlStateNormal)];
    }
    if (xmItem.selectedTitleColor.length) {
        [item setTitleTextAttributes:@{NSForegroundColorAttributeName:colorWithHexString(xmItem.selectedTitleColor, 1.f)} forState:(UIControlStateSelected)];
    }
    if ([xmItem.image hasPrefix:@"http"]) {
        
        [[SDWebImageManager sharedManager] downloadImageWithURL:[NSURL URLWithString:xmItem.image] options:0 progress:^(NSInteger receivedSize, NSInteger expectedSize) {
            
        } completed:^(UIImage * _Nullable image, NSError * _Nullable error, SDImageCacheType cacheType, BOOL finished, NSURL * _Nullable imageURL) {
            item.image = [image imageWithRenderingMode:UIImageRenderingModeAlwaysOriginal];
            [tabarController.tabBar setItems:tabarItems];
        }];
    }else
    {
        item.image = xmwx_imageForSetting(xmItem.image);
    }
    
    if ([xmItem.selectedImage hasPrefix:@"http"]) {
        
        [[SDWebImageManager sharedManager] downloadImageWithURL:[NSURL URLWithString:xmItem.selectedImage] options:0 progress:^(NSInteger receivedSize, NSInteger expectedSize) {
            
        } completed:^(UIImage * _Nullable image, NSError * _Nullable error, SDImageCacheType cacheType, BOOL finished, NSURL * _Nullable imageURL) {
            item.image = [image imageWithRenderingMode:UIImageRenderingModeAlwaysOriginal];

            [tabarController.tabBar setItems:tabarItems];
        }];
        
    }else
    {
        item.selectedImage = xmwx_imageForSetting(xmItem.selectedImage);
    }
    
    [tabarItems addObject:item];
}];
return tabarItems;

}
/**
渲染TabbarViewController

@param attributes component 下发数据
@param tabarController [UIApplication sharedApplication].keyWindow.rootViewController
*/
-(void)handleTabbarViewControllers:(NSDictionary *)attributes tabarController:(UITabBarController __kindof * )tabarController
{
NSMutableArray <UITabBarItem *> * tabbarItems = [self handleTabbarItems:attributes tabarController:tabarController];
NSArray * viewControllerItems = [NSJSONSerialization JSONObjectWithData:[[WXConvert NSString:[attributes objectForKey:XMWXAPPFrameComponteViewControllerItemsKey]] dataUsingEncoding:NSUTF8StringEncoding] options:NSJSONReadingAllowFragments error:nil];

NSMutableArray * viewControllers = [NSMutableArray array];

[viewControllerItems enumerateObjectsUsingBlock:^(NSDictionary *  _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
    
    XMWXNavigationItem * navigationItem = [XMWXNavigationItem infoWithDict:obj];
    
    XMWXViewController * viewController = [[XMWXViewController alloc] init];
    
    viewController.renderInfo = navigationItem;
    if (navigationItem.rootViewURL.length > 0) {
        if ([navigationItem.rootViewURL hasPrefix:@"http"]) {
            viewController.renderURL = [NSURL URLWithString:navigationItem.rootViewURL];
        }else
        {
            NSString * path = [[NSBundle mainBundle] pathForResource:navigationItem.rootViewURL ofType:@""];
            if (path) {
                viewController.renderURL = [NSURL fileURLWithPath:path];
            }
        }
    }
    
    XMWXViewController * __weak weakViewController = viewController;
    viewController.instance.onCreate = ^(UIView * view)
    {
        XMWXViewController * __strong vc = weakViewController;
        [vc.view addSubview:view];
    };
    viewController.instance.frame = viewController.view.bounds;
    viewController.instance.onLayoutChange = ^(UIView *view)
    {
        XMWXViewController * __strong vc = weakViewController;
        vc.instance.frame = vc.view.bounds;
    };
    
    UINavigationController * nav = [[UINavigationController alloc] initWithRootViewController:viewController];
    nav.tabBarItem = [tabbarItems objectAtIndex:idx];
    [viewControllers addObject:nav];
}];

[tabarController setViewControllers:viewControllers animated:YES];

}
/**
数据更改的时候调用
@param attributes component属性数据
*/
-(void)updateAttributes:(NSDictionary *)attributes
{
if ([UIApplication sharedApplication].keyWindow.rootViewController) {
[self handleTabbarItems:attributes tabarController:(UITabBarController *)[UIApplication sharedApplication].keyWindow.rootViewController];
[self handleTabbarViewControllers:attributes tabarController:(UITabBarController *)[UIApplication sharedApplication].keyWindow.rootViewController];
}else
{
[self firstInitAPPFrameWithAttributes:attributes];
}

}

到此原生Component已经完成。
那么我们需要回到我们初始化Weex环境的方法中加入```    //通过配置这个Component参数来配置程序框架HTML标签名
    [WXSDKEngine registerComponent:@"AppFrame" withClass:NSClassFromString(@"xxxAPPFrameComponte")];```
######开始书写[AppFrame.Vue](https://github.com/jiaowoxiaoming/app_weex/blob/master/app_weex/src/Components/Frame/AppFrame.vue)
回到WebStorm,加入如下代码

<template>
<AppFrame id='AppFrame' :tabarItems="tabbarItemsJsonString" :viewControllerItems="viewControllerItemsString"></AppFrame>
</template>这样就可以使用我们扩展的AppFrameComponenttabbarItemsJsonStringviewControllerItemsString```需要添加下面代码

<script>
    export default {
        data () {
            return {
                tabbarItemsJsonString:JSON.stringify(
                    [{上边的TabbarItem数据}]),
                viewControllerItemsString:JSON.stringify(
                    [{上边的navigationBarItem数据}]),
            };
        },
        methods: {}
    }
</script>

完整的AppFrame.Vue
到这里其实就可以编译我们的Xcode工程了。

开发中的联动调试

那么如何看我们的效果呢?可能从一开始就应该跟大家说如何联动调试,但是我觉得读到这里大家并没有开始着手做,只是看,所以也就没有中间如何联动调试的步骤,大家真的上手做的时候,中间是少不了的,到时候大家再看下面内容。
建议大家还是使用WebStorm,本文就是基于此IDE开发的Weex Project。
还有这里涉及到单页面的调试还是应用的整体调试。单页面调试还是用Weex官方提供的Playground,如何进行单页面的调试,Weex文档说明了。那么如何进行整个应用的联动调试呢?
其实就是实时将Weex project 打包,然后用部署到本机服务器。通过扫码或者本地写死AppFrame的renderURL。
Weex没有提及这样的调试,这里就详细说明。
在Weex Project中找到package.json文件更改serve后的端口(或者直接使用8080,可以更改为8081什么的)。操作如图

更改端口.png

然后在WebStorm中打开终端(使用终端App的话需要先cd到项目目录下)执行

npm install

npm run serve

成功如下图


run serve.png

创建一个dist目录
然后在终端输入

weex compile src dist

上边命令是将src目录下全部生成JSBundle文件
下面命令是针对某一个Vue文件生成JSBundle

weex compile src/xxx.vue dist
Weex打包JSBundle.png
这个时候我们就可以将
http://本机IP:8083/dist/components/Frame/AppFrame.js
转化成二维码,用XMWeex编译的App扫描生成的二维码或者将你自己现在开发的加一个二维码扫描,甚至你也可以写死,直接渲染上述地址。
如此方式实现AppDelegate
代码摘要:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    // Override point for customization after application launch.
    UITabBarController * tabarViewController = [[UITabBarController alloc] init];
    
    UIWindow * window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
    self.window = window;
    window.rootViewController = tabarViewController;
    
    
    window.backgroundColor = [UIColor whiteColor];
    [window makeKeyAndVisible];
    [WXAppConfiguration setAppGroup:@"application"];
    [WXAppConfiguration setAppName:@"application"];
    [WXAppConfiguration setAppVersion:@"1.0"];
    
    //init sdk enviroment
    [WXSDKEngine initSDKEnvironment];
    [WXSDKEngine registerModule:@"XMWXModule" withClass:NSClassFromString(@"XMWXModule")];
    [WXSDKEngine registerHandler:[XMWXWebImage new] withProtocol:@protocol(WXImgLoaderProtocol)];
    //通过配置这个Component参数来配置程序框架HTML标签名
    [WXSDKEngine registerComponent:@"AppFrame" withClass:NSClassFromString(@"XMWXAPPFrameComponte")];
    
#if TARGET_IPHONE_SIMULATOR//模拟器
    NSString * renderURL = @"http://192.167.0.3:8083/dist/components/Frame/AppFrame.js";
    //    NSString * renderURL = [NSString stringWithFormat:@"%@%@",host,@"AppFrame.weex.js"];
    [self instance:renderURL];
    
#elif TARGET_OS_IPHONE//真机
    XMWXScanViewController * scanVC = [[XMWXScanViewController alloc] init];
    tabarViewController.viewControllers = @[scanVC];
#endif
    
    [WXLog setLogLevel:WXLogLevelError];
    return YES;
}
-(WXSDKInstance *)instance:(NSString *)renderURLString
{
    if (!_instance) {
        _instance = [[WXSDKInstance alloc] init];
        [[NSURLCache sharedURLCache] removeAllCachedResponses];
//
        [_instance renderWithURL:[NSURL URLWithString:renderURLString]];
        
    }
    return _instance;
}

这个时候你已经能得到类似如下的App框架。

AppFrame.jpg
如果读到这,你会发现其实我们的这个AppFrame的页面并没有开发。其实渲染出的就是一个ViewController。
那么下面我们要做的就是开发每一个模块。循序渐进,从
下一篇Weex 从无到有开发一款上线应用 2
上一篇下一篇

猜你喜欢

热点阅读