Weex系列(5) —— 封装原生组件和模块

DaLei 2019-06-30

目录

  • Weex系列(序) —— 总要知道原生的一点东东(iOS)
  • Weex系列(序) —— 总要知道原生的一点东东(Android)
  • Weex系列(1) —— Hello World项目
  • Weex系列(2) —— 页面跳转和通信
  • Weex系列(3) —— 单页面还是多页面
  • Weex系列(4) —— 老生常谈的三端统一
  • Weex系列(5) —— 封装原生组件和模块
  • [Weex系列(6) —— css相关小结]
  • [Weex系列(7) —— web组件和webview]
  • [Weex系列(8) —— 是时候简析一下流程原理了]
  • [Weex系列(9) —— 踩坑填坑的集锦]
  • [Weex系列(10) —— 先这么多吧想到再写。。。]

哇,2019年了,时间总是那么快,快过新年了,忙点了也懒点了,还有点想家了,先祝大家新年快乐吧。

这一章官网上有介绍,但还是单独拎出来讲一讲,因为后期这块用的还是挺多的。

官网的所有组件和模块的截图:

Weex系列(5) —— 封装原生组件和模块

在官网 扩展版块,是可以找到封装的方法步骤的。

自定义模块

iOS:

第一步:

新建 myModule.h

#import <Foundation/Foundation.h>
#import <WeexSDK/WXModuleProtocol.h>

@interface myModule : NSObject<WXModuleProtocol>

@end

新建 myModule.m

#import "myModule.h"

@implementation myModule

WX_EXPORT_METHOD(@selector(log:))

- (void)log:(NSString *)inputParam
{
    NSLog(@"%@",inputParam);
}

@end

第二步:

AppDelegate.m里面注册module

[WXSDKEngine registerModule:@"myModule" withClass:[myModule class]];

Android:

第一步:

新建myModule.java

public class MyModule extends WXModule {

  //run JS thread
  @JSMethod (uiThread = false)
  public void log(String inputParam) {
    Log.d("自定义模块:", inputParam);
  }

}

第二步:

WXApplication.java里面注册module

WXSDKEngine.registerModule("MyModule", MyModule.class);

最后:

在上层vue里面,我们可以require我们自己封装的module,就能够调用原生的log方法,分别在xcode和Android Studio的控制台,看到hello weex的消息了。

这里需要强调一点的是:iOS和Android的module的名字方法要一致,这样在vue里面才能统一的。

weex.requireModule("myModule").log("hello weex")

自定义组件

组件封装起来比模块是麻烦许多的,一开始也是摸不着头脑,后来就找到weexsdk里面封装的组件,依样画葫芦的开始了。

Weex系列(5) —— 封装原生组件和模块

iOS:

第一步:

新建myComponent.h

#import "WXComponent.h"

@interface myComponent : WXComponent<UIWebViewDelegate>

- (void)notifyWebview:(NSDictionary *) data;

- (void)reload;

- (void)goBack;

- (void)goForward;

@end

新建myComponent.m

#import "myComponent.h"
#import <WeexSDK/WXComponent.h>
#import <WeexSDK/WXComponentManager.h>
#import "WXUtility.h"
#import "WXURLRewriteProtocol.h"
#import "WXSDKEngine.h"

#import <JavaScriptCore/JavaScriptCore.h>

@interface WXWebView : UIWebView

@end

@implementation WXWebView

- (void)dealloc
{
    if (self) {
//        self.delegate = nil;
    }
}

@end

@interface myComponent ()

@property (nonatomic, strong) JSContext *jsContext;

@property (nonatomic, strong) WXWebView *webview;

@property (nonatomic, strong) NSString *url;

@property (nonatomic, assign) BOOL startLoadEvent;

@property (nonatomic, assign) BOOL finishLoadEvent;

@property (nonatomic, assign) BOOL failLoadEvent;

@property (nonatomic, assign) BOOL notifyEvent;

@end

@implementation myComponent

WX_EXPORT_METHOD(@selector(goBack))
WX_EXPORT_METHOD(@selector(reload))
WX_EXPORT_METHOD(@selector(goForward))

- (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.url = attributes[@"src"];
    }
    return self;
}

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

- (void)viewDidLoad
{
    _webview = (WXWebView *)self.view;
    _webview.delegate = self;
    _webview.allowsInlineMediaPlayback = YES;
    _webview.scalesPageToFit = YES;
    [_webview setBackgroundColor:[UIColor clearColor]];
    _webview.opaque = NO;
    _jsContext = [_webview valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
    __weak typeof(self) weakSelf = self;
    _jsContext[@"$notifyWeex"] = ^(JSValue *data) {
        if (weakSelf.notifyEvent) {
            [weakSelf fireEvent:@"notify" params:[data toDictionary]];
        }
    };
    
    if (_url) {
        [self loadURL:_url];
    }
}

- (void)updateAttributes:(NSDictionary *)attributes
{
    if (attributes[@"src"]) {
        self.url = attributes[@"src"];
    }
}

- (void)addEvent:(NSString *)eventName
{
    if ([eventName isEqualToString:@"pagestart"]) {
        _startLoadEvent = YES;
    }
    else if ([eventName isEqualToString:@"pagefinish"]) {
        _finishLoadEvent = YES;
    }
    else if ([eventName isEqualToString:@"error"]) {
        _failLoadEvent = YES;
    }
}

- (void)setUrl:(NSString *)url
{
    NSString* newURL = [url copy];
    WX_REWRITE_URL(url, WXResourceTypeLink, self.weexInstance)
    if (!newURL) {
        return;
    }
    
    if (![newURL isEqualToString:_url]) {
        _url = newURL;
        if (_url) {
            [self loadURL:_url];
        }
    }
}

- (void)loadURL:(NSString *)url
{
    if (self.webview) {
        NSURLRequest *request =[NSURLRequest requestWithURL:[NSURL URLWithString:url]];
        [self.webview loadRequest:request];
    }
}

- (void)reload
{
    [self.webview reload];
}

- (void)goBack
{
    if ([self.webview canGoBack]) {
        [self.webview goBack];
    }
}

- (void)goForward
{
    if ([self.webview canGoForward]) {
        [self.webview goForward];
    }
}

- (void)notifyWebview:(NSDictionary *) data
{
    NSString *json = [WXUtility JSONString:data];
    NSString *code = [NSString stringWithFormat:@"(function(){var evt=null;var data=%@;if(typeof CustomEvent==='function'){evt=new CustomEvent('notify',{detail:data})}else{evt=document.createEvent('CustomEvent');evt.initCustomEvent('notify',true,true,data)}document.dispatchEvent(evt)}())", json];
    [_jsContext evaluateScript:code];
}

#pragma mark Webview Delegate

- (NSMutableDictionary<NSString *, id> *)baseInfo
{
    NSMutableDictionary<NSString *, id> *info = [NSMutableDictionary new];
    [info setObject:self.webview.request.URL.absoluteString ?: @"" forKey:@"url"];
    [info setObject:[self.webview stringByEvaluatingJavaScriptFromString:@"document.title"] ?: @"" forKey:@"title"];
    [info setObject:@(self.webview.canGoBack) forKey:@"canGoBack"];
    [info setObject:@(self.webview.canGoForward) forKey:@"canGoForward"];
    return info;
}

- (void)webViewDidStartLoad:(UIWebView *)webView
{
    
}

- (void)webViewDidFinishLoad:(UIWebView *)webView
{
    if (_finishLoadEvent) {
        NSDictionary *data = [self baseInfo];
        [self fireEvent:@"pagefinish" params:data domChanges:@{@"attrs": @{@"src":self.webview.request.URL.absoluteString}}];
    }
}

- (void)webView:(UIWebView *)webView didFailLoadWithError:(NSError *)error
{
    if (_failLoadEvent) {
        NSMutableDictionary *data = [self baseInfo];
        [data setObject:[error localizedDescription] forKey:@"errorMsg"];
        [data setObject:[NSString stringWithFormat:@"%ld", (long)error.code] forKey:@"errorCode"];
        
        NSString * urlString = error.userInfo[NSURLErrorFailingURLStringErrorKey];
        if (urlString) {
            // webview.request may not be the real error URL, must get from error.userInfo
            [data setObject:urlString forKey:@"url"];
            if (![urlString hasPrefix:@"http"]) {
                return;
            }
        }
        [self fireEvent:@"error" params:data];
    }
}

- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType
{
    if (_startLoadEvent) {
        NSMutableDictionary<NSString *, id> *data = [NSMutableDictionary new];
        [data setObject:request.URL.absoluteString ?:@"" forKey:@"url"];
        [self fireEvent:@"pagestart" params:data];
    }
    return YES;
}

@end

第二步:

AppDelegate.m里面注册component

[WXSDKEngine registerComponent:@"myComponent" withClass:[myComponent class]];

这里需要说明:上面基本上是照着weexsdk里面的webview组件改的,而且就是改了一下名字,方法什么的大家就可以自由发挥了。

Android:

第一步:

新建myComponent.java

/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 * 
 *   http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */
package com.taobao.weex.ui.component;

import android.content.Context;
import android.net.Uri;
import android.support.annotation.NonNull;
import android.text.TextUtils;
import android.view.View;

import com.taobao.weex.WXSDKInstance;
import com.taobao.weex.annotation.Component;
import com.taobao.weex.adapter.URIAdapter;
import com.taobao.weex.common.Constants;
import com.taobao.weex.dom.WXDomObject;
import com.taobao.weex.ui.view.IWebView;
import com.taobao.weex.ui.view.WXWebView;
import com.taobao.weex.utils.WXUtils;

import java.util.HashMap;
import java.util.Map;
@Component(lazyload = false)

public class myComponent extends WXComponent {

    public static final String GO_BACK = "goBack";
    public static final String GO_FORWARD = "goForward";
    public static final String RELOAD = "reload";
    protected IWebView mWebView;

    @Deprecated
    public myComponent(WXSDKInstance instance, WXDomObject dom, WXVContainer parent, String instanceId, boolean isLazy) {
        this(instance,dom,parent,isLazy);
    }

    public myComponent(WXSDKInstance instance, WXDomObject dom, WXVContainer parent, boolean isLazy) {
        super(instance, dom, parent, isLazy);
        createWebView();
    }

    protected void  createWebView(){
        mWebView = new WXWebView(getContext());
    }

    @Override
    protected View initComponentHostView(@NonNull Context context) {
        mWebView.setOnErrorListener(new IWebView.OnErrorListener() {
            @Override
            public void onError(String type, Object message) {
                fireEvent(type, message);
            }
        });
        mWebView.setOnPageListener(new IWebView.OnPageListener() {
            @Override
            public void onReceivedTitle(String title) {
                if (getDomObject().getEvents().contains(Constants.Event.RECEIVEDTITLE)) {
                    Map<String, Object> params = new HashMap<>();
                    params.put("title", title);
                    fireEvent(Constants.Event.RECEIVEDTITLE, params);
                }
            }

            @Override
            public void onPageStart(String url) {
                if ( getDomObject().getEvents().contains(Constants.Event.PAGESTART)) {
                    Map<String, Object> params = new HashMap<>();
                    params.put("url", url);
                    fireEvent(Constants.Event.PAGESTART, params);
                }
            }

            @Override
            public void onPageFinish(String url, boolean canGoBack, boolean canGoForward) {
                if ( getDomObject().getEvents().contains(Constants.Event.PAGEFINISH)) {
                    Map<String, Object> params = new HashMap<>();
                    params.put("url", url);
                    params.put("canGoBack", canGoBack);
                    params.put("canGoForward", canGoForward);
                    fireEvent(Constants.Event.PAGEFINISH, params);
                }
            }
        });
        return mWebView.getView();
    }

    @Override
    public void destroy() {
        super.destroy();
        getWebView().destroy();
    }

    @Override
    protected boolean setProperty(String key, Object param) {
        switch (key) {
            case Constants.Name.SHOW_LOADING:
                Boolean result = WXUtils.getBoolean(param,null);
                if (result != null)
                    setShowLoading(result);
                return true;
            case Constants.Name.SRC:
                String src = WXUtils.getString(param,null);
                if (src != null)
                    setUrl(src);
                return true;
        }
        return super.setProperty(key,param);
    }

    @WXComponentProp(name = Constants.Name.SHOW_LOADING)
    public void setShowLoading(boolean showLoading) {
        getWebView().setShowLoading(showLoading);
    }

    @WXComponentProp(name = Constants.Name.SRC)
    public void setUrl(String url) {
        if (TextUtils.isEmpty(url) || getHostView() == null) {
            return;
        }
        if (!TextUtils.isEmpty(url)) {
            loadUrl(getInstance().rewriteUri(Uri.parse(url), URIAdapter.WEB).toString());
        }
    }

    public void setAction(String action) {
        if (!TextUtils.isEmpty(action)) {
            if (action.equals(GO_BACK)) {
                goBack();
            } else if (action.equals(GO_FORWARD)) {
                goForward();
            } else if (action.equals(RELOAD)) {
                reload();
            }
        }
    }

    private void fireEvent(String type, Object message) {
        if (getDomObject().getEvents().contains(Constants.Event.ERROR)) {
            Map<String, Object> params = new HashMap<>();
            params.put("type", type);
            params.put("errorMsg", message);
            fireEvent(Constants.Event.ERROR, params);
        }
    }

    private void loadUrl(String url) {
        getWebView().loadUrl(url);
    }

    private void reload() {
        getWebView().reload();
    }

    private void goForward() {
        getWebView().goForward();
    }

    private void goBack() {
        getWebView().goBack();
    }

    private IWebView getWebView() {
        return mWebView;
    }

}

第二步:

WXApplication.java里面注册component

WXSDKEngine.registerComponent("myComponent", myComponent.class);

最后:

在上层vue里面,我们就可以直接使用封装好的组件。

这里需要强调一点的是:iOS和Android的组件名字一定要一致,这样在vue里面才能统一的。

<myComponent src="url" class="webview" :style="{ height : screenHeight+'px' }"></myComponent>

小结

1、从上面可以看出不管是组件还是模块,都是要iOS和Android各封装一套的,而且名字还要一致,如果兼容web端,还要做web的扩展,这样才能三端统一的。
2、封装组件的版块,我把weex sdk里面的web组件代码拿出来了,也是为了后面webview章节做铺垫吧。
3、建议大家可以多看看weex sdk的源码,(这里请忘掉我只是一个前端,我干嘛还要学习oc、java的这些想法吧)其实也还好,也可能是目前我们的项目没有太复杂,封装的还不是很多,也还算简单,谷歌上一搜基本都能解决吧。

最后祝大家新的一年,少点bug,多点money,越来越好吧。

如果喜欢就请点个赞收藏一下啦~~~

相关推荐