# API/组件自定义
# 1. 注册自定义API
如果小程序里需要调用一些宿主 App 提供的能力,而 FinClip 小程序 SDK 未实现或无法实现时,就可以通过注册自定义 API 来实现,使得小程序里也能够调用 App 中注册的 API 了。
注册自定义 API 分两个场景:
- 注册给原生小程序使用的自定义 API;
- 注册给小程序中 WebView 组件加载的 H5 使用的自定义 API。
# 1.1 注册小程序异步API
支持的app类型
小程序✅ 小游戏✅ H5应用✅
注册自定义的异步API的函数
/**
注册扩展Api
@param extApiName 扩展的api名称
@param handler 回调
@return 返回注册结果
*/
- (BOOL)registerExtensionApi:(NSString *)extApiName handler:(void (^)(FATAppletInfo *appletInfo, id param, FATExtensionApiCallback callback))handler;
自定义异步api(不涉及UI)
比如,我这里注册一个自定义finclipLogin
,以便小程序中可直接使用。
首先,App里集成SDK后,注册自定义的api:
[[FATClient sharedClient] registerExtensionApi:@"finclipLogin" handler:^(FATAppletInfo *appletInfo, id param, FATExtensionApiCallback callback) {
//param是入参
//返回成功和出参
callback(FATExtensionCodeSuccess, @{
@"token" : @"abcdefg",
@"name" : @"小明"
});
}];
然后,在小程序的根目录创建 FinClipConf.js
文件,配置示例如下:
module.exports = {
extApi:[
{ //普通交互API
name: 'finclipLogin', //扩展api名 该api必须Native方实现了
sync: false, //是否为同步api
params: { //扩展api 的参数格式,可以只列必须的属性
url: ''
}
}
]
}
注意:extApi 是个数组,所以,您可以注册多个自定义API。
小程序端更多自定义 API 配置信息可参考 ft.loadExtApi
最后,在小程序里调用自定义的API,示例代码:
ft.finclipLogin({
url:'https://www.baidu.com',
success: function (res) {
console.log("调用自定义api success");
console.log(res);
},
fail: function (res) {
console.log("调用自定义api fail");
console.log(res);
}
});
自定义异步api(打开原生页面)
比如,注册一个支付的自定义api(finclipPay
)。
[[FATClient sharedClient] registerExtensionApi:@"msPay" handler:^(FATAppletInfo *appletInfo, id param, FATExtensionApiCallback callback) {
// 1.获取小程序的支付参数
// 2.跳转至支付页面
UIViewController *topVC = [[UIApplication sharedApplication] fat_topViewController]; //sdk 提供的工具方法,获取顶层vc
FinTestViewController *payVC = [[FinTestViewController alloc] init];
payVC.param = param;
payVC.completion = ^(NSDictionary * _Nonnull result) {
callback(FATExtensionCodeSuccess, result);
};
[topVC presentViewController:payVC animated:YES completion:nil];
}];
在小程序
的根目录下创建FinClipConf.js
,内容示例如下:
module.exports = {
extApi:[
{ //登录API
name: 'finclipLogin', //扩展api名 该api必须Native方实现了
sync: false, //是否为同步api
params: { //扩展api 的参数格式,可以只列必须的属性
url: ''
}
},
{ //支付API
name: 'finclipPay', //扩展api名 该api必须Native方实现了
sync: false, //是否为同步api
params: { //扩展api 的参数格式,可以只列必须的属性
name: '',
price:'',
}
}
]
}
最后,在小程序里调用支付功能即可。
ft.msPay({
name:'赛罗-奥特曼',
price:26.2,
success: function (res) {
console.log("调用支付 success");
console.log(res);
},
fail: function (res) {
console.log("调用支付 fail");
console.log(res);
}
});
# 1.2 注册小程序同步API
支持的app类型
小程序✅ 小游戏✅ H5应用🚫
小程序里使用的API,既有异步API,也有同步API。从2.36.1开始,FinClip 小程序 SDK也支持注册自定义同步API了。
注册自定义的同步API的函数:
/**
注册同步扩展Api
@param syncExtApiName 扩展的api名称
@param handler 回调
@return 返回注册结果
*/
- (BOOL)registerSyncExtensionApi:(NSString *)syncExtApiName handler:(NSDictionary *(^)(FATAppletInfo *appletInfo, id param))handler;
比如,我这里注册一个同步的小程序API:
1).在初始化SDK之后,注册并实现同步的api。
[[FATClient sharedClient] registerSyncExtensionApi:@"finclipTestSync" handler:^NSDictionary *(FATAppletInfo *appletInfo, id param) {
NSLog(@"%p, param:%@", __func__, param);
NSDictionary *resultDict = @{
@"content":@"这是同步api返回的内容",
@"title":@"这是同步api返回的标题"
};
return resultDict;
}];
2).在小程序的根目录创建 FinClipConf.js
文件,并添加该同步 API
module.exports = {
extApi:[
{ //普通交互API
name: 'finclipLogin', //扩展api名 该api必须Native方实现了
sync: false, //是否为同步api
params: { //扩展api 的参数格式,可以只列必须的属性
url: ''
}
},
{
name: 'finclipTestSync',
sync: true, // 是否为同步api
params: {
name:'',
title:''
}
}
]
}
小程序端更多自定义 API 配置信息可参考 ft.loadExtApi
3).小程序里调用
const res = ft.finclipTestSync({'name':'张三', 'title':'Finclip'});
console.log(res.title);
注意: 自定义同步api的入参是字典,返回值也必须是字典类型,且内部不能包含无法json化的对象(比如view、自定义model)。 FinClipConf.js中的params声明的参数就必须得在调用的时候传递。比如我上面示例里声明了要有name和title两个参数,如果我使用
const res = ft.finclipTestSync({'name':'张三'})
、const res = ft.finclipTestSync({})
、const res = ft.finclipTestSync()
都会导致报错,无法将事件发送至原生。 所以FinClipConf.js中的params 最好是不加,或者声明为{}。
# 1.3 注册(含自定义scope的)自定义api
支持的app类型
小程序✅ 小游戏✅ H5应用🚫
当自定义api想要绑定一个自定义的弹框时,可以使用该api来注册。小程序里调用自定义api时,可以选择先展示弹框用户允许后,再执行自定义api的逻辑。
/**
注册权限异步扩展Api
注意:handler中异步返回的结果必须是可转json的字典。可用[NSJSONSerialization isValidJSONObject:xxxx]来判断
@param extApiName API名
@return 返回注册结果
*/
- (BOOL)registerScopeExtensionApi:(NSString *)extApiName handler:(void (^)(FATAppletInfo *appletInfo, id param, FATScopeChecker *scopeChecker,FATExtensionApiCallback callback))handler;
具体实现如下: 1).注册自定义scope的 自定义api。
[[FATClient sharedClient] registerScopeExtensionApi:@"finclipLogin" handler:^(FATAppletInfo *appletInfo, id param, FATScopeChecker *scopeChecker, FATExtensionApiCallback callback) {
}];
2).在自定义api实现里,控制是否要展示自定义的权限弹框
[[FATClient sharedClient] registerScopeExtensionApi:@"finclipLogin" handler:^(FATAppletInfo *appletInfo, id param, FATScopeChecker *scopeChecker, FATExtensionApiCallback callback) {
FATScopeCustomView *customView = [[[NSBundle mainBundle] loadNibNamed:@"FATScopeCustomView" owner:self options:nil] lastObject];
customView.frame = CGRectMake(0, 0, [UIScreen mainScreen].bounds.size.width, 300);
[scopeChecker checkScopeWithView:customView];
customView.scopeAuthCompleteHandler = ^(NSInteger result, NSArray<FATAppletScope *> * _Nonnull scopeArray) {
[scopeChecker dismissViewWithScopeList:scopeArray];
if (result == 0) {
// 拒绝
callback(FATExtensionCodeFailure, nil);
} else {
// 执行具体的业务逻辑
// 触发回调,可以是成功也可以是失败的回调
callback(FATExtensionCodeSuccess, nil);
}
};
}];
3).在小程序的根目录创建 FinClipConf.js
文件,并添加该自定义 API
module.exports = {
extApi:[
{ //普通交互API
name: 'finclipExtScope', //扩展api名 该api必须Native方实现了
sync: false, //是否为同步api
params: { //扩展api 的参数格式,可以只列必须的属性
url: ''
}
}
]
}
4).小程序中调用该自定义api
ft.finclipExtScope({
success: function (res) {
console.log("调用自定义api success");
console.log(res);
},
fail: function (res) {
console.log("调用自定义api fail");
console.log(res);
}
});
# 1.4 注册 JS API
支持的app类型
小程序✅ 小游戏🚫 H5应用🚫
小程序里可使用web-view组件加载H5,如果H5中也想调用宿主API的某个能力,就可以利用该方法注册一个API。
/// 为HTML 注册要调用的原生 api
/// @param webApiName 原生api名字
/// @param handler 回调
- (BOOL)fat_registerWebApi:(NSString *)webApiName handler:(void (^)(FATAppletInfo *appletInfo, id param, FATExtensionApiCallback callback))handler;
我这里为小程序里的H5注册了一个叫js2AppFunction
的方法,
[[FATClient sharedClient] fat_registerWebApi:@"js2AppFunction" handler:^(FATAppletInfo *appletInfo, id param, FATExtensionApiCallback callback) {
NSString *name = param[@"name"];
// id params = param[@"data"];
if ([name isEqualToString:@"getLocation"]) {
// 执行定位逻辑
// 返回结果给HTML
NSDictionary *dict = @{@"errno":@"403", @"errmsg":@"无权限", @"result": @{@"address":@"广东省深圳市南山区航天科技广场"}};
callback(FATExtensionCodeSuccess, dict);
} else if ([name isEqualToString:@"getColor"]) {
// 执行其他逻辑
// 返回结果给HTML
NSDictionary *dict = @{@"r":@"110",@"g":@"150",@"b":@"150"};
callback(FATExtensionCodeSuccess, dict);
}
}];
在H5内引用我们的桥接JSSDK文件,即可调用上面的注册的方法了。
HTML内调用注册的方法示例:
window.ft.miniProgram.callNativeAPI('js2AppFunction', {name:'getLocation'}, (result) => {
console.log(result)
});
# 2. 原生调用JS API
支持的app类型
小程序✅ 小游戏🚫 H5应用✅
同样的如果宿主App想要调用小程序加载的H5中的某个方法,就可以使用该API。
/**
原生调用HTML中的JS函数(前台运行的小程序)
@param eventName 函数名
@param paramString 函数的参数字典转成的json
@param pageId webView ID,可不传,默认调用最顶层页面里H5的函数
@param handler 调用结果回调:error code为FATErrorCodeAppletNotFound,未找到前台运行的小程序
*/
- (void)fat_callWebApi:(NSString *)eventName paramString:(NSString *)paramString pageId:(NSNumber *)pageId handler:(void (^)(id result, NSError *error))handler;
/**
原生调用HTML中的JS函数(appletId指定的小程序)
@param eventName 函数名
@param appletId 小程序id,指定调用的小程序
@param paramString 函数的参数字典转成的json
@param pageId webView ID,可不传,默认调用最顶层页面里H5的函数
@param handler 调用结果回调:error code为FATErrorCodeForegroundAppletNotFound,未找到appletId指定小程序
*/
- (void)fat_callWebApi:(NSString *)eventName applet:(NSString *)appletId paramString:(NSString *)paramString pageId:(NSNumber *)pageId handler:(void (^)(id result, NSError *error))handler;
首先,在H5内引用我们的桥接JSSDK文件。
然后,在HTML里注册好方法,比如方法名叫app2jsFunction
。
window.ft.onNativeAPIHandler('app2jsFunction', function(res) {
// app2jsFunction callback
})
最后,原生端调用如下API来调用HTML中的JS函数:
NSString *jsonParams = @""; //这里应该是参数字典转换成的json字符串。
NSNumber *pageId = @(1234); //这里是HTML中传过来的pageId
[[FATClient sharedClient] fat_callWebApi:@"app2jsFunction" paramString:jsonParams pageId:pageId handler:^(id result, NSError *error) {
}];
# 3. 注册原生组件
支持的app类型
小程序✅ 小游戏🚫 H5应用🚫
如果小程序中想使用app中的原生组件,我们可以通过注册原生组件的方式来实现。
而早期Camera、LivePlayer、LivePusher还未实现时,我们也支持外部注入这三个组件,我们已经写好了这三个组件的协议方法,您自定义组件只需要实现对应的协议方法。
注意:现在Camera组件已在SDK内实现,即使注册了自定义的Camera组件,也无效。
# 3.1 实现自定义的原生组件
首先,创建组件视图,实现其协议方法。
.h
#import <UIKit/UIKit.h>
#import <FinApplet/FATAppletNativeProtocol.h>
NS_ASSUME_NONNULL_BEGIN
@interface FATNativeView : UIView <FATAppletNativeViewProtocol>
@property (nonatomic, strong) NSNumber *nativeViewId;
@property (nonatomic, strong) NSString *type;
@end
@interface FATNativeCameraView : FATNativeView <FATAppletNativeCameraProtocol>
@end
@interface FATNativeLivePlayerView : FATNativeView <FATAppletNativeLivePlayerProtocol>
@end
@interface FATNativeLivePusherView : FATNativeView <FATAppletNativeLivePusherProtocol>
@end
NS_ASSUME_NONNULL_END
.m
@implementation FATNativeView
+ (UIView *)onCreateView:(NSDictionary *)param {
return [[self alloc] initWithParam:param];
}
- (instancetype)initWithParam:(NSDictionary *)param {
CGRect frame = CGRectZero;
NSDictionary *style = [param objectForKey:@"style"];
if (style) {
CGFloat x = [[style objectForKey:@"left"] floatValue];
CGFloat y = [[style objectForKey:@"top"] floatValue];
CGFloat height = [[style objectForKey:@"height"] floatValue];
CGFloat width = [[style objectForKey:@"width"] floatValue];
frame = CGRectMake(x, y, width, height);
}
self = [super initWithFrame:frame];
if (self) {
_type = param[@"type"];
_nativeViewId = param[@"nativeViewId"];
}
return self;
}
- (void)onUpdateView:(NSDictionary *)param {
NSDictionary *style = [param objectForKey:@"style"];
if (style) {
CGRect frame = CGRectZero;
CGFloat x = [[style objectForKey:@"left"] floatValue];
CGFloat y = [[style objectForKey:@"top"] floatValue];
CGFloat height = [[style objectForKey:@"height"] floatValue];
CGFloat width = [[style objectForKey:@"width"] floatValue];
frame = CGRectMake(x, y, width, height);
self.frame = frame;
}
}
- (void)onDestroyView:(NSDictionary *)param {
NSLog(@"销毁了%@",param);
}
@end
@implementation FATNativeCameraView
- (void)setCameraZoom:(NSDictionary *)param success:(FATNativeCallback)callBack {
}
@end
@implementation FATNativeLivePlayerView
@end
@implementation FATNativeLivePusherView
@end
然后,设置组件的视图class
[FATClient sharedClient].nativeViewManager.cameraClass = [FATNativeCameraView class];
[FATClient sharedClient].nativeViewManager.livePlayerClass = [FATNativeLivePlayerView class];
[FATClient sharedClient].nativeViewManager.livePusherClass = [FATNativeLivePusherView class];
[[FATClient sharedClient].nativeViewManager registerNativeViewType:@"native-view" nativeViewClass: [FATNativeView class]];
最后,小程序里使用对应的组件即可。
<camera mode="scanCode" resolution="medium" z-index="100" flash="{{flash}}" device-position="{{devicePosition}}"
bindinitdone="bindinitdone" bindstop="bindstop" binderror="binderror" bindscancode="bindscancode"
style="width: 50%; height: 300px;" frame-size="small">
</camera>
<live-pusher
hidden="{{hidden}}"
class="my-video"
id="pusher"
url="{{videoSrc}}"
mode="{{mode}}"
muted ="{{muted}}"
enable-camera = "{{enableCamera}}"
auto-focus = "{{autoFocus}}"
autopush="{{autopush}}"
orientation = "{{orientation}}"
beauty="{{beauty}}"
whiteness = "{{whiteness}}"
aspect = "{{aspect}}"
min-bitrate = "{{minBitrate}}"
max-bitrate = "{{maxBitrate}}"
audio-quality = "{{audioQuality}}"
waiting-image = "{{waitingImage}}"
waiting-image-hash = ''
zoom = "{{zoom}}"
device-position = "{{devicePosition}}"
background-mute
remote-mirror = "{{remoteMirror}}"
local-mirror = "{{localMirror}}"
audio-reverb-type = "{{audioReverbType}}"
enable-mic = "{{enableMic}}"
enable-agc = "{{enableAgc}}"
enable-ans = "{{enableAns}}"
audio-volume-type = "{{audioVolumeType}}"
video-width = "{{videoWidth}}"
video-height = "{{videoHeight}}"
beauty-style = "{{beautyStyle}}"
filter = "{{filter}}"
picture-in-picture-mode = "{{pictureInPictureMode}}"
bindstatechange="eventLog"
bindnetstatus= "eventLog"
binderror="eventLog"
bindbgmstart1 = "eventLog"
bindbgmprogress1 = "eventLog"
bindbgmcomplete = "eventLog"
bindaudiovolumenotify1 = "eventLog"
bindenterpictureinpicture1 = "eventLog"
bindleavepictureinpicture1 = "eventLog"
></live-pusher>
<live-player
hidden="{{hidden}}"
class="my-video"
id="player"
src="{{videoSrc}}"
mode="{{mode}}"
autoplay = "{{autoplay}}"
muted = "{{muted}}"
orientation = "{{orientation}}"
object-fit = "{{objectFit}}"
min-cache = "{{minCache}}"
max-cache = "{{maxCache}}"
sound-mode = "{{soundMode}}"
auto-pause-if-navigate = "{{autoPauseIfNavigate}}"
auto-pause-if-open-native = "{{autoPauseIfOpenNative}}"
picture-in-picture-mode = "{{pictureInPictureMode}}"
enable-auto-rotation = "{{enableAutoRotation}}"
bindstatechange="eventLog"
bindfullscreenchange = "eventLog"
bindnetstatus1 = "eventLog"
bindaudiovolumenotify1 = "eventLog"
bindenterpictureinpicture = "eventLog"
bindleavepictureinpicture = "eventLog"
binderror="eventLog"
></live-player>
<native-view type="native-view" style="left: 0px; top: 0px; width: 100px; height: 100px;">123</native-view>
# 3.2 原生给组件发送消息
宿主App给原生组件发送消息,是通过nativeViewManager来实现的。
/// 给nativeView 发送事件(前台运行的小程序)
/// @param eventName 事件名称
/// @param nativeViewId native-view id
/// @param detail 事件详细参数
/// @param completion 完成回调:error code为FATErrorCodeAppletNotFound,未找到前台运行的小程序
- (void)sendEvent:(NSString *)eventName nativeViewId:(NSNumber *)nativeViewId detail:(NSDictionary *)detail completion:(void (^)(id result, NSError *error))completion;
/// 给nativeView 发送事件(appletId指定的小程序)
/// @param eventName 事件名称
/// @param appletId 小程序的appId, 不能为空
/// @param nativeViewId native-view id
/// @param detail 事件详细参数
/// @param completion 完成回调:error code为FATErrorCodeForegroundAppletNotFound,未找到appletId指定小程序
- (void)sendEvent:(NSString *)eventName applet:(NSString *)appletId nativeViewId:(NSNumber *)nativeViewId detail:(NSDictionary *)detail completion:(void (^)(id result, NSError *error))completion;
示例代码
[[FATClient sharedClient].nativeViewManager sendEvent:@"eventName" nativeViewId:@(1234) detail:@{} completion:^(id result, FATError *error) {
}];
# 4. 原生给小程序发送全局消息
支持的app类型
小程序✅ 小游戏✅H5应用✅
可以通过以下api给小程序发送全局消息。
/// 发送 全局 事件(前台运行的小程序)
/// @param detail 事件详细参数
/// @param completion 完成回调:error code为FATErrorCodeAppletNotFound,未找到前台运行的小程序
- (void)sendCustomEventWithDetail:(NSDictionary *)detail completion:(void (^)(id result, NSError *error))completion;
/// 发送 全局 事件(appletId指定的小程序)
/// @param detail 事件详细参数
/// @param appletId 小程序的appId, 不能为空
/// @param completion 完成回调:error code为FATErrorCodeForegroundAppletNotFound,未找到appletId指定小程序
- (void)sendCustomEventWithDetail:(NSDictionary *)detail applet:(NSString *)appletId completion:(void (^)(id result, NSError *error))completion;
示例代码:
[[FATClient sharedClient].nativeViewManager sendCustomEventWithDetail:@{} completion:^(id result, NSError *error) {
}];