DCloud_iOS_XHY
DCloud_iOS_XHY
  • 发布:2020-02-14 21:43
  • 更新:2020-08-06 18:04
  • 阅读:5371

iOS 扩展 uni小程序SDK 原生能力

分类:uni-app

本文档已过期,请查看 新文档

概述

本文档主要介绍如何扩展 uni小程序SDK 原生能力。

什么是扩展原生能力?

扩展原生能力指的是将您原生开发的功能通过一定规范暴露给 uni小程序环境,然后即可在 uni小程序应用中调用您的原生功能。

扩展方式

uni 原生端是基于 WeexSDK 来实现扩展原生能力,扩展原生能力有两种方式:一种是不需要参与页面布局,只需要通过 API 调用原生功能,比如:获取当前定位信息、数据请求等功能,这种情况可通过扩展module的方式来实现;另一种是需要参与页面布局,比如:map、image,这种情况需要通过扩展component即组件的方法来实现;

开发前准备

  • iOS开发环境,请使用 Xcode 11.0 及以上版本;
  • 已有集成 iOS uni小程序SDK 原生工程;集成文档
  • 安装 uni小程序开发工具 HBuilderX (注:版本需与您使用的 uni小程序SDK 版本保持一致)

扩展 module

下面以TestModule为例,源码请查看 uni小程序SDK 包中的示例 demo 工程;

1. 新建TestModule类,继承 NSObject,让该类遵循 WXModuleProtocol 的协议。

#import <Foundation/Foundation.h>  
// 引入 WeexSDK.h 头文件  
#import "WeexSDK.h"  

@interface TestModule : NSObject <WXModuleProtocol>  

@end  

2. 添加实现方法

异步方法实现

/// 异步方法(注:异步方法会在主线程(UI线程)执行)  
/// @param options js 端调用方法时传递的参数  
/// @param callback 回调方法,回传参数给 js 端  
- (void)testAsyncFunc:(NSDictionary *)options callback:(WXModuleKeepAliveCallback)callback {  
    // options 为 js 端调用此方法时传递的参数  
    NSLog(@"%@",options);  

    // 可以在该方法中实现原生能力,然后通过 callback 回调到 js  

    // 回调方法,传递参数给 js 端 注:只支持返回 String 或 NSDictionary (map) 类型  
    if (callback) {  
        // 第一个参数为回传给js端的数据,第二个参数为标识,表示该回调方法是否支持多次调用,如果原生端需要多次回调js端则第二个参数传 YES;  
        callback(@"success",NO);  
    }  
}

通过宏 WX_EXPORT_METHOD 将异步方法暴露给 js 端,只有通过WX_EXPORT_METHOD暴露的原生方法才能被 js 端识别到

// 通过宏 WX_EXPORT_METHOD 将异步方法暴露给 js 端  
WX_EXPORT_METHOD(@selector(testAsyncFunc:callback:))

同步方法实现

/// 同步方法(注:同步方法会在 js 线程执行)  
/// @param options js 端调用方法时传递的参数  
- (NSString *)testSyncFunc:(NSDictionary *)options {  
    // options 为 js 端调用此方法时传递的参数  
    NSLog(@"%@",options);  

    /*  
     可以在该方法中实现原生功能,然后直接通过 return 返回参数给 js  
     */  

    // 同步返回参数给 js 端 注:只支持返回 String 或 NSDictionary (map) 类型  
    return @"success";  
}

通过宏 WX_EXPORT_METHOD_SYNC 将同步方法暴露给 js 端

// 通过宏 WX_EXPORT_METHOD_SYNC 将同步方法暴露给 js 端  
WX_EXPORT_METHOD_SYNC(@selector(testSyncFunc:))

3. 注册 module

在初始化 DCUniMPSDKEngine 方法后,进行 module 的注册

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {  
    ...  
    // 初始化引擎  
    [DCUniMPSDKEngine initSDKEnvironmentWihtLaunchOptions:options];  

    // 注册 module 注:module的 Name 需要保证唯一, class:为 module 的类名  
    [WXSDKEngine registerModule:@"TestModule" withClass:NSClassFromString(@"TestModule")];  

    return YES;  
}

到此,我们已经完成了一个简单的 module 扩展

4. 在 uni小程序 中调用 module 方法

module 支持在 vue 和 nvue 中使用

<template>  
    <div>  
        <button type="primary" @click="testAsyncFunc">testAsyncFunc</button>  
        <button type="primary" @click="testSyncFunc">testSyncFunc</button>  
    </div>  
</template>  

<script>  
    // 获取 module   
    var testModule = uni.requireNativePlugin("TestModule")  
    export default {  
        methods: {  
            testAsyncFunc() {  
                // 调用异步方法  
                testModule.testAsyncFunc({  
                        'name': 'unimp',  
                        'age': 1  
                    },  
                    (ret) => {  
                        console.log(ret)  
                    })  
            },  
            testSyncFunc() {  
                // 调用同步方法  
                var ret = testModule.testSyncFunc({  
                    'name': 'unimp',  
                    'age': 1  
                })  
            }  
        }  
    }  
</script>  

然后可以导出 uni小程序资源,导入到 App 中查看效果;

扩展组件 component

下面以TestComponent为例,源码请查看 uni小程序SDK 包中的示例 demo 工程;

1. 新建TestComponent类,继承WXComponent类。如果这个类里什么代码也不写,它和默认的的 view 组件能力是一致的

#import "WXComponent.h"  

@interface TestComponent : WXComponent  

@end  

2. 覆写 WXComponent 中的生命周期方法

- loadView 方法

一个组件默认对应于一个原生 view,如果未覆盖loadView提供自定义 view,基类会默认返回一个继承于 UIView 的实例。比如我们要实现一个组件支持地图功能,我们可以返回系统的 MKMapView

- (UIView *)loadView {  
    return [MKMapView new];  
}

- viewDidLoad

对组件 view 需要做一些配置,比如设置 delegate,可以在 viewDidLoad 生命周期做。如果当前 view 没有添加 subview 的话,不要设置 view 的 frame,WeexSDK 会根据 style 进行排版后设置。

- (void)viewDidLoad {  
      ((MKMapView*)self.view).delegate = self;  
}

3. 注册组件

在初始化 DCUniMPSDKEngine 方法后,进行 component 的注册

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {  
    ...  
    // 初始化引擎  
    [DCUniMPSDKEngine initSDKEnvironmentWihtLaunchOptions:options];  

    // 注册 component 注:component 的 Name 需要保证唯一, class:为 component 的类名  
    [WXSDKEngine registerComponent:@"testmap" withClass:NSClassFromString(@"TestMapComponent")];  

    return YES;  
}

4. 在uni小程序代码中使用组件

注意:扩展的 component 只能在 nvue 文件中使用

<template>  
    <div>  
        <testmap style="width:200px;height:200px"></testmap>  
    </div>  
</template>

自定义事件

1. 对于每个组件默认提供了一些事件能力,如点击等。假如想给我们的地图组件提供 mapLoaded 事件。

在uni小程序代码中,通过 @事件名="方法名" 添加事件,如下添加mapLoaded 事件

<template>  
    <div>  
        <testmap style="width:200px;height:200px" @mapLoaded="onMapLoaded"></testmap>  
    </div>  
</template>  

<script>  
export default {  
    methods: {  
        onMapLoaded:function(e) {  
            console.log("map loaded"+JSON.stringify(e))  
        }  
    }  
}  
</script>
2. 原生端实现:覆盖组件生命周期方法,记录事件是否需要处理

我们需要额外添加一个 BOOL 成员 mapLoadedEvent 用来记录该事件是否生效。

/// 前端注册的事件会调用此方法  
/// @param eventName 事件名称  
- (void)addEvent:(NSString *)eventName {  
    if ([eventName isEqualToString:@"mapLoaded"]) {  
        _mapLoadedEvent = YES;  
    }  
}  

/// 对应的移除事件回调方法  
/// @param eventName 事件名称  
- (void)removeEvent:(NSString *)eventName {  
    if ([eventName isEqualToString:@"mapLoaded"]) {  
        _mapLoadedEvent = NO;  
    }  
}
3. 给前端发送事件
- (void)mapViewDidFinishLoadingMap:(MKMapView *)mapView {  
    if (_mapLoadedEvent) {  
        // 想前端发送事件 params:传给前端的数据  
        [self fireEvent:@"mapLoaded" params:@{@"customKey":@"customValue"} domChanges:nil];  
    }  
}

自定义属性

给我们的地图组件添加一个新的属性showTraffic。在前端代码里可以控制组件是否显示路况信息

<template>  
    <div>  
        <testmap style="width:200px;height:200px" showTraffic="true"></testmap>  
    </div>  
</template>
原生端实现

1. 覆盖组件初始化方法 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;  
}

2. 在生命期事件中记得将属性值同步给地图控件

- (void)viewDidLoad {  
  ((MKMapView*)self.view).showsTraffic = _showsTraffic;  
}

3. 当属性更新时,同步给地图控件

/// 前端更新属性回调方法  
/// @param attributes 更新的属性  
- (void)updateAttributes:(NSDictionary *)attributes {  
    // 解析属性  
    if (attributes[@"showsTraffic"]) {  
        _showsTraffic = [WXConvert BOOL: attributes[@"showsTraffic"]];  
        ((MKMapView*)self.view).showsTraffic = _showsTraffic;  
    }  
}

更多的组件生命期方法

组件是由框架管理的,比如创建、布局、渲染、销毁。组件的生命周期方法都是可以重写的,你可以在这些生命周期中去做自己的事情。

方法 描述
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移除的时候调用.

给组件添加方法

1.原生端实现

在组件代码中使用宏 WX_EXPORT_METHOD 暴露原生方法供前端调用

@implementation TestMapComponent  

// 通过 WX_EXPORT_METHOD 将方法暴露给前端  
WX_EXPORT_METHOD(@selector(focus:))  

- (void)focus:(NSDictionary *)options {  
    // options 为前端传递的参数  
    NSLog(@"%@",options);  
}  
@end

2.在uni小程序代码 中调用 focus: 方法。

<template>  
  <testmap ref='mycomponent'></testmap>  
</template>  
<script>  
  module.exports = {  
    created: function() {  
      this.$refs.mycomponent.focus("Hello");  
    }  
  }  
</script>

至此,您已完成组件扩展;

以上示例中的完整uni小程序代码

<template>  
    <div>  
        <button type="primary" @click="testAsyncFunc">testAsyncFunc</button>  
        <button type="primary" @click="testSyncFunc">testSyncFunc</button>  
        <testmap ref='mycomponent' style="width:200px;height:200px" showTraffic="true" @mapLoaded="onMapLoaded"></testmap>  
    </div>  
</template>  

<script>  
    // 获取 module   
    var testModule = uni.requireNativePlugin("TestModule")  
    export default {  
        onLoad() {  
            // 调用组件方法  
            this.$nextTick(()=>{  
                this.$refs.mycomponent.focus("Hello")  
            })  
        },  
        methods: {  
            testAsyncFunc() {  
                // 调用异步方法  
                testModule.testAsyncFunc({  
                        'name': 'unimp',  
                        'age': 1  
                    },  
                    (ret) => {  
                        console.log(ret)  
                    })  
            },  
            testSyncFunc() {  
                // 调用同步方法  
                var ret = testModule.testSyncFunc({  
                    'name': 'unimp',  
                    'age': 1  
                })  
            },  
            onMapLoaded(e) {  
                console.log("map loaded" + JSON.stringify(e))  
            }  
        }  
    }  
</script>
0 关注 分享

要回复文章请先登录注册

npzlxph

npzlxph

回复 DCloud_iOS_XHY :
你好,怎么获取uni小程序底层的VC啊
2020-08-06 18:04
npzlxph

npzlxph

回复 4***@qq.com :
你好,请问能实现吗
2020-08-06 17:53
9***@qq.com

9***@qq.com

刚采完坑,https://liujingyuan.top/2020/06/12/Dcloud%E6%89%A9%E5%B1%95%E5%8E%9F%E7%94%9F%E7%BB%84%E4%BB%B6/
2020-06-12 18:55
DCloud_iOS_XHY

DCloud_iOS_XHY (作者)

回复 8***@qq.com :
njs只适合调用简单的api,并且如果出现问题无法排查 请按文档方式扩展原生能力吧
2020-03-11 12:16
8***@qq.com

8***@qq.com

可以将使用native.js写的代码放到主线程跑吗?有什么办法?
2020-03-10 21:53
DCloud_iOS_XHY

DCloud_iOS_XHY (作者)

回复 4***@qq.com :
可以,uni小程序底层就是一个 ViewController,你可以通过扩展module来实现,获取到这个 VC 然后跟原生跳转页面一样的
2020-02-25 15:45
4***@qq.com

4***@qq.com

问下uni小程序打开原生页面能实现吗?
2020-02-25 14:21