uni-app兼容模式组件(原“组件插件”)开发
内容是uni-app兼容模式组件(原“组件插件”)开发遇到的一些问题和解决方案
插件中emit的使用
注意:
- 不能把UTSJSONObject当成参数
- 参数不能是自定义类型
- emit不要驼峰命名(前期的hbuilder x版本不支持,后期不确定)
- 参数只能是map或map[](map数组),并且map中的value只能是map、string、
number(类型,Json、非map的array类型的value无法正确获取到内容✅未仔细验证)
参考官方内容
比如:
let lastResult = new Map<string, any>();
const listJsonArr = [{ "name": "0" }, {"name": "1" }];
lastResult.set("detailJsonArr",listJsonArr);//❌响应事件中,拿不到数据
lastResult.set("detailJson", { "name":"xxxx" });//❌响应事件中,拿不到数据
lastResult.set("detailStr",JSON.stringify(listJsonArr));//✅ string正常使用
let testMap = new Map<string, any>();
testMap.set("name", "testMapName")
lastResult.set("detailMap", testMap);//✅ Map正常使用
lastResult.set("detailMapArr",[testMap]);//✅ Map[](Map数组)正常使用
this.$emit('suggestions', lastResult)
结果如下图:
报错:does not conform to protocol
插件中不支持函数中的参数带有默认值,如下图,会报错
1、使用时插件无法出现,内容显示不出来的原因
- 再uniapp x中使用时,必须给这个插件高度和宽度,否则出不来!
///uniapp x 中使用
<uts-hello-view buttonText="点击按钮内容" style="width:375px;height: 375px;background-color: aqua;"></uts-hello-view>
- 在IOS中编写这个插件的内容时候,不能全用self.bounds,有可能出不来啊!!!!需要直接写死frame,或者使用约束;
- 同时,在didMoveToSuperview中获取view的frame不行,都是0,需要在layoutSubviews方法中获取view的frame才可以
- 每次添加新的自定义UTS组件后最好重新走一遍自定义基座
2、无法运行
- 检查是否都已经Podfile文件中版本修改为12
- 问题:Sandbox: bash(72928) deny(1) file-write-create
方法:build settings->User Script Sandboxing 修改为NO
3、使用第三方库时,不能在.h文件中导入这个内容第三方库 !!!
4、swift 如果类添加了public前缀,那么无法添加协议MAMapViewDelegate,会报错!!!
5、如何在uts插件中调用swift的方法
首先,swift必须是public 或者open修饰,比如 public class NHT_DemoView
但是高德地图又不能用这个修饰,所以将高德地图添加到一个view上,然后将这个view添加到另一个通过
public修饰的类上,通过这个类调用高德地图的api;
6、如何在UTS插件中使用swift的闭包
- 使用
swift:
@objc open var SelectLocationChangeBlock: (([String:Any])->Void)?
UTS插件:
NVLoad() : NHT_DemoView {
// -----NHT_AMAPView
let button = new NHT_DemoView()
button.SelectLocationChangeBlock = (res : any) => {
this.$emit('fetchgis', res)
};
return button
},
- 原生中有些方法的闭包参数是逃逸闭包,此时就要在闭包前面添加 @escaping 标记:
// 在闭包参数前添加@escaping
**在.ust文件中定义使用**
let blockMine : ((res : string) => void) | null = null;
// 逃逸闭包使用,在闭包参数前添加@escaping
export function testVariableBlock(@escaping completion : (res : string) => void) {
// 在闭包参数前添加@escaping
blockMine = completion
setTimeout(function () {
blockMine?.("xxxx111")
}, 5000);
};
7、 this.$emit传值报错,error: cannot convert value of type ‘String’ to expected argument type ‘[String : Any]?’
因为传值类型不对,可以自己声明一个变量,然后当参数传值
const map1 = new Map<string,any>()
map1.set('a', 'alpha');
或者改变响应回调的写法(必须按照你实际的返回内容),
button.SelectLocationChangeBlock = (res : Map<string, any>) => {
this.$emit('fetchgis', res)
};
自定义type不能传,在UTS插件中通过emit当成参数不能在使用的地方正确拿到数据
比如:
export type SuggestionItem = {
name ?: string,
address ?: string,
adcode ?: string,
latitude ?: string,
longitude ?: string,
};
let subSug : SuggestionItem = {}
subSug.name = "name"
subSug.address = "adress"
this.$emit('test', [subSug])//不行,在使用的地方拿不到正确的数据
this.$emit('test', { 'info': subSug })//不行,插件中不能直接传UTSJSONObject
// 可行的
let subJson = {} as UTSJSONObject
subJson["name"] = "name"
subJson["address"] = "adress"
let lastResult = new Map<string, any>();
lastResult.set("detail", subJson);
this.$emit('test', lastResult)//可行,能正常收到内容
8、error: uni_modules/nhyt-texs/utssdk/app-ios/src/index.swift:61:9: error: contextual closure type ‘(Any, Any) -> Void’ expects 2 arguments, but 1 was used in closure body
可能是watch中出现的问题
比如监听下面的值
“enableLocation”: {//允许定位
type: Boolean,
default: false
},
watch: {
//这样写对,必须有newValue : boolean, oldVel : boolean,只有newValue会报这个错误
“enableLocation”: {
handler(newValue : boolean, oldVel : boolean) {
// if (!this.isInitLocation && newValue && !this.innerEnableLocation) {
// this.isInitLocation = true
// this.innerEnableLocation = newValue
// this.initLocation()
// }
},
immediate: false
},
}
10、插件中使用通知
目前的问题是无法获取通知发过来的信息内容
插件.uts文件中使用通知
import { NotificationCenter, NSNotification, Notification } from 'Foundation';
import { UIApplication } from "UIKit"
// 响应通知方法
class NotificationListener {
// 按钮点击回调方法
@objc notificationAction() {
console.log("通知")
}
}
let listsner = new NotificationListener()
// 启动监听推送消息事件
export function onPushMessage() {
const method = Selector("notificationAction")
NotificationCenter.default.addObserver(listsner, selector = method, name = NSNotification.Name('testNNNN'), object = null)
NotificationCenter.default.addObserver(listsner, selector = method, name = UIApplication.userDidTakeScreenshotNotification, object = null)
setTimeout(function () {
NotificationCenter.default.post(name = NSNotification.Name(rawValue = 'testNNNN'), object = { "2": "xxx32" } as any)
offPushMessage()
}, 3000);
};
11、插件中iOS推送功使用
前提是,你的app已经在苹果开发者中心配置了推送证书
- 配置权限描述:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>UIBackgroundModes</key>
<array>
<string>remote-notification</string>
</array>
<key>NSUserNotificationsUsageDescription</key>
<string>打开XXX通知权限</string>
</dict>
</plist>
- UTS.entitlements
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>aps-environment</key>
<string>development</string>
</dict>
</plist>
- 接收远程推送消息的方法
applicationDidReceiveRemoteNotificationCompletionHandler(application : UIApplication, userInfo : Map<AnyHashable, any>, completionHandler : ((res : Int) => void)) {}
export class MyPluginClass implements UTSiOSHookProxy {
// uts 插件创建时的回调。
onCreate() {
}
// 应用正常启动时 (不包括已在后台转到前台的情况)的回调函数。
applicationDidFinishLaunchingWithOptions(application : UIApplication | null, launchOptions : Map<UIApplication.LaunchOptionsKey, any> | null = null) : boolean {
nowApplication = application
// console.log("===============applicationDidFinishLaunchingWithOptions")
return true
}
// 当应用程序接收到与用户活动相关的数据时调用此方法,例如,当用户使用 Universal Link 唤起应用时。
applicationContinueUserActivityRestorationHandler(application : UIApplication | null, userActivity : NSUserActivity | null, restorationHandler : ((res : [any] | null) => void) | null = null) : boolean {
console.log("===============applicationContinueUserActivityRestorationHandler")
return true
}
// 远程通知注册成功时的回调函数。(打自定义基座时需要勾选 push 模块)
didRegisterForRemoteNotifications(deviceToken : Data | null) {
console.log("===============didRegisterForRemoteNotifications:", deviceToken)
const token = PushObject.share().getHexString(for = deviceToken!)
console.log("token----", token);
NHTPushService.shareNHTPush().application(nowApplication!, didRegisterForRemoteNotificationsWithDeviceToken = deviceToken!);
}
// 远程通知注册失败时的回调函数。(打自定义基座时需要勾选 push 模块)
didFailToRegisterForRemoteNotifications(error : NSError | null) {
console.log("===============didFailToRegisterForRemoteNotifications:", error)
NHTPushService.shareNHTPush().application(nowApplication!, didFailToRegisterForRemoteNotificationsWithError = error!);
}
/* 接收到远程推送消息
如果想在后台的时候收到远程推送的时候能调用这个方法
那么推送时后台必须加上参数
"content-available":1 */
applicationDidReceiveRemoteNotificationCompletionHandler(application : UIApplication, userInfo : Map<AnyHashable, any>, completionHandler : ((res : Int) => void)) {
NHTPushService.shareNHTPush().application(application, didReceiveRemoteNotification = userInfo, fetchCompletionHandler = (res : UIBackgroundFetchResult) => {
completionHandler(0);
});
}
// 通过 url scheme 方式唤起 app 时的回调函数。(iOS9 之前的系统回调此方法,iOS9 之后的系统请使用 applicationOpenURLOptions)
applicationHandleOpenURL(application : UIApplication | null, url : URL | null) : boolean {
console.log("===============applicationDidFinishLaunchingWithOptions")
return true
}
// 通过 url scheme 方式唤起 app 时的回调函数。
applicationOpenURLOptions(app : UIApplication | null, url : URL, options : Map<UIApplication.OpenURLOptionsKey, any> | null = null) : boolean {
console.log("===============applicationDidFinishLaunchingWithOptions")
return true
}
// 当应用从活动状态主动变为非活动状态的时的回调函数。
applicationWillResignActive(application : UIApplication | null) {
// console.log("===============applicationWillResignActive")
}
// 应用完全激活时的回调函数。
applicationDidBecomeActive(application : UIApplication | null) {
// console.log("===============applicationDidBecomeActive")
}
// 应用程序进入后台时的回调函数。
applicationDidEnterBackground(application : UIApplication | null) {
console.log("===============did enter background")
}
// 当应用在后台状态,将要进入到前台运行时的回调函数。
applicationWillEnterForeground(application : UIApplication | null) {
console.log("===============applicationWillEnterForeground")
}
// 应用程序的 main 函数。
applicationMain(argc : Int32, argv : UnsafeMutablePointer<UnsafeMutablePointer<CChar> | null>) {
console.log("applicationMain")
}
}
12、常见几种iOS原生函数在uniapp x中的使用示例
UTS中使用时的注意点:
- 参数之间用=连接
- OC写的方法,在uniapp x中使用样式和在swift中类似,可以参考
//oc中的方法
-(void)checkIsHavePower_Microphone:(void (^)(NSInteger status))allowBlock;
//UTS文件中使用
export function checkIsHavePower_Microphone(callBack : (res : number) => void) {
NHTFunction.share().checkIsHavePower_Microphone((state : Int) => {
callBack(Number(state))
});
};
//oc中的方法
-(void)startChangeTextToSpeech:(NSString *)text repeatPlay:(BOOL)repeatPlay;
//UTS文件中使用
export function startChangeText(speechText : string, repeatPlay : boolean) {
NHTFunction.share().startChangeText(toSpeech = speechText, repeatPlay = repeatPlay);
};
13、类型转换
- swift 中的 Dictionary 类型,在 uts 中使用 Map 类型代替
let resultInfo = res as Map<string, any | null>
- Int转number
Number(res)
14.部分原生数据类型问题
部分类型(比如:CBCharacteristic),不能单独从数据中取出并使用,会直接崩溃,console也不行
export function notifyBLECharacteristicValueChange(options : NotifyBLECharacteristicValue) {
NHT_Bluetooth.share().notifyBLECharacteristicValueChange((res : any) => {
var resultInfo = res as Map<string, any | null>
console.log("----订阅特征值--notifyBLECharacteristicValueChange", res);//这个可以
const characteristic = resultInfo.get('characteristic') ?? "xxx"; //CBCharacteristic
if (characteristic != null) {
//不能打印characteristic(类型是CBCharacteristic),也不能使用,会崩溃
console.log("----订阅特征值--notifyBLECharacteristicValueChange",characteristic);
}
options.success(characteristic)//这样也会崩溃
});
}
15、调用原生方法时,不同参数类型的处理方法
- [String:Any]
原生参数:[String:Any]
UTS插件中传的参数:Map<string,any>
比如:@objc open func addLine(options:[String:Any]){ }
调用:let params = new Map<string,any>()
this.$el.addLine(options = params)
- [Float]
原生参数:[Float]
比如:@objc open func addCircle(strokeColor:[Float]){ }
调用:
const fillColorLast = [
fillColor.red.toFloat(),
fillColor.green.toFloat(),
fillColor.blue.toFloat(),
fillColor.alpha.toFloat()
];
this.$el.addCircle( strokeColor = strokeColorLast)