FinClip为企业提供小程序生态圈技术产品,开发者可在FinClip小程序开发帮助中心找到相关FinClip小程序指引

# API/组件自定义

# 1. 注册自定义API

如果小程序里需要调用一些宿主 App 提供的能力,而 FinClip 小程序 SDK 未实现或无法实现时,就可以通过注册自定义 API 来实现,使得小程序里也能够调用 App 中注册的 API 了。

注册自定义 API 分两个场景:

  1. 注册给原生小程序使用的自定义 API;
  2. 注册给小程序中 WebView 组件加载的 H5 使用的自定义 API。

# 1.1 注册小程序异步API

注册自定义的异步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

小程序里使用的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

当自定义api想要绑定一个自定义的弹框时,可以使用该api来注册。

注册自定义api时,绑定自定义scope对象。当小程序里调用自定义api时,可以选择先展示弹框用户允许后,再执行自定义api的逻辑。

/**
 注册自定义scope的异步扩展Api
 注意:handler中异步返回的结果必须是可转json的字典。可用[NSJSONSerialization isValidJSONObject:xxxx]来判断
 
 @param extApiName 扩展的api名称
 @param scope 设置的FATAppletScope列表
 @param handler 回调
 @return 返回注册结果
 */
- (BOOL)registerScopeExtensionApi:(NSString *)extApiName BindScopes:(NSArray <FATAppletScope *>*)scope handler:(void (^)(FATAppletInfo *appletInfo, id param, FATScopeChecker *scopeChecker,FATExtensionApiCallback callback))handler;

具体实现如下: 1).注册绑定自定义scope的 自定义api。

// 创建自定义scope对象
FATAppletScope *scope = [[FATAppletScope alloc] init];
scope.scope = @"finclipLoginE";
scope.title = @"自定义finclipLoginE的title";
scope.desc = @"自定义finclipLoginE的desc";
scope.scopeName = @"自定义finclipLoginE的scopeName";
// 注册自定义scope的自定义api,并绑定scope
[[FATClient sharedClient] registerScopeExtensionApi:@"finclipExtScope" BindScopes:@[scope] handler:^(FATAppletInfo *appletInfo, id param, FATScopeChecker *scopeChecker, FATExtensionApiCallback callback) {
}];

2).在自定义api实现里,控制是否要展示自定义的权限弹框

[[FATClient sharedClient] registerScopeExtensionApi:@"finclipExtScope" BindScopes:@[scope] handler:^(FATAppletInfo *appletInfo, id param, FATScopeChecker *scopeChecker, FATExtensionApiCallback callback) {
    // 通过FATScopeChecker对象在调用api前控制是否弹出小程序的权限弹窗
    [scopeChecker checkScopeWithCallBack:^(BOOL status, NSString * _Nonnull errMsg) {
        if (!status) {
            callback(FATExtensionCodeFailure, @{@"errMsg" : errMsg});
            return; 
        }
        
        // 执行自定义api的具体逻辑
        
        // 返回结果给小程序
        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

小程序里可使用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中的某个方法,就可以使用该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.registNativeAPIHandler('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中的原生组件,我们可以通过注册原生组件的方式来实现。

而早期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. 原生给小程序发送全局消息

可以通过以下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) {    
}];
© FinClip with ❤ , Since 2017