Extend to iOS

Extend to iOS

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.

Module extend

Weex SDK provides only rendering capabilities, rather than have other capabilities, such as network, picture, and URL redirection. If you want these features, you need to implement it.

For example: If you want to implement an address jumping function, you can achieve a Module following the steps below.

Step to customize a module

  1. Module
    customized must implement WXModuleProtocol
  2. A macro named WX_EXPORT_METHOD must be added, as it is the only way to export methods to JavaScript.
  3. The weexInstance should be synthesized. Each module object is bind to a specific instance.
  4. Module methods will be invoked in UI thread, so do not put time consuming operation there. If you want to execute the whole module methods in other thread, please implement the method - (NSThread *)targetExecuteThread in protocol. In the way, tasks distributed to this module will be executed in targetExecuteThread.
  5. Weex params can be String or Map.
  6. Module supports to return results to Javascript in callback. This callback is type of WXModuleCallback, the params of which can be String or Map.
@implementation WXEventModule
@synthesize weexInstance;
WX_EXPORT_METHOD(@selector(openURL:callback))
- (void)openURL:(NSString *)url callback:(WXModuleCallback)callback
{
NSString *newURL = url;
if ([url hasPrefix:@"//"]) {
newURL = [NSString stringWithFormat:@"http:%@", url];
} else if (![url hasPrefix:@"http"]) {
newURL = [NSURL URLWithString:url relativeToURL:weexInstance.scriptURL].absoluteString;
}
UIViewController *controller = [[WXDemoViewController alloc] init];
((WXDemoViewController *)controller).url = [NSURL URLWithString:newURL];
[[weexInstance.viewController navigationController] pushViewController:controller animated:YES];
callback(@{@"result":@"success"});
}
@end

export synchronous methods v0.10+

If you want to export synchronous methods which could make Javascript receive return values from natvie, you can use WX_EXPORT_METHOD_SYNC macro.

native code:

@implementation WXEventModule
WX_EXPORT_METHOD_SYNC(@selector(getString))
- (NSString *)getString
{
return @"testString";
}
@end

js code:

const eventModule = weex.requireModule('event')
const returnString = syncTest.getString() // return "testString"

You can alse return number/array/dictionary except string.

notice: the exported synchronous native method can only be called on JS thread. Do not do heavy work which will block js execution.

notice: Vue 2.0 has not supported this feature yet. It will be supported in version 0.12 at the soonest.

Register the module

You can register the customized module by calling the method registerModule:withClass in WXSDKEngine.

WXSDKEngine.h
/**
* @abstract Registers a module for a given name
* @param name The module name to register
* @param clazz The module class to register
**/
+ (void)registerModule:(NSString *)name withClass:(Class)clazz;
[WXSDKEngine registerModule:@"event" withClass:[WXEventModule class]];

Handler extend

Weex SDK doesn’t have capabilitis, such as image download 、navigator operation,please implement these protocols by yourself.

WXImgLoaderProtocol


Weex SDK has no image download capability, you need to implement WXImgLoaderProtocol. Refer to the following examples.

WXImageLoaderProtocol.h
@protocol WXImgLoaderProtocol <WXModuleProtocol>
/**
* @abstract Creates a image download handler with a given URL
* @param imageUrl The URL of the image to download
* @param imageFrame The frame of the image you want to set
* @param options : The options to be used for this download
* @param completedBlock : A block called once the download is completed.
image : the image which has been download to local.
error : the error which has happened in download.
finished : a Boolean value indicating whether download action has finished.
*/
-(id<WXImageOperationProtocol>)downloadImageWithURL:(NSString *)url imageFrame:(CGRect)imageFrame userInfo:(NSDictionary *)options completed:(void(^)(UIImage *image, NSError *error, BOOL finished))completedBlock;
@end

Implement above protocol as follows.

@implementation WXImgLoaderDefaultImpl
#pragma mark -
#pragma mark WXImgLoaderProtocol
- (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 the handler

You can register the handler which implements the protocol by calling registerHandler:withProtocol in WXSDKEngine.

WXSDKEngine.h
/**
* @abstract Registers a handler for a given handler instance and specific protocol
* @param handler The handler instance to register
* @param protocol The protocol to confirm
*/
+ (void)registerHandler:(id)handler withProtocol:(Protocol *)protocol;
[WXSDKEngine registerHandler:[WXImgLoaderDefaultImpl new] withProtocol:@protocol(WXImgLoaderProtocol)];

Custom Native Components for iOS

Component extend

There are a lot of native components ready to be used in the Weex SDK, but users always have their own use cases. You might have written an awesome native UI widget in your previous work and just want to wrap up it and export to Weex. So we provide a way to enable developers to create their own custom fully-native components.

This guide will use the implementation of existing component image to show you how to build a native component. It will also assume that you are familiar with iOS programming.

Registration

Defining a custom native component is simple. Just call [WXSDKEngine registerComponent:withClass:] with the component’s tag name as first argument.

[WXSDKEngine registerComponent:@"image" withClass:[WXImageComponent class]];

Then you can create a WXImageComponent class to represent the implementation of image component.

Now you can use <image> wherever you want in the template.

<image></image>

Adding Properties

The next thing we can do is to extend some native properties to make the component more powerful. As an image, let’s say we should have a src attribute as image’s remote source and a resize attribute as image’s resize mode(contain/cover/stretch).

@interface WXImageComponent ()
@property (nonatomic, strong) NSString *imageSrc;
@property (nonatomic, assign) UIViewContentMode resizeMode;
@end

All of the styles, attributes and events will be passed to the component’s initialization method, so here you can store the properties which you are interested in.

@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"]];
_resizeMode = [WXConvert UIViewContentMode:attributes[@"resize"]];
}
return self;
}
@end

The properties getted in the attributes are of id type, so we have to convert them to the type we want using a conversion function. Basic conversion functions can be found in the WXConvert file, or you can just add your own conversion function.

Hooking Render Life Cycle

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.

As in the image component example, if we need to use our own image view, we can override the loadView method.

- (UIView *)loadView
{
return [[WXImageView alloc] init];
}

Now Weex will use WXImageView to render the image component.

As an image component, we will need to fetch the remote image and set it to the image view. This can be done in viewDidLoad method when the view is created and loaded. viewDidLoad is also the best time to perform additional initialization for your view, such as content mode changing.

- (void)viewDidLoad
{
UIImageView *imageView = (UIImageView *)self.view;
imageView.contentMode = _resizeMode;
imageView.userInteractionEnabled = YES;
imageView.clipsToBounds = YES;
imageView.exclusiveTouch = YES;
// Do your image fetching and updating logic
}

If image’s remote source can be changed, you can also hook the updateAttributes: method to perform your attributes changing logic. Component’s view always has been loaded while updateAttributes: or updateStyles: is called.

- (void)updateAttributes:(NSDictionary *)attributes
{
if (attributes[@"src"]) {
_imageSrc = [WXConvert NSString:attributes[@"src"]];
// Do your image updating logic
}
if (attributes[@"resize"]) {
_resizeMode = [WXConvert UIViewContentMode:attributes[@"resize"]];
self.view.contentMode = _resizeMode;
}
}

Maybe there is even more life cycle hooks you might need to consider, such as layoutDidFinish while layout computing is finished. If you want to go deeper, check out the WXComponent.h file in the source code.

Now you can use <image> and its attributes wherever you want in the template.

<image style="your-custom-style" src="image-remote-source" resize="contain/cover/stretch"></image>

Component Method

from WeexSDK 0.9.5, you can define your component method by macro WX_EXPORT_METHOD
for example:

@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

after your registration for your own custom component, now you can call it in your js file.

<template>
<mycomponent id='mycomponent'></mycomponent>
</template>
<script>
module.exports = {
created: function() {
this.$el('mycomponent').focus();
}
}
</script>