uni-app与iOS原生App的数据交互
uni-app开发过程中,难免需要原生代码去处理业务逻辑,如何与原生进行数据交互就变得尤为重要。怎么传递数据,怎么接收数据,我们才能更好处理这些逻辑?下面我们就介绍一些uni-app与iOS原生的一些数据处理方式。
一、使用uni原生插件
这种方式是uniapp比较推荐的一种方式。但是实现起来比较依赖原生知识。不过这一部分交给原生端开发就可以了。
插件类型:
Module模式:不需要参与页面布局,只需要通过 API 调用原生功能,比如:获取当前定位信息、数据请求等功能,通过扩展module的方式来实现;
Component模式:需要参与页面布局,比如:map、image等需要显示UI的功能,通过扩展component即组件的方法来实现;
vue页面中仅支持使用Module类型的原生插件,不支持调用同步方法返回数据
nvue页面中支持使用Module和Component两种类型的原生插件。也就是如需实现嵌入页面的ui组件,前提是该页面需要使用nvue编写。
1.Module模式:
通过宏 UNI_EXPORT_METHOD 将异步方法暴露给 js 端,只有通过UNI_EXPORT_METHOD暴露的原生方法才能被 js 端识别到。
通过宏 UNI_EXPORT_METHOD_SYNC 将同步方法暴露给 js 端。
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>
2.component模式:
复写 DCUniComponent
中的生命周期方法
(1)liadView方法
一个组件默认对应一个原生 view,如果未复写loadView方法提供自定义view,会默认调用基类方法返回一个继承于 UIView 的实例。
(2)viewDidLoad
如果需要对组件view做一些配置,比如设置delegate,在 viewDidLoad 生命周期方法中是一个比较好的时机
在 uni-app 中使用组件
注意:扩展的 component 只能在 nvue 文件中使用,不需要引入即可直接使用
在uni-app项目中新建nvue文件
<template>
<view>
<dc-testmap style="width:750rpx;height:300px"></dc-testmap>
</view>
</template>
自定义事件
<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>
在前端注册的mapLoaded方法,会在原生端调用addEvent:方法
如果需要原生端向前端发送事件,原生端在合适的时机调用fireEvent:params: domChanges:方法
自定义属性
给组建添加一个新的属性showTraffic
<template>
<div>
<dc-testmap style="width:750rpx;height:300px" showTraffic="true"></dc-testmap>
</div>
</template>
对应原生端的实现,覆盖组件方法 onCreateComponentWithRef... 给组件添加一个成员变量记录 showTraffic 属性的值,并在 viewDidLoad 方法中初始化
-(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"]];
}
}
onCreateComponentWithRef的方法是比viewDidLoad先进入的,所以可以先处理数据,然后在初始化方法viewDidLoad中进行一些操作
当前端更新属性时,会触发updateAttributes:方法,同步给地图控件
/// 前端更新属性回调方法
/// @param attributes 更新的属性
- (void)updateAttributes:(NSDictionary *)attributes {
// 解析属性
if (attributes[@"showsTraffic"]) {
_showsTraffic = [DCUniConvert BOOL: attributes[@"showsTraffic"]];
((MKMapView*)self.view).showsTraffic = _showsTraffic;
}
}
给组件添加方法
原生端实现
在组件代码中使用宏 UNI_EXPORT_METHOD 暴露原生方法供前端调用
@implementation TestMapComponent
// 通过 UNI_EXPORT_METHOD 将方法暴露给前端
UNI_EXPORT_METHOD(@selector(focus:))
// options 为前端传递的参数,支持 NSDictionary 或 NSString 类型
- (void)focus:(NSDictionary *)options {
NSLog(@"%@",options);
}
@end
在 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>
globalEvent 事件
在module 和 component中 用于页面监听持久性事件,例如定位信息,陀螺仪等的变化。
globalEvent事件只能通过页面的DCUniSDKInstance实例给当前页面发送globalEvent事件。其他页面无法接受。
示例:
页面监听event事件
var globalEvent = uni.requireNativePlugin('globalEvent');
globalEvent.addEventListener('myEvent', function(e) {
console.log('myEvent'+JSON.stringify(e));
});
在原生代码 发出myEvent事件
NSDictionary * dict = [NSDictionary dictionaryWithObjectsAndKeys:@"value",@"key",nil];
NSString * eventName = @"myEvent";
DCUniSDKInstance * instance = self.uniInstance;
[instance fireGlobalEvent:eventName params:params];
二、HTML5 plus api中的Native.js方法
这种方式的话,使用起来非常简单,我们只需要在app原生端创建一个类,使用这个类接手uni-app的调用方法即可。
在uni-app端根据native 的api去创建对应的原生类,然后使用原生类去调用原生方法,进行传参就能进行简单的数据交互。
uni-app端的代码实现:
let newVCobj = plus.ios.newObject('MessageHand');
plus.ios.invoke(newVCobj, 'jump:', userInfo);
原生端的代码实现
-(void)jump:(NSDictionary *)userInfo {
dispatch_async(dispatch_get_main_queue(), ^{
HomeViewController * homeVC = [[HomeViewController alloc]init];
homeVC.userInfo = userInfo;
AppDelegate * delegate=(AppDelegate*)[[UIApplication sharedApplication] delegate];
delegate.rootViewController.navigationBarHidden = NO;
[delegate.rootViewController pushViewController:homeVC animated:YES];
});
}
native.js如何获取原生的数据呢?这个问题就稍微复杂,从H5+ api中没有找到文档讲解,在网上查也没有查到好的办法,但是可以通过两种方式。
第一,uni-app端主动获取。我们可以在原生定一个getInfo的方法,让uni-app去主动获取。
let newVCobj = plus.ios.newObject('MessageHand');
plus.ios.invoke(newVCobj, 'getInfo');
原生端的代码实现
-(NSString*)getInfo {
return @“”;
}
第二种方式的话,通过回调方法去拿到数据。
let messageHand = plus.ios.newObject('MessageHand');
let delegate = plus.ios.invoke(messageHand,'getInfoCallBack:', (e)=>{
// 可以拿到事件,但是拿不到e数据值
});
原生端的代码实现
-(NSString*) getInfoCallBack:(void(^)(NSString * _Nonnull timer))handleBlock{
handleBlock(@"nihao");
return @"wohenhao";
}
但是第二种方式有一个问题就是,如果在回调方法中我是拿不到预期的结果值,那又如何传递给uni-app端数据。