1.概述—————————————下载PDF本文档主要介绍了如何进行AppCan iOS原生插件开发。
1.1面向的读者在您阅读此文档时,我们假定您已经具备了基础的iOS应用开发经验,并能够理解相关基础概念。
此外,您也应该对HTML,JavaScript,CSS等有一定的了解,并且熟悉在JavaScript和Objective-C环境下的JSON格式数据操作。
提示:@自定义插件作者 目前ios扩展的插件目前只能在线打包进行测试和使用,阅读如下文档
关于自定义插件上传(目前支持在线),请参阅文档。
关于自定义插件发布到插件中心,请参阅文档。
1.2开发环境需求OS X 10.10+
Xcode 7.0+
AppCan iOS插件开发包(git地址)
2.插件开发的准备工作以下以开发一个范例插件DemoPlugin为例,为您介绍如何进行iOS插件开发
在本文档中,会用 斜体字 表示那些不是必须,但我们强烈推荐您这样做的操作。
2.1 创建静态库工程打开Xcode,在菜单栏中选择 File - New - Project…
选择 iOS - Framework & Library - Cocoa Touch Static Library
填入您的插件基本信息,Product Name填EUExDemoPlugin(注1),点击Next
选择静态库工程的保存地址,点击create,建立一个静态库工程
编辑EUExDemoPlugin这个target的Build Settings如下(注2):
将Product Name对应的值修改为 uexDemoPlugin(注3)
将Pre-configuration Build Products Path 修改为$SRCROOT/uexDemoPlugin(注4)
编辑EUExDemoPlugin这个target的Build Phases,找到Copy Files这个phase,清空其Subpath设置,移除下面列表中的.h文件
注1: 此处静态库工程的命名规则为 EUEx + 插件名称,之后出现的EUExDemoPlugin亦是如此。
注2: 修改target的BuildSettings的方法如下图所示,选中工程主体-选择指定的target-选择BuildSettings-选中all,然后在右上角搜索框中搜索相应的键,双击编辑对应的值
注3: 此处Product Name 的命名规则为 uex + 插件名称,之后出现的uexDemoPlugin亦是如此。
注4: 此设置使得工程编译得到的.a文件会生成在工程目录下的uexDemoPlugin文件夹中,方便后续的插件打包
2.2 编辑插件调试工程关闭刚刚创建的静态库工程,从AppCan iOS插件开发包中找到调试插件的工程模板,复制一份到本地目录
打开工程模板中的调试工程AppCanPlugin.xcodeproj,将刚刚创建的静态库工程EUExDemoPlugin.xcodeproj引入此调试工程(直接拖拽即可,注意引入之前要确认静态库工程已经被关闭)
编辑主工程AppCanPlugin这个target的Build Phases如下(注1):Target Dependencies 中,添加插件工程的EUExDemoPluin的target
Link Binary With Libraries中,添加libeuxDemoPlugin.a(注2)
注1:
编辑Build Phases方法如下图所示:选中工程主体-选择target-选择Build Phases - 展开相应的Phase - 点击下方的按钮进行相应的操作

注2:
编辑完成后应该如下图所示:

2.3 插件调试工程简介
见下图,红框标注部分都是在插件开发调试中可能会用到的部分。

好了,到此,前期的准备工作就已经完成了,可以正式开始插件开发了!
3.开始插件开发
所有的开发和调试工作,都可以直接在刚刚建立的插件调试工程中进行!
3.1 编写插件入口类在AppCan插件开发包中,打开AppCan引擎头文件文件夹,找到engineHeader,将此文件夹引入插件工程,如下图所示

新建插件入口类EUExPlugin。如果你的插件静态库工程名就是EUExDemoPlugin,那么这个类应该已经自动生成了,此步可跳过。
在EUExDemoPlugin这个类的头文件中引入EUExBase.h 并使得EUExDemoPlugin类继承自EUExBase
在此类中实现生命周期方法initWithBrwView:和clean
-(instancetype)initWithBrwView:(EBrowserView*)eInBrwView
{
self=[superinitWithBrwView:eInBrwView];
if(self){
//NSLog(@"插件实例被创建");
}
returnself;
}
-(void)clean{
//NSLog(@"网页即将被销毁");
}
插件中类的命名规则插件的入口类必须命名为EUEx开头的类名;
插件中其他的类无命名限制,但建议增加独特的前缀,以避免和引擎以及其他插件中的类产生类名冲突,导致打包失败
EUExBase.h简介EUExBase是AppCan插件入口的基类,所有的插件入口类都必须继承自此类。
EUExBase拥有1个实例变量和3个实例方法
实例变量meBrwView是一个弱引用,指向了AppCan的网页对象。任何对网页的操作都会通过此对象进行。
实例方法initWithBrwView:是默认的初始化方法。每当一个网页里调用某插件的方法时(比如uexDemoPlugin.test();),都会先去寻找插件的入口实例(EUExDemoPlugin),如果不存在,则会通过此方法创建一个新的实例并持有它。
对于同一个插件,每个网页里只会有一个插件实例,但不同的网页会拥有不同的插件实例。
插件子类可以覆写此方法进行自定义初始化设置,但必须调用父类的此方法
实例方法clean 会在网页被关闭前调用插件子类可以覆写此方法进行必要的清除工作,比如停止NSTimer,关闭网络连接等等。
在此方法被调用后,插件不应该再使用self.meBrwView对网页进行任何操作。
在此方法被调用后, 除非特殊情况,不要让插件实例被其他任何类强硬用,否则很有可能会造成内存问题。
实例方法absPath:用于转换AppCan协议路径(res://,wgt://等)至绝对路径。
3.2 插件和网页进行交互
3.2.1 暴露接口给网页
本小节示范了如何让网页JS去调用一个原生的方法helloWorld,实现 JavaScript —> OC 的操作
在EUExDemoPlugin类中实现一个方法helloWorld:
-(void)helloWorld:(NSMutableArray*)inArguments{
//打印 hello world!
NSLog(@"hello world!");
}
插件入口类中实现供网页调用的方法的注意事项1.注意所有供网页调用的方法必须带有一个类型为NSMutableArray类型的参数
2.引擎默认是在主线程异步调用插件方法,因此需要长时间耗时的阻塞操作,最好放在非主线程中进行,以免造成App卡死。在主工程的plugin.xml中添加此接口的信息,规则如下
plugin.xml中注册插件方法的基本规则
1.每一个插件唯一对应了一个节点,节点中必须声明此插件的名字 用name字段表示
2.在插件节点内,每个 节点对应了一个暴露给网页的插件方法,方法名字用name字段表示
在网页中写一个按钮,在点击按钮的JS事件中调用uexDemoPlugin.helloWorld();
html部分
JavaScript部分
varhelloWorld=function(){
uexDemoPlugin.helloWorld();
}好了 让我们运行工程,点击按钮看一下效果吧!

3.2.2 网页传值给原生环境
本小节示范了如何从网页传值给原生环境
在EUExDemoPlugin类中实现一个方法sendValue:
-(void)sendValue:(NSMutableArray*)inArguments{
//打印传入的参数个数
NSLog(@"arguments count : %@",@(inArguments.count));
//打印每个参数的描述,和参数所在的类的描述
for(NSIntegeri=0;i
id obj=inArguments[i];
NSLog(@"value : %@ , class : %@ ",[obj description],[[objclass]description]);
}
}在plugin.xml中添加方法
在网页中调用的JS如下
uexDemoPlugin.sendValue("aaa",12,true,["x","y"],{key:"value"});结果如下

JaveScript—>OC传值的转换规则
由上述例子可以看到,JSValue按照如下规则转换成了NSObject
JSValue
NSObject
String
NSString
Number
NSNumber
Boolean
NSNumber
Array
NSArray
Object
NSDictionary
null,undefined
NSNull
Function
不支持(注)
注: 任何function都会被转换成一个空的NSDictionary,其所有信息都会丢失;
3.2.3 网页传递JSON数据给原生环境上述直接传值在Android方面会引起诸多兼容性问题,并且后续的拓展性较差。因此建议统一采用JSON数据格式进行传值。
AppCan引擎内封装了SBJSON库用于JSON的解析,之后的示例代码都会采用这个库进行JSON的序列化/反序列化。
当然,如果您偏好其他的JSON解析方法,只需要替换其中的JSON解析步骤即可,完全没有任何影响。从AppCan插件开发包的AppCan引擎头文件文件夹中找到JSON文件夹(这个文件夹中包含了SBJSON库的头文件),引入静态库工程。在EUExDemoPlugin类中引入JSON.h
在EUExDemoPlugin类中实现一个方法sendJSONValue:,并在config.xml中添加相应的方法。
-(void)sendJSONValue:(NSMutableArray*)inArguments{
if([inArguments count]<1){
//当传入的参数为空时,直接返回,避免数组越界错误。
return;
}
id json=[inArguments[0]JSONValue];
NSLog(@"json : %@ class : %@",[json description],[[jsonclass]description]);
}在网页中调用的JS如下
varjson={
key1:"aaa",
key2:12,
key3:true,
key4:["x","y"],
key5:{key:"value"}
}
uexDemoPlugin.sendJSONValue(JSON.stringify(json));结果如下

3.2.4 原生异步回调JS给网页此小节示范了如何通过EUtility工具类中的方法执行网页中的JS,实现OC-->JavaScript的操作
异步回调的本质是执行一段JS脚本在EUExDemoPlugin类中引入EUtility.h,这个头文件在engineHeader文件夹中,之前就应该已经引入工程了。EUtility是引擎中的工具类,此类中主要封装了原生操作网页的一系列方法。
由于方法较多,这里就不一一解释了,可以直接参看EUtility.h中的方法注释。

如果EUtility.h中报Expected a Type错误,在EUtility.h中引入系统库UIKit(#import )即可解决
//EUtility.h中和JS相关的方法有4个
//在指定网页中执行JS脚本
+(void)brwView:(EBrowserView*)inBrwView evaluateScript:(NSString*)inScript;
//在主窗口中执行JS脚本
+(void)evaluatingJavaScriptInRootWnd:(NSString*)script;
//在最顶端的窗口中执行JS脚本
+(void)evaluatingJavaScriptInFrontWnd:(NSString*)script;
//以及对上述3个方法的进一步封装
+(void)uexPlugin:(NSString*)pluginName callbackByName:(NSString*)functionName withObject:(id)obj andType:(uexPluginCallbackType)type inTarget:(id)target;
//详细参数说明请见EUtility.h中的注释在EUExDemoPlugin类中实现一个方法doCallback:,并在config.xml中添加相应的方法。
-(void)doCallback:(NSMutableArray*)inArguments{
NSDictionary*dict=@{
@"key":@"value"
};
//构造JavaScript脚本
//[dict JSONFragment] 可以把NSString NSDictionary NSArray 转换成JSON字符串
NSString*jsStr=[NSStringstringWithFormat:@"if(uexDemoPlugin.cbDoCallback){uexDemoPlugin.cbDoCallback('%@')}",[dictJSONFragment]];
//回调给当前网页
[EUtilitybrwView:self.meBrwView evaluateScript:jsStr];
}在网页中注册回调函数cbDoCallback,并调用doCallback方法在cbDoCallback函数中,我们封装一个JS方法showDetails用于展示回调结果
//封装一个JS方法用于查看回调结果
varshowDetails=function(){
varcount=arguments.length;
varresult="";
for(inti=0;i
result+=("type:"+typeof(obj)+"\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\n value:"+obj+"\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\n");
}
alert(result);
}
window.uexOnload=function(){
uexDemoPlugin.cbDoCallback=function(jsonStr){
//回调的参数是JSON字符串,需要解析成Object
varjson=JSON.parse(jsonStr);
//查看回调结果
showDetails(jsonStr,json,json.key);
}
};uexDemoPlugin.doCallback();
注册异步回调的JS方法注意事项注册回调函数必须要在window.uexOnload或者AppCan.ready(如果你已经引入了appcan.js)中进行
插件调试时,建议使用window.uexOnload,避免AppCanJSSDK可能的干扰调用接口后,控制台显示数据如下

每次都要如上构造JavaScript脚本确实有些繁琐,因此EUtility封装了一个更简洁的方法,回调过程可以省略如下
NSDictionary*dict=@{
@"key":@"value"
};
[EUtilityuexPlugin:@"uexDemoPlugin"
callbackByName:@"cbDoCallback"
withObject:dict
andType:uexPluginCallbackWithJsonString
inTarget:self.meBrwView];由于不同的方法可能都需要进行回调,因此可以进行进一步封装,方便复用
/
* 异步回调方法的封装
*
* @param funcName 回调函数名
* @param obj 回调的对象
*/
-(void)callbackJSONWithName:(NSString*)funcNameobject:(id)obj{
[EUtilityuexPlugin:@"uexDemoPlugin"
callbackByName:funcName
withObject:obj
andType:uexPluginCallbackWithJsonString
inTarget:self.meBrwView];
}NSDictionary*dict=@{
@"key":@"value"
};
//然后在插件接口中直接调用此方法即可
[selfcallbackJSONWithName:@"cbDoCallback"object:dict];
3.2.5 同步返回值给网页此回调方式仅限3.3+引擎
方法可以直接同步返回值给网页,避免繁琐的异步过程。
注意若使用同步回调,需避免在返回值前执行耗时的操作,以免影响用户体验。在EUExDemoPlugin类中实现一个方法doSyncCallback:
-(NSDictionary*)doSyncCallback:(NSMutableArray*)inArguments{
return@{
@"key1":@"value1",
@"key2":@(NO),
@"key3":@{
@"subKey":@"subValue"
}
};
}
如何在plugin.xml中注册一个同步方法在plugin.xml中声明此方法,并且设置属性sync为"true",表明这是一个同步方法
在网页中如下所示调用JS进行测试
//将获取的返回值赋值给obj
varobj=uexDemoPlugin.doSyncCallback();
//查看obj的结构
showDetails(obj,obj.key1,obj.key2,obj.key3.subKey);控制台显示的结果如下

OC—>JavaScript同步返回值的转换规则NSObject
JSValue
NSString
String
@ YES,@ NO
Boolean
其他NSNumber
Number
NSArray
Array
NSDictionary
Object
nil,NSNull
null
block
Function(注)
注:返回block会被转化成JS中的function,但block中的代码如果需要继续与JS交互,可能会用到JavaScriptCore.framework这个系统库中的方法,这里就不做详细介绍了,您可以自行去研究。
3.3 插件UI操作
3.3.1 在网页上添加View
本小节介绍了插件如何在网页上添加原生的View
添加view的限制原生View总是会在网页顶端,即网页中所有
等网页元素上方在EUExDemoPlugin类中实现方法addView: removeView并在plugin.xml中声明addView方法有一个必选参数isScrollable 用来控制被添加的view是跟随网页滑动 还是固定在窗口上EUtility 中有2个方法brwView: addSubviewToScrollView:,brwView: addSubview:分别对应了上述2种情况
用一个实例变量aView来管理被添加的View
@property(nonatomic,strong)UIView*aView;-(void)addView:(NSMutableArray*)inArguments{
if(self.aView){
//如果已经添加了view 直接返回
return;
}
if([inArguments count]<1){
//参数不传 直接返回
return;
}
id info=[inArguments[0]JSONValue];
if(!info||![info isKindOfClass:[NSDictionaryclass]]){
//参数解析后不是NSDictionary 直接返回
return;
}
if(!info[@"isScrollable"]){
//如果参数信息不包含isScrollable这个键 直接返回
return;
}
BOOL isScroll=[info[@"isScrollable"]boolValue];
//新建一个view,并将其背景设置为红色
UIView*view=[[UIViewalloc]initWithFrame:CGRectMake(10,400,300,200)];
view.backgroundColor=[UIColorredColor];
if(isScroll){
[EUtilitybrwView:self.meBrwView addSubviewToScrollView:view];
}else{
[EUtilitybrwView:self.meBrwView addSubview:view];
}
//插件对象持有此view,方便对其进行移除操作
self.aView=view;
}
-(void)removeView:(NSMutableArray*)inArguments{
if(self.aView){
[self.aView removeFromSuperview];
self.aView=nil;
}
}在网页中我们设置一个单选框用于控制是否跟随网页滑动,相关HTML以及JS代码如下
isScrollable

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容,请联系我们,一经查实,本站将立刻删除。
如需转载请保留出处:https://51itzy.com/kjqy/17500.html