# API/组件自定义

# 1. 注册自定义API

如果小程序里需要调用一些宿主 App 提供的能力,而 “人民网+”小程序 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;

比如,我这里注册一个自定义Login,以便小程序中可直接使用。

首先,App里集成SDK后,注册自定义的api:

[[FATClient sharedClient] registerExtensionApi:@"Login" handler:^(FATAppletInfo *appletInfo, id param, FATExtensionApiCallback callback) {
    // xxxx
    callback(FATExtensionCodeSuccess, nil);
}];

然后,在小程序的根目录创建 FinClipConf.js 文件,配置示例如下:

module.exports = {
  extApi:[
    { //普通交互API
      name: 'Login', //扩展api名 该api必须Native方实现了
      sync: false, //是否为同步api
      params: { //扩展api 的参数格式,可以只列必须的属性
        url: ''
      }
    }
  ]
}

注意:extApi 是个数组,所以,您可以注册多个自定义API。

最后,在小程序里调用自定义的API,示例代码:

ft.Login({
    url:'https://www.baidu.com',
    success: function (res) {
        console.log("调用customEvent success");
        console.log(res);
    },
    fail: function (res) {
        console.log("调用customEvent fail");
        console.log(res);
    }
});

# 1.2 注册小程序同步API

小程序里使用的API,既有异步API,也有同步API。从2.36.1开始,“人民网+”小程序 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: 'Login', //扩展api名 该api必须Native方实现了
      sync: false, //是否为同步api
      params: { //扩展api 的参数格式,可以只列必须的属性
        url: ''
      }
    },
    {
        name: 'TestSync',
        sync: true, // 是否为同步api
        params: {
            name:'',
            title:''
        }
    }
  ]
}

3).小程序里调用

const res = ft.TestSync({'name':'张三', 'title':'“人民网+”'});
console.log(res.title);

注意: 自定义同步api的入参是字典,返回值也必须是字典类型,且内部不能包含无法json化的对象(比如view、自定义model)。 FinClipConf.js中的params声明的参数就必须得在调用的时候传递。比如我上面示例里声明了要有name和title两个参数,如果我使用const res = ft.TestSync({'name':'张三'})const res = ft.TestSync({})const res = ft.TestSync()都会导致报错,无法将事件发送至原生。 所以FinClipConf.js中的params 最好是不加,或者声明为{}。

# 1.3 注册 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.miniProgram.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. 注册原生组件

由于资源有限,livePusher 和livePlayer等原生组件的实现可能需要借助外部的第三方控件,这时候就可以注册原生组件。我们现在支持注册的原生组件有三个:Camera、LivePlayer、LivePusher。

# 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];

# 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) {
            
}];

# 3.3 原生给小程序发送全局消息

/// 发送 全局 事件(前台运行的小程序)
/// @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) {    
}];