初始化APPFrame
终端创建 Weex工程
weex init xxx_weex
安装IDE插件
WebStorm安装Weex语法支持和插件。由于最开始Weex文件为.we,现在Weex已经完美支持Vue,所以现在也是用Vue开发。
具体安装步骤如下
同样的下载Vue.js
安装后重启WebStorm。
由于WebStorm还不能直接创建Vue文件,所以需要先添加一下Vue文件模板。操作如下
模板内容如下
<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文件
到这里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;
}
类文件如下图
这样做的思路:
将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
调用instance
的onCreate(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>这样就可以使用我们扩展的
AppFrameComponent了
tabbarItemsJsonString和
viewControllerItemsString```需要添加下面代码
<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什么的)。操作如图
然后在WebStorm中打开终端(使用终端App的话需要先cd到项目目录下)执行
npm install
再
npm run serve
成功如下图
创建一个dist目录
然后在终端输入
weex compile src dist
上边命令是将src目录下全部生成JSBundle文件
下面命令是针对某一个Vue文件生成JSBundle
weex compile src/xxx.vue dist
这个时候我们就可以将
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的页面并没有开发。其实渲染出的就是一个ViewController。
那么下面我们要做的就是开发每一个模块。循序渐进,从
我
下一篇Weex 从无到有开发一款上线应用 2