uni原生插件指的是将您原生开发的功能按照规范封装成插件包,然后即可在uni-app前端项目中通过js调用您开发的原生能力。
打开Xcode,创建一个新的工程,template选择Framework或StaticLibrary(示例工程选择的是Framework),然后点击Next
在ProductName中输入插件工程名称(建议使用一个性化的前缀,避免与其他人的插件包名冲突),其他项不需要修改保持工程默认填充的即可,然后点击Next
然后选择工程存放路径,建议直接存放在iOSSDK目录中的HBuilder-uniPluginDemo插件开发主工程目录下,如下图所示,然后点击Create
强烈建议将插件工程存放在HBuilder-uniPluginDemo插件开发主工程目录中,因为插件工程需要根据插件开发主工程相对路径引用一些文件,之后您升级SDK的时候只需要将您的插件工程Copy到新的SDK的相同位置下即可
可以删除工程自动创建的.h文件,这个文件用不到
然后选中工程名,在TARGETS->BuildSettings中,将Mach-OType设置为StaticLibrary如下图所示
然后将插件工程关闭,接下来需要将插件工程导入到插件开发主工程中
打开iOSSDK/HBuilder-uniPluginDemo工程目录,双击目录中的HBuilder-uniPlugin.xcodeproj文件运行插件开发主工程
在Xcode项目左侧目录选中主工程名,然后点击右键选择AddFilesto“HBuilder-uniPlugin”...
然后选择您刚刚创建的插件工程路径中,选中插件工程文件,勾选Createfolderreferences和Addtotargets两项,然后点击Add
这时在Xcode左侧目录中可以看到插件工程已经添加到了主工程中,如下图所示
然后在Xcode项目左侧目录选中主工程名,在TARGETS->BuildPhases->Dependencies中点击+
在弹窗中选中插件工程,如图所示,然后点击Add,将插件工程添加到Dependencies中
然后在LinkBinaryWithLibraries中点击+,同样在弹窗中选中插件工程,点击Add
此时可以看到Dependencies和LinkBinaryWithLibraries都添加了插件工程,如下图所示
接下来需要在插件工程的HeaderSearchPaths中添加开发插件所需的头文件引用,头文件存放在主工程的HBuilder-Hello/inc中,添加方法如下图所示,在Xcode项目左侧目录选中插件工程名,找到TARGETS->BuildSettings->HeaderSearchPaths双击右侧区域打开添加窗口,然后将inc目录拖入会自动填充相对路径,然后将模式改成recursive
原生插件是基于DCUniPlugin规范来实现,扩展原生功能有两种方式:
您需要根据实际的情况选择扩展方式,当然插件中可以同时存在module和component,也可以是多个module和多个component;
特别注意如果需要扩展自定义的module或者component,一定注意不要将oc的runtime暴露给JS,不要将一些诸如dlopen(),dlsym(),respondsToSelector:,performSelector:,method_exchangeImplementations()的动态和不可控的方法暴露给JS,也不要将系统的私有API暴露给JS。否则将可能面临苹果上架审核问题。
以TestModule为例,源码请查看iOSSDK/HBuilder-uniPluginDemo/DCTestUniPlugin插件工程;
新建TestModule类,继承DCUniModule,引入DCUniModule.h头文件。
TestModule.h文件
#import
然后在TestModule.m文件中添加实现方法
异步方法实现
///异步方法(注:异步方法会在主线程(UI线程)执行)///@paramoptionsjs端调用方法时传递的参数支持:String、Number、Boolean、JsonObject类型///@paramcallback回调方法,回传参数给js端支持:NSString、NSDictionary(只能包含基本数据类型)、NSNumber类型-(void)testAsyncFunc:(NSDictionary*)optionscallback:(UniModuleKeepAliveCallback)callback{//options为js端调用此方法时传递的参数NSLog(@"%@",options);//可以在该方法中实现原生能力,然后通过callback回调到jsif(callback){//第一个参数为回传给js端的数据,第二个参数为标识,表示该回调方法是否支持多次调用,如果原生端需要多次回调js端则第二个参数传YES;callback(@"success",NO);}}通过宏UNI_EXPORT_METHOD将异步方法暴露给js端,只有通过UNI_EXPORT_METHOD暴露的原生方法才能被js端识别到
//通过宏UNI_EXPORT_METHOD将异步方法暴露给js端UNI_EXPORT_METHOD(@selector(testAsyncFunc:callback:))同步方法实现
///同步方法(注:同步方法会在js线程执行)///@paramoptionsjs端调用方法时传递的参数支持:String、Number、Boolean、JsonObject类型-(NSString*)testSyncFunc:(NSDictionary*)options{//options为js端调用此方法时传递的参数NSLog(@"%@",options);/*可以在该方法中实现原生功能,然后直接通过return返回参数给js*///同步返回参数给js端支持:NSString、NSDictionary(只能包含基本数据类型)、NSNumber类型return@"success";}通过宏UNI_EXPORT_METHOD_SYNC将同步方法暴露给js端
//通过宏UNI_EXPORT_METHOD_SYNC将同步方法暴露给js端UNI_EXPORT_METHOD_SYNC(@selector(testSyncFunc:))TestModule.m文件截图:
如果需要在App启动时初始化或者需要获取系统的一些事件,需要新建一个XXXXProxy类(注意命名加前缀防止冲突),继承NSObject遵守UniPluginProtocol协议
Module进阶
关于Module和Module方法的执行特性(同步、异步;执行线程),需要了解:
uniExecuteThread
Module方法默认会在UI线程(iOS主线程)中被调用,建议不要在这做太多耗时的任务。
如果你的任务不需要在UI线程执行或需要在特定线程执行,需要实现DCUniModule中的uniExecuteThread的属性,并返回你希望方法执行所在的线程。
Module自定义队列和线程
在DCUniModule中可以通过uniExecuteQueue来实现自定义queue,通过uniExecuteThread来实现自定义的thread
特别注意如果同时指定了uniExecuteQueue和uniExecuteThread,只会执行uniExecuteQueue,uniExecuteThread将会被忽略。
如果只实现了uniExecuteThread,代码中注意要线程保活
参考示例代码
配置完如下图所示**(必须严格按照格式配置)**:
到此,我们已经完成了一个简单的module扩展,接下来讲解如何在uni-app项目中调用刚刚扩展的module方法
module支持在vue和nvue中调用,添加如下代码
本操作需要在HBuilderX中进行,需要您新建一个uni-app项目,并编写js端代码,最后导出本地资源右键->发现->原生App-本地打包->生成本地打包App资源
项目编译完成后会在HBuilderX控制台输出资源存路径,点击路径会自动打开资源所在文件夹
如下图所示,__UNI_7F5F813文件夹即为应用资源包(__UNI_7F5F813为uni-app的id)
接下来,将应用资源导入到插件开发主工程的HBuilder-Hello/Pandora/apps/中,如下图所示,直接拖进去即可
然后打开工程的control.xml文件,将appid改成uni-app的id,如下图所示
然后运行项目测试,如下图所示(能调到module的方法,并且可以获取module返回的数据,则说明功能正常)
注:前端代码修改后重新导入资源时,需要在插件开发工程中删除之前导入的资源,同时将模拟器或真机上的App删除,在按照上面的教程操作,避免因为缓存问题导致加载的还是旧的资源;
接下来,我们学习一下如何扩展Component
以TestComponent为例,源码请查看iOSSDK/HBuilder-uniPluginDemo/DCTestUniPlugin插件工程;
新建TestComponent类,继承DCUniComponent类(如果这个类里什么代码也不写,它和默认的的
一个组件默认对应一个原生view,如果未复写loadView方法提供自定义view,会默认调用基类方法返回一个继承于UIView的实例。比如我们要实现一个组件支持地图功能,我们可以返回系统的MKMapView。
**注:**不需要为view设置frame,view的大小及位置由前端css决定
-(UIView*)loadView{return[MKMapViewnew];}-viewDidLoad
如果需要对组件view做一些配置,比如设置delegate,在viewDidLoad生命周期方法中是一个比较好的时机
**注:**可以直接通过self.view获取view实例
-(void)viewDidLoad{((MKMapView*)self.view).delegate=self;}至此,已经完成了一个简单component的实现
配置完后如下图所示
接下来可以在uni-app中使用组件
注意:扩展的component只能在nvue文件中使用,不需要引入即可直接使用
在uni-app项目中新建nvue文件,加入下面的代码
然后运行测试,效果如下图:
刚刚做的组件只实现了UI显示,下面讲解组件的交互方式等一些高阶用法
uni-app中是通过@事件名="方法名"添加事件,如下方代码所示在nvue中,给地图组件添加mapLoaded事件
-(void)onCreateComponentWithRef:(NSString*)reftype:(NSString*)typestyles:(NSDictionary*)stylesattributes:(NSDictionary*)attributesevents:(NSArray*)eventsuniInstance:(DCUniSDKInstance*)uniInstance{if(attributes[@"showsTraffic"]){_showsTraffic=[DCUniConvertBOOL:attributes[@"showsTraffic"]];}}在生命周期方法中记得将属性值同步给地图控件
-(void)viewDidLoad{((MKMapView*)self.view).showsTraffic=_showsTraffic;}当前端更新属性时,会触发updateAttributes:方法,同步给地图控件**
原生端实现
在组件代码中使用宏UNI_EXPORT_METHOD暴露原生方法供前端调用
@implementationTestMapComponent//通过UNI_EXPORT_METHOD将方法暴露给前端UNI_EXPORT_METHOD(@selector(focus:))//options为前端传递的参数,支持NSDictionary或NSString类型-(void)focus:(NSDictionary*)options{NSLog(@"%@",options);}@end在uni-app中调用focus:方法
在module和component中用于页面监听持久性事件,例如定位信息,陀螺仪等的变化。
globalEvent事件只能通过页面的DCUniSDKInstance实例给当前页面发送globalEvent事件。其他页面无法接受。
示例:
页面监听event事件
varglobalEvent=uni.requireNativePlugin('globalEvent');globalEvent.addEventListener('myEvent',function(e){console.log('myEvent'+JSON.stringify(e));});在原生代码发出myEvent事件
NSDictionary*dict=[NSDictionarydictionaryWithObjectsAndKeys:@"value",@"key",nil];NSString*eventName=@"myEvent";DCUniSDKInstance*instance=self.uniInstance;[instancefireGlobalEvent:eventNameparams:params];插件开发完毕并通过测试后,接下来就可以生成插件包了
此步骤应该在您插件所有功能都开发完毕,并在开发工程中测试完成进行
|--插件id//插件包是一个以插件id命名的文件夹|--android//存放android插件所需要的依赖库及资源文件|--ios//存放ios插件所需要的依赖库及资源文件|--package.json//插件配置文件插件包的目录结构是固定的,是一个以插件id命名的文件夹,其中android路径中放的是android端插件所需要的依赖库及资源文件,ios路径中放的是ios端插件所需要的依赖库及资源文件,package.json为插件的配置文件,接下来,我们需要生成iOS插件所需的依赖库,和编写package.json文件
如下图所示,将编译工程选择为插件项目(DCTestUniPlugin),运行设备选择GenericiOSDevice
然后点击EditScheme...
在弹窗中,将Run->Info->BuildConfiguration切换到Release,然后点击Close关闭弹窗
然后在Xcode左侧目录中选中插件工程名,查看TARGETS->BuildSettings->Architectures,确保
在iOSDeploymentTarget中选中最低支持的iOS版本,建议选择iOS11.0
然后点击运行按钮或Command+B编译运行工程
编译完成后,点击顶部菜单Product>ShowBuildFolderinFinder打开编译后的路径,Release-iphoneos中的库就是编译后的插件库文件;
package.json为插件的配置文件,配置了插件id、格式、插件资源以及插件所需权限等等信息
{"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"}}}然后以插件id为名新建一个文件夹,将编辑好的package.json放进去,然后在文件夹中在新建一个ios文件夹,将刚刚生成的依赖库(DCTestUniPlugin.framework)copy到ios根目录,这样我们的插件包就构建完成了,如下图所示
**注:**iOS插件包至少需要包含:package.json文件和ios文件夹(小写的ios)里面包含.a或.framework依赖库;
A:因为uni框架机制,module的uniInstance没有绑定viewController,故uniInstance.viewController值为nil,如果想通过UIViewController来跳转页面可使用下面的方法获取UIViewController**
//引用头文件#import"PDRCoreApp.h"#import"PDRCoreAppManager.h"#import"PDRCoreAppInfo.h"//获取路径信息PDRCoreAppInfo*appinfo=[PDRCoreInstance].appManager.getMainAppInfo;//将图片存储到appinfo.documentPath路径下即可,可以创建子目录;//示例,原生图片存储路径为NSString*imgPath=[appinfo.documentPathstringByAppendingPathComponent:@"test.png"];js端获取图片路径有两种方式
说明:"_doc"是一个特殊字符,和原生端的appinfo.documentPath对应
A:如果您依赖的三方库与SDK依赖的三方库冲突
A:为了解决部分功能库引用了一些第三方库可能与开发者项目的第三方库重复引用我们从3.0.7后对于部分库和资源进行了调整。
如果开发者原来工程对资源文件有引用,现在找不到资源文件,请参考下边列表(文件路径去离线SDK下的SDK/Bundles/下寻找)
如果开发者原来工程对库的头文件有引用,现在找不到头文件,请参考下边列表(文件路径去离线SDK下的SDK/inc/下寻找)