《weex 實踐指北》第二章:客戶端接入 weex(iOS 視角)
對于你的“容器”而言,weex只是View層。
“請牢記上述的格言”,個人推薦大家在配置Cocoapods的時候,既要有weex團隊打包好的SDK,也要有引入源碼的準備。
pod 'WeexSDK', '0.9.4'
# pod 'WeexSDK', :path => '/User/icepy/weex/ios/sdk'
pod 'WXDevtool', '0.8.2'
# pod 'WXDevtool', :path => '/User/icepy/WXDevtool'
默認,你已經把環境準備完畢
初始化
既然weex只是View層,對于它接入,應該就比較簡單了。
參考: https://weex-project.io/doc/advanced/integrate-to-ios.html
參考: https://weex-project.io/doc/advanced/extend-to-ios.html
理論上,weex的初始化應該在App啟動時。但是,你也可以在特定的時候初始化weex。
[WXSDKEngine initSDKEnviroment];
當然,如果你需要有一點業務的配置,也可以使用 WXAppConfiguration ,因為這不是必須的。
[WXAppConfiguration setAppVersion:@""];
[WXAppConfiguration setAppName:@""];
[WXAppConfiguration setAppGroup:@""];
有時候日志信息,也是非常必要的,你可以使用 WXLog 來配置
[WXLog setLogLevel:WXLogLevelAll]
日志等級,有一組枚舉,你可以查看一下
typedef NS_ENUM(NSUInteger, WXLogLevel){
/**
* No logs 沒有日志
*/
WXLogLevelOff = 0,
/**
* Error only 僅僅是錯誤信息
*/
WXLogLevelError = WXLogFlagError,
/**
* Error and warning 錯誤和警告
*/
WXLogLevelWarning = WXLogLevelError | WXLogFlagWarning,
/**
* Error, warning and info 錯誤,警告,信息
*/
WXLogLevelInfo = WXLogLevelWarning | WXLogFlagInfo,
/**
* Log, warning info 這里的log代表了js層面的console.log,可以在Xcode控制臺中可見
*/
WXLogLevelLog = WXLogFlagLog | WXLogLevelInfo,
/**
* Error, warning, info and debug logs
*/
WXLogLevelDebug = WXLogLevelLog | WXLogFlagDebug,
/**
* All
*/
WXLogLevelAll = NSUIntegerMax
};
完善你的ViewController
前言講到了weex只是一層View,那么將它和UIView劃等號也是可以的。在你的VC中,應該有一個URL的屬性,可以接到js bundle 文件。
- (instancetype)initWithURL:(NSURL *)url
{
self = [super init];
if (self) {
_url = url;
}
return self;
}
@interface ViewController ()
@property(nonatomic, strong) WXSDKInstance *instance;
@property(nonatomic, strong) UIView *weexView;
@property(nonatomic, strong) NSURL *url;
@end
然后在 viewDidLoad 中進行初始化。
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
[self render]
}
- (void) render {
CGFloat width = self.view.frame.size.width;
[self.instance destroyInstance];
self.instance = [[WXSDKInstance alloc] init];
self.instance.viewController = self;
self.instance.frame = CGRectMake(self.view.frame.size.width - width, self.top, width, self.weexHeight);
__weak typeof(self) weakSelf = self;
self.instance.onCreate = ^(UIView *view){
[weakSelf.weexView removeFromSuperview];
weakSelf.weexView = view;
[weakSelf.view addSubview:weakSelf.weexView];
UIAccessibilityPostNotification(UIAccessibilityScreenChangedNotification, weakSelf.weexView);
};
self.instance.onFailed = ^(NSError *error){
NSLog(@"%@",error.userInfo);
};
self.instance.renderFinish = ^(UIView *view){
[weakSelf.instance fireGlobalEvent:@"geolocation" params:@{@"key":@"value"}];
};
[self.instance renderWithURL:self.url options:nil data:nil];
}
從上述的邏輯來看,應該可以推敲出,weex 渲染結果雖然需要一個UIView來承接,最后再將這個UIView替換掉系統的view,所以理論上,你可以生成多個weex的instance,來拼湊頁面,只需要設置好frame即可。weex 在代碼層面上提供了幾個block,來讓你感知到渲染階段的幾個周期,比如是渲染錯誤了,還是渲染完成了,還是在渲染創建之前。
最后,別忘記了在 dealloc 里調用一下 destroyInstanc 來銷毀 weex 實例,不然會內存溢出。
-(void)dealloc{
[self.instance destroyInstance];
}
到此,weex可以將你用web技術寫好的頁面,呈現在對應的View中。不過,一個應用如果都是純展示,那么到此就結束了,但往往不是。也許,你還需要各種各樣Native的能力,也許Native也需要各種各樣JS的能力。
且看看 weex 為我們提供了多少種方式。
JS Call Native
weex 提供了Module來讓JS主動Call Native,第一步你還是需要注冊你的 Module。
[WXSDKEngine registerModule:@"test-logger" withClass: [WXLoggerModule class]];
// WXLoggerModule.h
#import <Foundation/Foundation.h>
#import <WeexSDK/WXModuleProtocol.h>
@interface WXLoggerModule : NSObject <WXModuleProtocol>
@end
// WXLoggerModule.m
#import "WXLoggerModule.h"
#import <WeexSDK/WeexSDK.h>
@interface WXLoggerModule()
@end
@implementation WXLoggerModule
@synthesize weexInstance;
WX_EXPORT_METHOD(@selector(info:callback:));
- (void) info: (NSDictionary *) log callback:(WXKeepAliveCallback) callback{
NSLog(@"WXLoggerModule ----> : %@",log);
callback(@{@"success":@YES,@"address":@"beijing"},YES);
}
@end
// .js 文件
var logger = require('@weex-module/test-logger');
logger.info({name:"icepy"});
Native Call JS
目前來說Native主動去Call JS,還只能使用事件的方式。
var globalEvent = require('@weex-module/globalEvent');
globalEvent.addEventListener('nativecalljs',function (e){
});
self.instance.renderFinish = ^(UIView *view){
[weakSelf.instance fireGlobalEvent:@"nativecalljs" params:@{@"name":@"icepy"}];
};
組件
從業務層面來說JS與Native可以互相通信,這是非常重要的一點,但是如果當前端有無法實現的或者很難實現的組件時,這個時候Component就派上用場了。
[WXSDKEngine registerComponent:@"test-image" withClass:[WXImageComponent class]];
//WXImageComponent.h
#import <WeexSDK/WeexSDK.h>
@interface WXImageComponent : WXComponent
@property(nonatomic, strong) NSString *imageSrc;
@end
//WXImageComponent.m
#import "WXImageComponent.h"
static CGFloat IMAGEWIDTH = 320;
static CGFloat IMAGEHEIGHT = 320;
@implementation WXImageComponent
-(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]) {
_imageSrc = [WXConvert NSString:attributes[@"src"]];
}
return self;
}
-(void)viewDidLoad
{
[super viewDidLoad];
NSLog(@"DTWXImgaeCompnent ---- > %@",self.imageSrc);
UIImageView *imageView = [[UIImageView alloc] initWithFrame:CGRectMake(0, 20, IMAGEWIDTH, IMAGEHEIGHT)];
imageView.userInteractionEnabled = YES;
imageView.clipsToBounds = YES;
imageView.exclusiveTouch = YES;
imageView.contentMode = UIViewContentModeScaleToFill;
NSURL *imageURL = [NSURL URLWithString:self.imageSrc];
NSData *imageData = [NSData dataWithContentsOfURL:imageURL];
UIImage *image = [UIImage imageWithData:imageData];
imageView.image = image;
[self.view addSubview:imageView];
}
@end
使用起來就和使用Vue的組件類似
<test-image src=""></test-image>
其實,組件還有大量的生命周期,在這些生命周期內,你可以進行一些處理,總體來說,這是一個很有趣的事情。