注意:Weex 所有暴露给 JS 的内置 module 或 component API 都是安全和可控的,它们不会去访问系统的私有 API,也不会去做任何 runtime 上的 hack 更不会去改变应用原有的功能定位。如果需要扩展自定义的 module 或者 component ,一定注意不要将 OC 的 runtime 暴露给 JS ,不要将一些诸如
dlopen(),dlsym(),respondsToSelector:,performSelector:,method_exchangeImplementations()的动态和不可控的方法暴露给JS,也不要将系统的私有API暴露给JS。否则将可能面临苹果上架审核问题。 在这篇文档里,我们使用 Objective-C 来扩展 Weex 功能,如果想使用 Swift,可以参考 使用 Swift 扩展。
JSEnv 扩展
接口
1. NSDictionary *options = @{@"testVlaue":@"hello"};
2. 3. [_instance renderWithURL:url options:options data:nil];
4.
使用
1. var value = weex.config.testValue;
2. 3. console.log(value);
自定义 module
自定义 module,需要让自己的 class 遵循 WXModuleProtocol 这个protocol,通过 WX_EXPORT_METHOD 这个宏暴露出需要透出到 JavaScript 调用的方法,再向 WeexSDK 注册 module,就可以完成一个简单 module 的自定义。
* 新建一个基类为 NSObject 的 class WXCustomEventModule,让该类遵循 WXModuleProtocol 的协议。
* 添加打印的方法,通过 WX_EXPORT_METHOD 暴露该方法。
* 在初始化完成 Weex SDK 之后注册该 module。
到此,我们已经完成了一个简单的 module 方法的封装,在 JS 里使用方法如下:
1. weex.requireModule("event").showParams("hello Weex)
Module 进阶
关于 Module 和 Module 方法的执行特性(同步、异步;执行线程),需要了解:
* weexInstance在一个 Weex 页面中,默认 WXSDKInstance 的实例持有多个 module 的实例, 而 Module 的实例是是没有对 WXSDKInstance 做持有的,在自定义的 module 中添加 @synthesize weexInstance,module 实例可以对持有它本身的 WXSDKInstance 实例做一个弱引用,通过 weexInstance 可以拿到调用该 module 的页面的一些信息。
* targetExecuteThreadModule 方法默认会在 UI 线程(iOS 主线程)中被调用,建议不要在这做太多耗时的任务。如果你的任务不需要在 UI 线程执行或需要在特定线程执行,需要实现 WXModuleProtocol 中的 - (NSThread *) 的方法,并返回你希望方法执行所在的线程。
* WXModuleKeepAliveCallbackModule 支持返回值给 JavaScript 中的回调,回调的类型是 WXModuleKeepAliveCallback。回调的参数可以是 String 或者 Map。该 block 第一个参数为回调给 JavaScript 的数据,第二参数是一个 BOOL 值,表示该回调执行完成之后是否要被清除。JavaScript 每次调用都会产生一个回调,但是对于单独一次调用,是否要在完成该调用之后清除掉回调函数 id 就由这个选项控制,如非特殊场景,建议传 NO。
* WX_EXPORT_METHOD_SYNC使用 WX_EXPORT_METHOD 暴露到前端的方法都是异步方法(获得结果需要通过回调函数获得)。如果期望获得同步调用结果,可以使用 WX_EXPORT_METHOD_SYNC 声明同步的 Module 方法。
扩展组件
- 新建一个基类为
WXComponent的类。如果这个类里什么代码也不写,它和默认的的div组件能力是一致的。 - 覆盖
WXComponent中的生命周期方法 -
loadView一个组件默认对应于一个 view,如果未覆盖loadView提供自定义 view,会让WXComponent基类创建WXView。WXView继承自UIView。比如我们要实现一个组件支持地图功能,我们可以返回系统的MKMapView。-
- (UIView *)loadView {
- return [MKMapView new];
- }
-
-
viewDidLoad对组件 view 需要做一些配置,比如设置 delegate,可以在viewDidLoad生命周期做。如果当前 view 没有添加 subview 的话,不要设置 view 的 frame,WeexSDK 会根据 style 进行排版后设置。-
- (void)viewDidLoad {
- ((MKMapView*)self.view).delegate = self;
- }
-
-
注册组件
- [WXSDKEngine registerComponent:@"map" withClass:[WXMapComponent class]];
-
在前端代码中使用新组件
自定义事件
-
对于每个组件,WeexSDK 默认提供了一些事件能力,如点击等。假如想给我们的地图组件提供
mapLoaded事件。-
- export default {
- methods: {
- onMapLoaded:function(e) {
- console.log("map loaded"+JSON.stringify(e))
- }
- }
- }
-
覆盖组件生命周期方法,记录事件是否需要处理我们需要额外添加一个
BOOL成员mapLoaded用来记录该事件是否生效。-
- (void)addEvent:(NSString *)eventName {
- if ([eventName isEqualToString:@"mapLoaded"]) {
- _mapLoaded = YES;
- }
- }
-
-
- (void)removeEvent:(NSString *)eventName {
-
- if ([eventName isEqualToString:@"mapLoaded"]) {
- _mapLoaded = NO;
- }
- }
-
-
给前端发送事件
-
- (void)mapViewDidFinishLoadingMap:(MKMapView *)mapView {
- if (_mapLoaded) {
- [self fireEvent:@"mapLoaded" params:@{@"customKey":@"customValue"} domChanges:nil];
- }
- }
-
自定义属性
给我们的地图组件添加一个新的属性 showTraffic。在前端代码里可以控制组件是否显示车流情况。
1.
2.
-
覆盖组件初始化方法
initWithRef…给组件添加一个成员变量记录showTraffic属性的值,并在 init 方法中初始化。-
- (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]) {
- if (attributes[@"showsTraffic"]) {
- _showsTraffic = [WXConvert BOOL: attributes[@"showsTraffic"]];
- }
- }
- return self;
- }
-
-
在生命期事件中记得将属性值同步给地图控件
-
- (void)viewDidLoad {
- ((MKMapView*)self.view).showsTraffic = _showsTraffic;
- }
-
-
当属性更新时,同步给地图控件
-
- (void)updateAttributes:(NSDictionary *)attributes {
- if (attributes[@"showsTraffic"]) {
- _showsTraffic = [WXConvert BOOL: attributes[@"showsTraffic"]];
- ((MKMapView*)self.view).showsTraffic = _showsTraffic;
- }
- }
-
更多的组件生命期方法
组件是由 Weex 管理的,比如创建、布局、渲染、销毁。Weex 组件的生命周期方法都是可以重写的,你可以在这些生命周期中去做自己的事情。
方法| 描述
---|---
initWithRef:type:…| 用给定的属性初始化一个component.
layoutDidFinish| 在component完成布局时候会调用.
loadView| 创建component管理的view.
viewWillLoad| 在component的view加载之前会调用.
viewDidLoad| 在component的view加载完之后调用.
viewWillUnload| 在component的view被释放之前调用.
viewDidUnload| 在component的view被释放之后调用.
updateStyles:| 在component的style更新时候调用.
updateAttributes:| 在component的attribute更新时候调用.
addEvent:| 给component添加event的时候调用.
removeEvent:| 在event移除的时候调用.
给组件添加方法
在组件代码中使用宏 WX_EXPORT_METHOD 声明组件方法供前端调用。
1. @implementation WXMyComponent
2. 3. WX_EXPORT_METHOD(@selector(focus))
4. 5. - (instancetype)initWithRef:(NSString *)ref type:(NSString *)type styles:(NSDictionary *)styles attributes:(NSDictionary *)attributes events:(NSArray *)events weexInstance:(WXSDKInstance *)weexInstance {
6. if (self = [super initWithRef:ref type:type styles:styles attributes:attributes events:events weexInstance:weexInstance]) {
7. // handle your attributes
8. // handle your styles
9. }
10. 11. return self;
12. }
13. 14. - (void)focus {
15. NSLog(@"you got it");
16. }
17. @end
在 JS 中调用 focus 方法。
1.
2.
获取组件的 CSS 样式
-
在 0.19 版本之前,Weex 使用 Yoga 排版引擎,可以通过访问 WXComponent 的 cssNode 属性获取。例如:
- self.cssNode->style.flex = 1.0;
- float height = self.cssNode->style.dimensions[CSS_HEIGHT];
- self.cssNode->style.flex = 1.0;
-
从 0.19 版本开始,Weex 使用自已的排版引擎,是 C++ 代码。获取 CSS 属性可以从 WXComponent 的 styles 字典(NSDictionary)里获取,也可以访问 WXComponent 的 flexCssNode 属性,其类型为 C++ 类 WeexCore::WXCoreLayoutNode。注意,需要源文件为 .mm 类型。
-
从 0.20 版本开始,iOS 接入 WeexCore,排版引擎也沉入 WeexCore层,并且 CSS 相关属性不再上传给 WXComponent 对象,WXComponent 对象的 styles 字典里只有非 CSS 样式。我们的目的是上层 UI 组件只需要关心排版引擎生成的最终坐标即可,不需要关心前端标记的 CSS 属性。如果仍然需要获取 CSS 样式,可以在 .mm 文件中直接访问 flexCssNode,或通过 WXComponent+Layout.h 文件中提供的扩展方法。
- /**
-
- @abstract Get css style value for key. The key should be of CSS standard form.
-
- This method is for convenience use in C/ObjC environment. And if you want to
-
- retrieve all style values or in C++, you could use flexCssNode directly.
- *
-
- Thread usage:
-
- This method should be invoked in component thread by WXPerformBlockOnComponentThread.
-
- Note that all initWithRef methods of WXComponent and its subclasses are performed in
-
- component thread by default. Therefore you can call this method directly in initWithRef.
- *
-
- Supported keys:
-
- width, height, min-width, min-height, max-width, max-height,
-
- margin-(left/right/top/bottom)
-
- padding-(left/right/top/bottom)
-
- border-(left/right/top/bottom)-width
-
- left, right, top, bottom
-
- flex-grow
- */
-
- (float)getCssStyleValueForKey:(NSString *)key;
-
- // Other methods which should also be used in component thread.
-
- (WXCoreFlexDirection)getCssStyleFlexDirection;
-
- (WXCoreFlexWrap)getCssStyleFlexWrap;
-
- (WXCoreJustifyContent)getCssStyleJustifyContent;
-
- (WXCoreAlignItems)getCssStyleAlignItems;
-
- (WXCoreAlignSelf)getCssStyleAlignSelf;
-
- (WXCorePositionType)getCssStylePositionType;
-
- (WXCoreDirection)getCssDirection;
-
- /**
自定义 handler
Handler 的作用是什么?比如 WeexSDK 目前没有提供图片下载的能力,而是在 WXImgLoaderProtocol 定义了一些获取图片的接口。Image 组件正是通过 WXImgLoaderProtocol 获得并展示图片,开发者可以实现该 protocol 中的接口方法,这样 image 标签才能正常展示图片。
开发者也可以定义自己的 protocol 和对应的实现来使用 handler 机制。常见的一个场景就是在每个 App 中需要特定的 Handler 来实现一些公共的能力。
* 新建基类为 NSObject 的类,并实现 WXImgLoaderProtocol 协议,实现 WXImgLoaderProtocol 的方法。下面加载图片的逻辑需要依赖 SDWebImage,你也可以不依赖 SDWebimage 使用自己的方式加载对应 URL 图片。
1. @implementation WXImgLoaderDefaultImpl
2. - (id<WXImageOperationProtocol>)downloadImageWithURL:(NSString *)url imageFrame:(CGRect)imageFrame userInfo:(NSDictionary *)userInfo completed:(void(^)(UIImage *image, NSError *error, BOOL finished))completedBlock {
3. if ([url hasPrefix:@"//"]) {
4. url = [@"http:" stringByAppendingString:url];
5. }
6. return (id<WXImageOperationProtocol>)[[SDWebImageManager sharedManager] downloadImageWithURL:[NSURL URLWithString:url] options:0 progress:^(NSInteger receivedSize, NSInteger expectedSize) {
7. } completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) {
8. if (completedBlock) {
9. completedBlock(image, error, finished);
10. }
11. }];
12. }
13. @end
-
注册
- [WXSDKEngine registerHandler:[WXImgLoaderDefaultImpl new] withProtocol:@protocol(WXImgLoaderProtocol)];
-
使用
- id
imageLoader = [WXSDKEngine handlerForProtocol:@protocol(WXImgLoaderProtocol)]; - [iamgeLoader downloadImageWithURL:imageURl imageFrame:frame userInfo:customParam completed:^(UIImage *image, NSError *error, BOOL finished) {
- }];
- id
