NOTICE: All of the exported APIs in Weex are controllable and safe, they can not access private APIs or do any system hacks at runtime, neither can they change the primary purpose of the Application.
If you are extending your custom modules/components, be sure NOT to export the ability of Objective-C runtime, be sure NOT to export dynamic and uncontrolled methods such as
dlopen()
,dlsym()
,respondsToSelector:
,performSelector:
,method_exchangeImplementations()
, be sure NOT to export any private methods.
In this section we will talk about how to extend Weex on iOS using Objective-C. For Swift, please refer Extend using Swift
# JSEnv Extend
# Api
NSDictionary *options = @{@"testVlaue":@"hello"};
[_instance renderWithURL:url options:options data:nil];
# usage
var value = weex.config.testValue;
console.log(value);
# Extend module
To extend your custom weex modules in iOS, you must make your class conform to WXModuleProtocol
protocol, and then exports your method to javaScript using macro WX_EXPORT_METHOD
, finally register your module with your class and a self-define module name.
- New a class derived from
NSObject
conformingWXModuleProtocol
protocol - Add your module method and then exports using macro
WX_EXPORT_METHOD
- Register module after WeexSDK's initialization
By far, we've finished a basic custom module, and you may understand how to custom a weex module in iOS using Objective-C. We can use it in javaScript code like this:
weex.requireModule("event").showParams("hello Weex)
# Advanced
You must understand more in WXModuleProtocol
protocol, we'll talk more about blueprint methods and properties in this protocol.
weexInstance
The instance ofWXSDKInstance
holds the references of all modules created in a single page. if you add@synthesize weexInstance
in your module class, your module will hold a reference to the instance ofWXSDKInstance
who create and initialize your module, or you get nothing. You can get more details byweexInstance
such as pageName.targetExecuteThread
We will schedule your module method to main thread(UI thread), we highly recommend that you can not do much works here, if must, you can add implementation for this method. You can provide a thread so that we can schedule to.WXModuleKeepAliveCallback
Sometimes you can return your result to your caller, callback is important in this scene,the params for callback can be string or dictionary. You must specify a second params to keep your callback function id in js after used. We'll create a new function id every time callback,NO
will be a better choice for memory.WX_EXPORT_METHOD_SYNC
This feature only works on WeexSDK 0.10 and later. Synchronous method only works in JavaScript thread, you cannot do much works here. Export asynchronous method usingWX_EXPORT_METHOD
, you may get result in callback function. UseWX_EXPORT_METHOD_SYNC
to export synchronous method. You can get result on the left of operand=
.
# Extend component
New a class derived from
WXComponent
class or other subclasses ofWXComponent
. If we do nothing in this class and then register it to WeexSDK engine, its works just likediv
.Override the lifecycle of
WXComponent
loadView
We will load a view for a component. If you didn't override this method, supperclass will provide aWXView
derived fromUIView
. If we want to load html or just to show a map, overrideloadView
and provide a custom view is a good choice.
- (UIView *)loadView {
return [MKMapView new];
}
viewDidLoad
If you want to make some configurations for your custom view like set delegate, you can finish here. You don't need to set frame for your custom view if it doesn't has any subview, WeexSDK will set it's frame according to css style after layout.
- (void)viewDidLoad {
((MKMapView*)self.view).delegate = self;
}
- Register the component
[WXSDKEngine registerComponent:@"map" withClass:[WXMapComponent class]];
- Use the custom component
<template>
<div>
<map style="width:200px;height:200px"></map>
</div>
</template>
# Custom events
Weex engine has done some works to support common events and other attributes, if you want support your own attributes, let's continue.
- Custom events
Our goal is to support
mapLoaded
event for the component we just implemented. The front-end code can be like this.
<template>
<div>
<map style="width:200px;height:200px" @mapLoaded="onMapLoaded"></map>
</div>
</template>
<script>
export default {
methods: {
onMapLoaded:function(e) {
console.log("map loaded"+JSON.stringify(e))
}
}
}
</script>
- Override add/remove event in Objective-C
We must save status for event added or not, so we add a
BOOL
member namedmapLoaded
for the component class to make it record, and when event map loaded, we can fire event according to this record.
- (void)addEvent:(NSString *)eventName {
if ([eventName isEqualToString:@"mapLoaded"]) {
_mapLoaded = YES;
}
}
- (void)removeEvent:(NSString *)eventName {
if ([eventName isEqualToString:@"mapLoaded"]) {
_mapLoaded = NO;
}
}
- Fire event to front-end
- (void)mapViewDidFinishLoadingMap:(MKMapView *)mapView {
if (_mapLoaded) {
[self fireEvent:@"mapLoaded" params:@{@"customKey":@"customValue"} domChanges:nil];
}
}
# Custom attributes
The next target is that we add a custom attribute showTraffic
, we can display real time traffic or not according to this attribute. The front-end code can be like the following.
<template>
<div>
<map style="width:200px;height:200px" showTraffic="true"></map>
</div>
</template>
- Override component init method
initWithRef...
Add aBOOL
membershowsTraffic
to make the status whether front-end user use the attribute or not record. We can get all the attribute for this component by override init method of component.
- (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;
}
- Set property for custom view when it is loaded.
- (void)viewDidLoad {
((MKMapView*)self.view).showsTraffic = _showsTraffic;
}
- Handle attribute updates
- (void)updateAttributes:(NSDictionary *)attributes {
if (attributes[@"showsTraffic"]) {
_showsTraffic = [WXConvert BOOL: attributes[@"showsTraffic"]];
((MKMapView*)self.view).showsTraffic = _showsTraffic;
}
}
# More life-cycle methods
A native Component has a life cycle managed by Weex. Weex creates it, layout it, renders it and destroys it.
Weex offers component life cycle hooks that give you visibility into these key moments and the ability to act when they occur.
method | description |
---|---|
initWithRef:type:... | Initializes a new component using the specified properties. |
layoutDidFinish | Called when the component has just laid out. |
loadView | Creates the view that the component manages. |
viewWillLoad | Called before the load of component's view . |
viewDidLoad | Called after the component's view is loaded and set. |
viewWillUnload | Called just before releasing the component's view. |
viewDidUnload | Called when the component's view is released. |
updateStyles: | Called when component's style are updated. |
updateAttributes: | Called when component's attributes are updated. |
addEvent: | Called when adding an event to the component. |
removeEvent: | Called when removing an event frome the component. |
# Add method for component
You can define your component method by macro WX_EXPORT_METHOD
@implementation WXMyComponent
WX_EXPORT_METHOD(@selector(focus))
- (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]) {
// handle your attributes
// handle your styles
}
return self;
}
- (void)focus {
NSLog(@"you got it");
}
@end
You can call focus
in your js file.
<template>
<mycomponent ref='mycomponent'></mycomponent>
</template>
<script>
module.exports = {
created: function() {
this.$refs.mycomponent.focus();
}
}
</script>
# Get css style of component
- Before v0.19, Weex used Yoga layout engine. You can access css styles via cssNode of WXCompoent. Such as
self.cssNode->style.flex = 1.0;
float height = self.cssNode->style.dimensions[CSS_HEIGHT];
From v0.19, Weex replaced layout engine which is C++ codes. You can get css styles from styles dictionary of a WXComponent. You can also access flexCssNode property which is of type WeexCore::WXCoreLayoutNode, but must in .mm files.
From v0.20, WeexCore is imported to iOS, and css styles are never uploaded to WXComponent. The styles dictionary only contains non-css styles. We think that upper UI components should only care about final layout results generated by layout engine. If you still want to access css styles, you may use flexCssNode in .mm files or by extension methods provided in 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;
# Custom handlers
What is the purpose of a handler? We don't provide functionality for downloading image but defines a blueprint of methods in WXImgLoaderProtocol
for loading image, and image component get image content from these methods. You must implement methods in WXImgLoaderProtocol
except the optional
methods to display image from specified url.
You can also define your own protocol
and implement its handler.
- New a class derived from
NSObject
conformingWXImgLoaderProtocol
and then add implementation for methods inWXImgLoaderProtocol
.
The flowing code may require SDWebImage as dependency, you can download remote url image by your own way without SDWebImage.
@implementation WXImgLoaderDefaultImpl
- (id<WXImageOperationProtocol>)downloadImageWithURL:(NSString *)url imageFrame:(CGRect)imageFrame userInfo:(NSDictionary *)userInfo completed:(void(^)(UIImage *image, NSError *error, BOOL finished))completedBlock {
if ([url hasPrefix:@"//"]) {
url = [@"http:" stringByAppendingString:url];
}
return (id<WXImageOperationProtocol>)[[SDWebImageManager sharedManager] downloadImageWithURL:[NSURL URLWithString:url] options:0 progress:^(NSInteger receivedSize, NSInteger expectedSize) {
} completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) {
if (completedBlock) {
completedBlock(image, error, finished);
}
}];
}
@end
- Register handler for a protocol
[WXSDKEngine registerHandler:[WXImgLoaderDefaultImpl new] withProtocol:@protocol(WXImgLoaderProtocol)];
- Use the handler
id<WXImgLoaderProtocol> imageLoader = [WXSDKEngine handlerForProtocol:@protocol(WXImgLoaderProtocol)];
[iamgeLoader downloadImageWithURL:imageURl imageFrame:frame userInfo:customParam completed:^(UIImage *image, NSError *error, BOOL finished) {
}];