首页 uniapp正文

uniapp苹果iOS原生插件开发实战详解教程

技术支持:点击这里直接沟通

开发环境

  • iOS开发环境,Xcode 12.1 及以上版本

  • 下载开发插件需要的 SDK包 并解压

  • 安装 uni-app 开发工具 HBuilderX

SDK 包结构说明

|--iOSSDK         |-- HBuilder-Hello                // uni-app 离线打包工程     |-- HBuilder-uniPluginDemo        // uni-app 插件开发主工程 (本文档需要使用的工程)     |-- SDK                            // 依赖库及依赖资源文件     |-- Feature-iOS.xls                // 功能模块与依赖库对应关系说明表格     |-- readme.txt                    // 目录说明Copy to clipboardErrorCopied

SDK 目录中的 HBuilder-uniPluginDemo为 uni原生插件开发主工程,该工程已经将各项配置都配置齐全,开发uni原生插件需要依赖此工程,本文档的插件示例工程DCTestUniPlugin也在目录中,另外插件市场的 原生增强提示框插件 对应的原生插件工程DCRichAlert也放到了此目录中提供给开发者作为参考,其他工程及文件,开发uni原生插件不需要关心,如果想了解更多可以参考 App离线打包 相关文档

创建插件工程

打开 Xcode,创建一个新的工程,template 选择 Framework 或 Static Library(示例工程选择的是 Framework),然后点击 Next

在 Product Name 中输入插件工程名称(建议使用一个性化的前缀,避免与其他人的插件包名冲突),其他项不需要修改保持工程默认填充的即可,然后点击Next

然后选择工程存放路径,建议直接存放在 iOSSDK目录中的 HBuilder-uniPluginDemo 插件开发主工程目录下,如下图所示,然后点击 Create

强烈建议将插件工程存放在 HBuilder-uniPluginDemo 插件开发主工程目录中,因为插件工程需要根据插件开发主工程相对路径引用一些文件,之后您升级SDK的时候只需要将您的插件工程 Copy 到新的 SDK 的相同位置下即可

可以删除工程自动创建的 .h 文件,这个文件用不到

然后选中工程名,在TARGETS->Build Settings中,将 Mach-O Type 设置为 Static Library 如下图所示

然后将插件工程关闭,接下来需要将插件工程导入到插件开发主工程中

导入插件工程

打开 iOSSDK/HBuilder-uniPluginDemo工程目录,双击目录中的HBuilder-uniPlugin.xcodeproj 文件运行插件开发主工程

在 Xcode 项目左侧目录选中主工程名,然后点击右键选择Add Files to “HBuilder-uniPlugin” ...

然后选择您刚刚创建的插件工程路径中,选中插件工程文件,勾选 Create folder references 和 Add to targets 两项,然后点击Add

这时在 Xcode 左侧目录中可以看到插件工程已经添加到了主工程中,如下图所示

工程配置

然后在 Xcode 项目左侧目录选中主工程名,在TARGETS->Build Phases->Dependencies中点击+

在弹窗中选中插件工程,如图所示,然后点击Add,将插件工程添加到Dependencies

然后在Link Binary With Libraries中点击+,同样在弹窗中选中插件工程,点击Add

此时可以看到 Dependencies 和 Link Binary With Libraries 都添加了插件工程,如下图所示

接下来需要在插件工程的Header Search Paths中添加开发插件所需的头文件引用,头文件存放在主工程的HBuilder-Hello/inc中,添加方法如下图所示,在 Xcode 项目左侧目录选中插件工程名,找到TARGETS->Build Settings->Header Search Paths双击右侧区域打开添加窗口,然后将inc目录拖入会自动填充相对路径,然后将模式改成recursive

代码实现

写法说明

HBuilderX 3.0.5 版本开始, 推荐使用新的插件写法。

如果插件按照新写法,出现云打包报错, 请先确认HBuilderX版本在3.0.5或其以上。

插件扩展方式

原生插件是基于 DCUniPlugin 规范来实现,扩展原生功能有两种方式:

  • module:不需要参与页面布局,只需要通过 API 调用原生功能,比如:获取当前定位信息、数据请求等功能,通过扩展module的方式来实现;

  • component:需要参与页面布局,比如:mapimage等需要显示UI的功能,通过扩展component即组件的方法来实现;

您需要根据实际的情况选择扩展方式,当然插件中可以同时存在 module 和 component,也可以是多个 module 和 多个 component

特别注意 如果需要扩展自定义的 module 或者 component ,一定注意不要将 oc 的 runtime 暴露给 JS ,不要将一些诸如 dlopen(), dlsym(), respondsToSelector:performSelector:method_exchangeImplementations() 的动态和不可控的方法暴露给JS,也不要将系统的私有API暴露给JS。否则将可能面临苹果上架审核问题。

扩展 module

TestModule为例,源码请查看 iOSSDK/HBuilder-uniPluginDemo/DCTestUniPlugin 插件工程;

新建TestModule类,继承 DCUniModule,引入 DCUniModule.h 头文件。

TestModule.h 文件

#import <Foundation/Foundation.h> // 引入 DCUniModule.h 头文件 #import "DCUniModule.h" @interface TestModule : DCUniModule @endCopy to clipboardErrorCopied

TestModule.h 文件截图:

然后在 TestModule.m 文件中添加实现方法

异步方法实现

/// 异步方法(注:异步方法会在主线程(UI线程)执行) /// @param options js 端调用方法时传递的参数 /// @param callback 回调方法,回传参数给 js 端 - (void)testAsyncFunc:(NSDictionary *)options callback:(UniModuleKeepAliveCallback)callback {      // options 为 js 端调用此方法时传递的参数 NSLog(@"%@",options); // 可以在该方法中实现原生能力,然后通过 callback 回调到 js      // 回调方法,传递参数给 js 端 注:只支持返回 String 或 NSDictionary (map) 类型    if (callback) {        // 第一个参数为回传给js端的数据,第二个参数为标识,表示该回调方法是否支持多次调用,如果原生端需要多次回调js端则第二个参数传 YES;         callback(@"success",NO);     } }Copy to clipboardErrorCopied

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

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

同步方法实现

/// 同步方法(注:同步方法会在 js 线程执行) /// @param options js 端调用方法时传递的参数 - (NSString *)testSyncFunc:(NSDictionary *)options {     // options 为 js 端调用此方法时传递的参数     NSLog(@"%@",options);     /*      可以在该方法中实现原生功能,然后直接通过 return 返回参数给 js      */     // 同步返回参数给 js 端 注:只支持返回 String 或 NSDictionary (map) 类型     return @"success"; }Copy to clipboardErrorCopied

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

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

TestModule.m 文件截图:

Hook系统事件

如果需要在 App 启动时初始化或者需要获取系统的一些事件, 需要新建一个XXXXProxy类(注意命名加前缀防止冲突),继承 NSObject 遵守UniPluginProtocol协议

-(void)onCreateUniPlugin; - (BOOL)application:(UIApplication *_Nullable)application didFinishLaunchingWithOptions:(NSDictionary *_Nullable)launchOptions; - (void)application:(UIApplication *_Nullable)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *_Nullable)deviceToken; - (void)application:(UIApplication *_Nullable)application didFailToRegisterForRemoteNotificationsWithError:(NSError *_Nullable)err; - (void)application:(UIApplication *_Nullable)application didReceiveRemoteNotification:(NSDictionary *_Nullable)userInfo; - (void)application:(UIApplication *_Nullable)application didReceiveLocalNotification:(UILocalNotification *_Nullable)notification; - (BOOL)application:(UIApplication *_Nullable)application handleOpenURL:(NSURL *_Nullable)url; - (BOOL)application:(UIApplication *_Nullable)app openURL:(NSURL *_Nonnull)url options:(NSDictionary<UIApplicationOpenURLOptionsKey, id> *_Nullable)options NS_AVAILABLE_IOS(9_0); - (void)applicationWillResignActive:(UIApplication *)application; - (void)applicationDidBecomeActive:(UIApplication *)application; - (void)applicationDidEnterBackground:(UIApplication *)application; - (void)applicationWillEnterForeground:(UIApplication *)application; - (BOOL)application:(UIApplication *_Nullable)application continueUserActivity:(NSUserActivity *_Nullable)userActivity restorationHandler:(void(^_Nullable)(NSArray * __nullable restorableObjects))restorationHandler API_AVAILABLE(ios(8.0));Copy to clipboardErrorCopied

具体示例请查看 离线SDK包 中 HBuilder-uniPluginDemo/DCTestUniPlugin/DCTestUniPlugin/TestPluginProxy.m

Module 进阶

关于 Module 和 Module 方法的执行特性(同步、异步;执行线程),需要了解:

uniExecuteThread

Module 方法默认会在 UI 线程(iOS 主线程)中被调用,建议不要在这做太多耗时的任务。

如果你的任务不需要在 UI 线程执行或需要在特定线程执行,需要实现 DCUniModule 中的 uniExecuteThread 的属性,并返回你希望方法执行所在的线程。

Module 自定义队列和线程

在 DCUniModule 中可以通过 uniExecuteQueue来实现自定义 queue,通过uniExecuteThread 来实现自定义的 thread

特别注意 如果同时指定了 uniExecuteQueue 和 uniExecuteThread, 只会执行 uniExecuteQueueuniExecuteThread将会被忽略。

如果只实现了 uniExecuteThread, 代码中注意 要线程保活

参考示例代码

    -(NSThread*)uniExecuteThread {     if ( nil == _uniExecuteThread) {         _uniExecuteThread = [[NSThread alloc] initWithTarget:self selector:@selector(uniNewThread) object:nil];         [_uniExecuteThread setName:@"TestUniModule"];         [_uniExecuteThread start];     }     return _uniExecuteThread; } -(void)uniNewThread {     @autoreleasepool {         NSRunLoop *runLoop = [NSRunLoop currentRunLoop];         [runLoop addPort:[NSMachPort port] forMode:NSRunLoopCommonModes];         [runLoop run];     } }Copy to clipboardErrorCopied

配置插件信息

选中工程中的HBuilder-uniPlugin-Info.plist文件右键->Open As->Source Code找到dcloud_uniplugins节点,copy下面的内容添加到dcloud_uniplugins节点下,按您插件的实际信息填写对应的项

<dict>     <key>hooksClass</key>     <string>填写 hooksClass 类名 </string>     <key>plugins</key>     <array>         <dict>             <key>class</key>             <string>填写 module 或 component 的类名</string>             <key>name</key>             <string>填写暴露给js端对应的 module 或 component 名称</string>             <key>type</key>             <string>填写 module 或 component</string>         </dict>     </array> </dict>Copy to clipboardErrorCopied

配置说明

  • hooksClass:App系统方法钩子类,值是类名,是给有些插件需要在 app 启动时做初始化或者获取系统事件用的,如果没有可以不填为空

  • class:module 或 component 对应的原生类名(示例中为 TestModule

  • name:暴露给js端使用的 module 或 component 对应的名称(注意:module 的 name 必须以插件id为前缀或和插件id相同,示例为DCTestUniPlugin-TestModule其中 DCTestUniPlugin 为插件的id,需要保证唯一性,避免与其他插件冲突,component 的name 没有强制要求,但是也要保证唯一比如 dc-map

  • type:module 或 component (示例为module

配置完如下图所示(必须严格按照格式配置)

到此,我们已经完成了一个简单的 module 扩展,接下来讲解如何在 uni-app 项目中调用刚刚扩展的 module 方法

在 uni-app 项目中调用 module 方法

module 支持在 vue 和 nvue 中调用,添加如下代码

<template>     <div>         <button type="primary" @click="testAsyncFunc">testAsyncFunc</button>         <button type="primary" @click="testSyncFunc">testSyncFunc</button>     </div> </template> <script>     // 首先需要通过 uni.requireNativePlugin("ModuleName") 获取 module      var testModule = uni.requireNativePlugin("DCTestUniPlugin-TestModule")     export default {         methods: {             testAsyncFunc() {                 // 调用异步方法                 testModule.testAsyncFunc({                         'name': 'uni-app',                         'age': 1                     },                     (ret) => {                         uni.showToast({                             title:'调用异步方法 ' + ret,                             icon: "none"                         })                     })             },             testSyncFunc() {                 // 调用同步方法                 var ret = testModule.testSyncFunc({                     'name': 'uni-app',                     'age': 1                 })                 uni.showToast({                     title:'调用同步方法 ' + ret,                     icon: "none"                 })             }         }     } </script>Copy to clipboardErrorCopied

然后我们要生成 uni-app 项目的本地打包资源,导入到插件开发工程中,测试一下功能是否正常

导入 uni-app 资源

生成 uni-app 本地打包资源

首先需要生成本地打包资源,在 HBuilderX 中选您的 uni-app 工程,右键->发现->原生App-本地打包->生成本地打包App资源

项目编译完成后会在 HBuilderX 控制台输出资源存路径,点击路径会自动打开资源所在文件夹

如下图所示,__UNI_7F5F813文件夹即为应用资源包(__UNI_7F5F813 为 uni-app 的 id)

导入 uni-app 资源

接下来,将应用资源导入到插件开发主工程的HBuilder-Hello/Pandora/apps/中,如下图所示,直接拖进去即可

然后打开工程的 control.xml 文件,将 appid 改成 uni-app 的 id,如下图所示

然后运行项目测试,如下图所示(能调到 module 的方法,并且可以获取 module 返回的数据,则说明功能正常)

再次导入资源时注意事项

:前端代码修改后重新导入资源时,需要在插件开发工程中删除之前导入的资源,同时将模拟器或真机上的 App 删除,在按照上面的教程操作,避免因为缓存问题导致加载的还是旧的资源;

接下来,我们学习一下如何扩展 Component

扩展组件 component

TestComponent为例,源码请查看 iOSSDK/HBuilder-uniPluginDemo/DCTestUniPlugin 插件工程;

新建TestComponent类,继承DCUniComponent类(如果这个类里什么代码也不写,它和默认的的 <view> 组件能力是一致的)

#import "DCUniComponent.h" @interface TestComponent : DCUniComponent @endCopy to clipboardErrorCopied

复写 DCUniComponent 中的生命周期方法

loadView 方法

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

注:不需要为view设置frameview的大小及位置由前端css决定

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

viewDidLoad

如果需要对组件view做一些配置,比如设置delegate,在 viewDidLoad 生命周期方法中是一个比较好的时机

注:可以直接通过 self.view 获取 view 实例

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

至此,已经完成了一个简单 component 的实现

然后我们需要在info.plist 中添加 component 的配置信息,和 module 配置格式一样具体请参考 配置插件信息

  • class:填写TestComponent

  • name:填写dc-testmap (在页面中使用时的组件名称,添加一个个性化的前缀,避免与其他组件冲突)

  • type:填写component

配置完后如下图所示

接下来可以在 uni-app 中使用组件

在 uni-app 中使用组件

注意:扩展的 component 只能在 nvue 文件中使用,不需要引入即可直接使用

在 uni-app 项目中新建 nvue 文件,加入下面的代码

<template>     <view>         <dc-testmap style="width:750rpx;height:300px"></dc-testmap>     </view> </template>Copy to clipboardErrorCopied

然后生成本地打包资源,导入到插件开发工程中进行测试,具体操作参考文档上方的教程 导入 uni-app 资源

然后运行测试,效果如下图:

刚刚做的组件只实现了 UI 显示,下面讲解组件的交互方式等一些高阶用法

自定义事件

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

uni-app 中是通过 @事件名="方法名" 添加事件,如下方代码所示在nvue中,给地图组件添加mapLoaded 事件

<template>     <div>         <dc-testmap style="width:750rpx;height:300px" @mapLoaded="onMapLoaded"></dc-testmap>     </div> </template> <script> export default {     methods: {         onMapLoaded:function(e) {             // 原生端传递的数据保存在 e.detail 中             console.log("map loaded: "+JSON.stringify(e.detail))         }     } } </script>Copy to clipboardErrorCopied
对应的原生端实现

我们需要添加一个 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;     } }Copy to clipboardErrorCopied
原生端向前端发送事件

在地图加载完毕的方法中触发 mapLoaded 事件

- (void)mapViewDidFinishLoadingMap:(MKMapView *)mapView {     if (_mapLoadedEvent) {         // 向前端发送事件,params 为传给前端的数据 注:数据最外层为 NSDictionary 格式,需要以 "detail" 作为 key 值         [self fireEvent:@"mapLoaded" params:@{@"detail":@{@"mapLoaded":@"success"}} domChanges:nil];     } }Copy to clipboardErrorCopied
globalEvent 事件

用于页面监听持久性事件,例如定位信息,陀螺仪等的变化。

示例:

页面监听event事件

var globalEvent = uni.requireNativePlugin('globalEvent'); globalEvent.addEventListener('myEvent', function(e) {   console.log('myEvent'+JSON.stringify(e)); });Copy to clipboardErrorCopied

插件 原生代码发出myEvent事件

NSDictionary * dict = [NSDictionary dictionaryWithObjectsAndKeys:@"value",@"key",nil]; NSString * eventName = @"myEvent"; DCUniSDKInstance * instance =  [instance fireGlobalEvent:eventName params:params];Copy to clipboardErrorCopied

注意 globalEvent事件只能通过页面的DCUniSDKInstance实例给当前页面发送globalEvent事件。其他页面无法接受。

自定义属性

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

<template>     <div>         <dc-testmap style="width:750rpx;height:300px" showTraffic="true"></dc-testmap>     </div> </template>Copy to clipboardErrorCopied
对应的原生端实现

覆盖组件方法 onCreateComponentWithRef... 给组件添加一个成员变量记录 showTraffic 属性的值,并在 init 方法中初始化

-(void)onCreateComponentWithRef:(NSString *)ref type:(NSString *)type styles:(NSDictionary *)styles attributes:(NSDictionary *)attributes events:(NSArray *)events uniInstance:(DCUniSDKInstance *)uniInstance {     if (attributes[@"showsTraffic"]) {         _showsTraffic = [DCUniConvert BOOL: attributes[@"showsTraffic"]];     } }Copy to clipboardErrorCopied

在生命周期方法中记得将属性值同步给地图控件

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

当前端更新属性时,会触发updateAttributes:方法,同步给地图控件**

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

更多的组件生命期方法

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

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

给组件添加方法

原生端实现

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

@implementation TestMapComponent // 通过 UNI_EXPORT_METHOD 将方法暴露给前端 UNI_EXPORT_METHOD(@selector(focus:)) // options 为前端传递的参数,支持 NSDictionary 或 NSString 类型 - (void)focus:(NSDictionary *)options {     NSLog(@"%@",options); } @endCopy to clipboardErrorCopied

在 uni-app 中调用 focus: 方法

<template>   <dc-testmap ref='mycomponent'></dc-testmap> </template> <script>   module.exports = {     created: function() {       // 通过 this.$refs.mycomponent 获取地图组件       // 调用组件 focus 方法       this.$refs.mycomponent.focus({'value':'Hello'});     }   } </script>Copy to clipboardErrorCopied

uni-app 完整项目存放在 iOSSDK/HBuilder-uniPluginDemo/UniPluginDemo

插件开发完毕并通过测试后,接下来就可以生成插件包了

生成插件包

此步骤应该在您插件所有功能都开发完毕,并在开发工程中测试完成进行

插件包结构说明

|-- 插件id                            // 插件包是一个以插件id命名的文件夹     |-- android                        // 存放 android 插件所需要的依赖库及资源文件     |-- ios                            // 存放 ios 插件所需要的依赖库及资源文件     |-- package.json                // 插件配置文件Copy to clipboardErrorCopied

插件包的目录结构是固定的,是一个以插件id命名的文件夹,其中 android 路径中放的是 android 端插件所需要的依赖库及资源文件,ios 路径中放的是 ios 端插件所需要的依赖库及资源文件,package.json为插件的配置文件,接下来,我们需要生成 iOS 插件所需的依赖库,和编写 package.json 文件

编译生成插件库文件(.framework 或 .a)

如下图所示,将编译工程选择为插件项目(DCTestUniPlugin),运行设备选择Generic iOS Device

然后点击Edit Scheme...

在弹窗中,将Run->Info->Build Configuration切换到Release,然后点击Close关闭弹窗

然后在 Xcode 左侧目录中选中插件工程名,查看TARGETS->Build Settings->Architectures,确保

  • Build Active Architecture Only->Release 为 No

  • Valid Architectures 中至少包含 arm64 和 armv7(一般保持工程默认配置即可)

iOS Deployment Target中选中最低支持的 iOS 版本,建议选择 iOS9.0

然后点击运行按钮或 Command + B 编译运行工程

编译完成后,在插件工程 Products 下生成的库(DCTestUniPlugin.framework)即为插件所需要的依赖库文件,右键->Show in Finder,可打开库所在文件夹

编写 package.json 配置文件

package.json 为插件的配置文件,配置了插件id、格式、插件资源以及插件所需权限等等信息

新建一个 package.json 文件,然后请参考 uni原生插件包格式 说明,根据您插件实际情况填写插件配置信息,示例插件配置完后如下所示

{     "name": "TestUniPlugin",     "id": "DCTestUniPlugin",     "version": "1.0.0",     "description": "uni示例插件",     "_dp_type": "nativeplugin",     "_dp_nativeplugin": {         "ios": {             "plugins": [{                 "type": "module",                 "name": "DCTestUniPlugin-TestModule",                 "class": "TestModule"             }, {                 "type": "component",                 "name": "dc-testmap",                 "class": "TestComponent"             }],             "frameworks": ["MapKit.framework"],             "integrateType": "framework",             "deploymentTarget": "9.0"         }     } }Copy to clipboardErrorCopied

然后以插件id为名新建一个文件夹,将编辑好的 package.json 放进去,然后在文件夹中在新建一个 ios 文件夹,将刚刚生成的依赖库(DCTestUniPlugin.framework)copy 到 ios 根目录,这样我们的插件包就构建完成了,如下图所示

注:iOS 插件包至少需要包含:package.json文件 和ios文件夹(小写的ios)里面包含 .a 或 .framework 依赖库;

使用插件

放到 HBuilderX 的 uni-app 项目中直接使用

插件包开发完后,可以直接放到 HX 的 uni-app 项目中使用,具体请参考文档 HBuilderX 中使用本地插件

提交插件市场

按照上面的方式放到 HBuilderX 的 uni-app 项目中提交打包测试完插件后,可以将插件包压缩成 zip 格式,然后提交到插件市场共享给其他开发者使用,登录注册DCloud插件市场,然后按照提示步骤提交插件,(需要编写对应插件的使用说明文档,.md(markdown)格式);

常见问题

Q:集成广告SDK说明

A: 由于官方 UniAD 广告组件集成了“广点通”和“穿山甲”SDK,目前不支持自行开发包含这两个SDK的原生插件,云打包会导致冲突;

Q: 插件开发需要依赖第三方SDK,或需要依赖资源文件

  • 如果您的插件需要依赖第三方的SDK,开发阶段引入三方SDK的时候要引入到主工程,然后将三方SDK提供的 .h 头文件直接添加到插件工程中这样就可以正常调用三方SDK的Api了,功能开发完毕后在构建插件包的时候,需要将依赖的三方SDK库文件放到ios路径下,然后按照规范编辑 package.json

  • 如果需要依赖资源文件比如图片等,建议把资源文件放到.bundle包中使用,开发阶段资源文件也要添加到主工程中,不然引用不到,功能开发完毕后在构建插件包的时候,把 bundle 包放到 ios 路径下,然后按照规范配置 package.json,注意:xib 文件也属于资源文件,需要放到主工程中;

具体请参考开源项目 百度OCR识别插件源码,对应插件市场的 插件

Q: 如何跳转原生 UIViewController

A: 因为 uni 框架机制,module 的 uniInstance 没有绑定 viewController,故 uniInstance.viewController 值为 nil,如果想通过 UIViewController 来跳转页面可使用下面的方法获取 UIViewController**

// 获取当前显示的 UIViewController + (UIViewController *)dc_findCurrentShowingViewController {     //获得当前活动窗口的根视图     UIViewController *vc = [UIApplication sharedApplication].keyWindow.rootViewController;     UIViewController *currentShowingVC = [self findCurrentShowingViewControllerFrom:vc];     return currentShowingVC; } + (UIViewController *)findCurrentShowingViewControllerFrom:(UIViewController *)vc {     // 递归方法 Recursive method     UIViewController *currentShowingVC;     if ([vc presentedViewController]) {         // 当前视图是被presented出来的         UIViewController *nextRootVC = [vc presentedViewController];         currentShowingVC = [self findCurrentShowingViewControllerFrom:nextRootVC];     } else if ([vc isKindOfClass:[UITabBarController class]]) {         // 根视图为UITabBarController         UIViewController *nextRootVC = [(UITabBarController *)vc selectedViewController];         currentShowingVC = [self findCurrentShowingViewControllerFrom:nextRootVC];     } else if ([vc isKindOfClass:[UINavigationController class]]){         // 根视图为UINavigationController         UIViewController *nextRootVC = [(UINavigationController *)vc visibleViewController];         currentShowingVC = [self findCurrentShowingViewControllerFrom:nextRootVC];     } else {         // 根视图为非导航类         currentShowingVC = vc;     }     return currentShowingVC; }Copy to clipboardErrorCopied

Q: 如何获取页面可访问图片资源存储路径 (vue页面需要注意,nvue页面不存这个问题)

A: 有些插件可能需要返回本地的图片路径给页面来显示,如果页面是vue的话是使用 WKWebview 渲染,访问本地图片资源可能会存在跨域问题访问不到图片资源,所以需要将图片存放到指定路径下,按照下面的方法获取图片存储路径即可

原生端实现

// 引用头文件 #import "PDRCoreApp.h" #import "PDRCoreAppManager.h" #import "PDRCoreAppInfo.h" // 获取路径信息 PDRCoreAppInfo *appinfo = [PDRCore Instance].appManager.getMainAppInfo; // 将图片存储到 appinfo.documentPath 路径下即可,可以创建子目录; // 示例,原生图片存储路径为 NSString *imgPath = [appinfo.documentPath stringByAppendingPathComponent:@"test.png"];Copy to clipboardErrorCopied

js 端获取图片路径有两种方式

说明:"_doc" 是一个特殊字符,和原生端的 appinfo.documentPath对应

// 1.使用相对路径(直接使用 "_doc/" 拼接图片路径) var relativePath = "_doc/test.png" // 2.使用平台的绝对路径(先将 "_doc/" 转换成原生绝对路径在拼接图片路径) var docPath = plus.io.convertLocalFileSystemURL("_doc/"); var absolutePath = docPath + 'test.png'Copy to clipboardErrorCopied

Q: 打包报错 “Undefined symbols for architecture”

A: 这类错误基本都是缺少依赖库导致的,仔细看报错信息,查看未定义的符号属于哪个库(有可能是系统库,也有可能是第三方库)然后将缺少的库放到插件包或修改package.json 配置文件后重新提交打包;

Q: 依赖库冲突 “duplicate symbols for ...”

A:如果您依赖的三方库与SDK依赖的三方库冲突

  • 为了保证SDK功能的完整性所以请您使用SDK内置的三方库,移除您依赖的三方库,对于源码开源的三方库比如 SDWebImage、ZXing等,这些库的 .h 头文件存放在 SDK/inc 路径中的,将对应库的头文件引入到插件工程中使用即可,如果您使用内置的三方库导致原生功能异常,请反馈给我们;

  • 如果引入的是 .a 或 .framework 库里面包含某个三方库导致符号定义冲突,可根据 这篇文档 移除对应库的符号,然后测试一下功能是否正常;

SDK名称版本备注
百度定位v4.1.1
百度地图v4.1.1
高德定位v2.6.3
高德地图v6.9.0
微信SDKv1.8.6.2
AlipaySDKv15.7.4
QQSDKv3.3.6_lite
新浪微博SDKv003233010
又拍云v4.1.7
友盟v6.1.0
IJKPlayer--
SDWebImagev5.10.0对应的库 libSDWebImage.a
Masonry--包含在 DCUniVideoPublic.framework 中
SSZipArchive2.2.3对应的库 libcoreSupport.a
ZXing
对应的库 libDCUniZXing.a 源码已修改

Q: 之前引用的头文件和资源 现在找不到了

A:为了解决部分功能库引用了一些第三方库 可能与开发者项目的第三方库重复引用 我们从3.0.7后 对于部分库和资源进行了调整。

如果开发者原来工程对资源文件有引用,现在找不到资源文件,请参考下边列表(文件路径去离线SDK下的 SDK/Bundles/下寻找)

旧资源新资源
TZImagePickerController.bundleDCTZImagePickerController.bundle
SVProgressHUD.bundleDCSVProgressHUD.bundle

如果开发者原来工程对库的头文件有引用,现在找不到头文件,请参考下边列表(文件路径去离线SDK下的 SDK/inc/下寻找)

TZImagePickerController -> DCTZImagePickerController

旧头文件新头文件
TZAssetCell.hDCTZAssetCell.h
TZAssetModel.hDCTZAssetModel.h
TZGifPhotoPreviewController.hDCTZGifPhotoPreviewController.h
TZImageCropManager.hDCTZImageCropManager.h
TZImageManager.hDCTZImageManager.h
TZImagePickerController.hDCTZImagePickerController.h
TZLocationManager.hDCTZLocationManager.h
TZPhotoPickerController.hDCTZPhotoPickerController.h
TZPhotoPreviewCell.hDCTZPhotoPreviewCell.h
TZPhotoPreviewController.hDCTZPhotoPreviewController.h
TZProgressView.hDCTZProgressView.h
TZVideoPlayerController.hDCTZVideoPlayerController.h
UIViewControllerHUD.hDCUIViewControllerHUD.h
NSBundle+TZImagePicker.hNSBundle+DCTZImagePicker.h
UIView+Layout.hUIView+DCLayout.h

SVProgressHUD -> DCSVProgressHUD

旧头文件新头文件
SVIndefiniteAnimatedView.hDCSVIndefiniteAnimatedView.h
SVProgressAnimatedView.hDCSVProgressAnimatedView.h
SVProgressHUD.hDCSVProgressHUD.h
SVRadialGradientLayer.hDCSVRadialGradientLayer.h

SocketRoket -> DCSocketRoket

旧头文件新头文件
SRWebSocket.hDCSRWebSocket.h



评论

在线客服-可直接交谈

您好!有什么需要可以为您服务吗?