初始化APPFrame
終端創(chuàng)建 Weex工程
weex init xxx_weex
安裝IDE插件
WebStorm安裝Weex語法支持和插件摩钙。由于最開始Weex文件為.we茸歧,現(xiàn)在Weex已經(jīng)完美支持Vue倚舀,所以現(xiàn)在也是用Vue開發(fā)。
具體安裝步驟如下
同樣的下載Vue.js
安裝后重啟WebStorm赴背。
由于WebStorm還不能直接創(chuàng)建Vue文件山宾,所以需要先添加一下Vue文件模板笔咽。操作如下
模板內(nèi)容如下
<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開發(fā)環(huán)境已經(jīng)完成。接下來我們就要開始擴展組件和模塊了蔫巩。
擴展原生AppFrame Component
由于Weex開發(fā)的是單個頁面谆棱,也沒有系統(tǒng)的ViewController 和 NavigationController以及TabBarController,那么我們怎么開始一個架設(shè)一個動態(tài)的應(yīng)用框架呢快压?這就需要我們書寫一個原生的AppFrame組件。
因為是從無到有開發(fā)础锐,我們就新建一個Xcode工程嗓节。
如果是已有工程那么就創(chuàng)建一個新的Target或者Project,或者新建工程通過pod導(dǎo)入皆警。
創(chuàng)建一個 WXComponent
子類拦宣。
然后重寫-(instancetype)initWithRef:(NSString *)ref type:(NSString *)type styles:(NSDictionary *)styles attributes:(NSDictionary *)attributes events:(NSArray *)events weexInstance:(WXSDKInstance *)weexInstance
方法,
方法實現(xiàn)如下
-(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
的數(shù)據(jù)通過服務(wù)端下發(fā)的形式進行創(chuàng)建。
以及tabbarViewController.ViewControllers
也通過服務(wù)端下發(fā)的形式進行創(chuàng)建信姓。
-------------2017.05.24 明晚再寫-----------------
如果App設(shè)計是最常用的TabbarUI鸵隧,那么下發(fā)的tabbarItem的數(shù)據(jù)結(jié)構(gòu)如下:
{
// 標(biāo)題
title:'',
// 沒選中字體顏色
normalTitleColor:'999999',
// 選中字體顏色
selectedTitleColor:'FF3C00',
// 背景顏色
tintColor:'FF3C00',
// 選中圖片
selectedImage:'',
// 沒選中圖片
image:''
}
當(dāng)然有非常規(guī)的UI那么同樣需要配置的數(shù)據(jù)有這些,其他就需要針對應(yīng)用來自己設(shè)計數(shù)據(jù)結(jié)構(gòu)意推。
導(dǎo)航欄也同樣需要考慮系統(tǒng)的是否滿足需求豆瘫,不滿足的那么就在當(dāng)前頁面做一個假的導(dǎo)航欄(這樣有一定的缺陷,比如我們再通話中使用app那么假的導(dǎo)航欄需要處理成新的高度)菊值。下邊是針對系統(tǒng)導(dǎo)航欄的下發(fā)數(shù)據(jù)
{
// 導(dǎo)航欄標(biāo)題
title:'',
// 透明導(dǎo)航欄的字體顏色
clearTitleColor:'ffffff',
// 高斯模糊導(dǎo)航欄的字體顏色
blurTitleColor:'000000',
// 左邊選項按鈕
leftItemsInfo:
[
{
// 原生調(diào)取 weex方法名
aciton:'',
// 渲染 按鈕的鏈接 如果以http開頭 會渲染網(wǎng)絡(luò)JSBundle 反之 渲染本地JSBundle
itemURL:''
}
],
// 右邊選項按鈕
rightItemsInfo:
[
{
aciton:'',
itemURL:host + dirctoryPath + 'DemoRefreshRightItem.js',
// 設(shè)計圖中container的大小
frame:'{{0, 0}, {32, 16}}'
}
],
// 把導(dǎo)航欄變成透明的
clearNavigationBar:true,
// 把導(dǎo)航欄隱藏
hiddenNavgitionBar:false,
// 導(dǎo)航欄背景顏色
navigationBarBackgroundColor:'',
// 導(dǎo)航欄背景圖片
navgationBarBackgroundImage:'',
// 自定義標(biāo)題視圖的JSBundle地址 如果以http開頭 會渲染網(wǎng)絡(luò)JSBundle 反之 渲染本地JSBundle
customTitleViewURL:'',
// 這個是當(dāng)前導(dǎo)航欄棧頂?shù)腏SBundleURL
rootViewURL:host + dirctoryPath + 'index.js',
}
渲染過程
根據(jù)應(yīng)用的設(shè)計我們需要作出不同的AppFrame調(diào)整外驱,這個是毋庸置疑。再次回到原生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];
//獲取網(wǎng)站主頁地址
if (!self.pageName || [self.pageName isEqualToString:@""]) {
self.pageName = [WXUtility urlByDeletingParameters:url].absoluteString ? : @"";
}
//這里會配置一些請求信息 比如手機系統(tǒng) 手機型號 app名字 屏幕信息等
request.userAgent = [WXUtility userAgent];
//告訴監(jiān)視器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;
//這里省略源碼犯助,此處驗證請求回調(diào)數(shù)據(jù)是否有效 并告知WXMonitor請求結(jié)果
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
的一些信息是否有誤维咸,并打出相應(yīng)Log也切。
接下來會創(chuàng)建WXRootView
調(diào)用instance
的onCreate(UIView * view){ }
block。
重新調(diào)用一下引擎默認配置(
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
[self _registerDefaultComponents];
[self _registerDefaultModules];
[self _registerDefaultHandlers];
});
}```)
通過```WXSDKManager```取得```WXBridgeManager```實例腰湾,調(diào)用```- (void)createInstance:(NSString *)instance template:(NSString *)temp options:(NSDictionary *)options data:(id)data
```還是會驗證參數(shù)是否有效雷恃,看渲染堆```instanceIdStack```中是否含有該```instanceId```,有的話看是否有在渲染的```instanceId```,有就添加任務(wù)费坊,沒有就把該id置頂創(chuàng)建倒槐,讓```WXBridgeContext```實例去渲染(該操作驗證并必須在```WXBridgeThread```)。到此就開始了JS引擎的渲染附井,可想而知會生成DOM樹讨越,然后逐個解析UI Component(js call native 創(chuàng)建以及渲染UI)會從```WXComponent```的```initWithRefxxxxx```開始两残。
######應(yīng)用BasicWeexViewController的實現(xiàn)
不多贅述,按照官方給的思路把跨,很容易就能封裝出人弓。這是我項目中的[XMWXViewController](https://github.com/jiaowoxiaoming/XMWeex/blob/master/XMWeex/XMWeex/ViewControllers/XMWXViewController.m)
######AppFrameComponent 實現(xiàn)
接上,AppFrame要做的就是將下發(fā)的tabbarItem 和 navigationItem形成框架 和UI着逐。不同UI設(shè)計對用不同的AppFrameComponent 實現(xiàn)崔赌。
下面是最常用最簡單(我項目中的[XMWXAPPFrameComponte](https://github.com/jiaowoxiaoming/XMWeex/blob/master/XMWeex/XMWeex/WeexComponent/APPFrame/Basic/XMWXAPPFrameComponte.m))的實現(xiàn):
pragma mark - private method
/**
初始化APP框架
@param attributes 返回的RenderInfo
*/
-(void)firstInitAPPFrameWithAttributes:(NSDictionary *)attributes
{
dispatch_async(dispatch_get_main_queue(), ^{
//設(shè)置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];
});
}
/**
創(chuàng)建tabbar.items
@param attributes component 下發(fā)數(shù)據(jù)
@param tabarController [UIApplication sharedApplication].keyWindow.rootViewController
@return 創(chuàng)建的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 下發(fā)數(shù)據(jù)
@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];
}
/**
數(shù)據(jù)更改的時候調(diào)用
@param attributes component屬性數(shù)據(jù)
*/
-(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已經(jīng)完成。
那么我們需要回到我們初始化Weex環(huán)境的方法中加入``` //通過配置這個Component參數(shù)來配置程序框架HTML標(biāo)簽名
[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數(shù)據(jù)}]),
viewControllerItemsString:JSON.stringify(
[{上邊的navigationBarItem數(shù)據(jù)}]),
};
},
methods: {}
}
</script>
完整的AppFrame.Vue
到這里其實就可以編譯我們的Xcode工程了健芭。
開發(fā)中的聯(lián)動調(diào)試
那么如何看我們的效果呢?可能從一開始就應(yīng)該跟大家說如何聯(lián)動調(diào)試秀姐,但是我覺得讀到這里大家并沒有開始著手做慈迈,只是看,所以也就沒有中間如何聯(lián)動調(diào)試的步驟省有,大家真的上手做的時候痒留,中間是少不了的,到時候大家再看下面內(nèi)容蠢沿。
建議大家還是使用WebStorm狭瞎,本文就是基于此IDE開發(fā)的Weex Project。
還有這里涉及到單頁面的調(diào)試還是應(yīng)用的整體調(diào)試搏予。單頁面調(diào)試還是用Weex官方提供的Playground,如何進行單頁面的調(diào)試弧轧,Weex文檔說明了雪侥。那么如何進行整個應(yīng)用的聯(lián)動調(diào)試呢?
其實就是實時將Weex project 打包精绎,然后用部署到本機服務(wù)器速缨。通過掃碼或者本地寫死AppFrame的renderURL。
Weex沒有提及這樣的調(diào)試代乃,這里就詳細說明旬牲。
在Weex Project中找到package.json
文件更改serve
后的端口(或者直接使用8080,可以更改為8081什么的)。操作如圖
然后在WebStorm中打開終端(使用終端App的話需要先cd到項目目錄下)執(zhí)行
npm install
再
npm run serve
成功如下圖
創(chuàng)建一個dist目錄
然后在終端輸入
weex compile src dist
上邊命令是將src目錄下全部生成JSBundle文件
下面命令是針對某一個Vue文件生成JSBundle
weex compile src/xxx.vue dist
這個時候我們就可以將
http://本機IP:8083/dist/components/Frame/AppFrame.js
轉(zhuǎn)化成二維碼搁吓,用XMWeex編譯的App掃描生成的二維碼或者將你自己現(xiàn)在開發(fā)的加一個二維碼掃描原茅,甚至你也可以寫死,直接渲染上述地址堕仔。
如此方式實現(xiàn)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參數(shù)來配置程序框架HTML標(biāo)簽名
[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;
}
這個時候你已經(jīng)能得到類似如下的App框架擂橘。
如果讀到這,你會發(fā)現(xiàn)其實我們的這個AppFrame的頁面并沒有開發(fā)摩骨。其實渲染出的就是一個ViewController通贞。
那么下面我們要做的就是開發(fā)每一個模塊朗若。循序漸進,從
我
下一篇Weex 從無到有開發(fā)一款上線應(yīng)用 2