Touchpoint 移动 SDK
Touchpoint 移动 SDK 的目的是在移动应用和 Alida Touchpoint 之间创建集成。这种集成使得能够发布和管理在移动应用中显示的 Touchpoint 活动,而无需发布新版本的移动应用或进行任何代码更改。
有关 Touchpoint 的一些重要概念可以在这里找到。以下简要描述了其中一些概念,但上面的链接提供了更详细的描述。
最低要求
- iOS 版本 10.0
示例应用
https://github.com/vcilabs/touchpointkit-sample-ios
使用Pods进行安装
请将以下内容添加到您的Podfile
中。请查阅此处以确定最新标签。
pod 'TouchPointKit', :git => 'https://github.com/vcilabs/touchpoint-kit-ios.git', :tag => '1.0.3'
然后运行pod install
使用SPM进行安装
在XCode包管理器内,添加以下URL:https://github.com/vcilabs/touchpoint-kit-ios
。请查阅此处以确定最新标签。
概念
SDK的核心设计目标是只需让移动应用开发者提供一次 efforts起始 efforts来使移动应用的各个屏幕“启用Touchpoint”,并消除与切换显示特定活动相关的任何持续 efforts。Touchpoint管理员可以在无需开发工作的情况下切换Touchpoint活动。
由于这种解耦,本SDK中永远不会有对特定活动的引用。所有活动的配置和分发都由Touchpoint管理员在Touchpoint用户界面内完成。
- 触发器:触发器是激活并显示给用户Touchpoint活动的方式。SDK支持三种不同类型的触发器。
- 横幅:当用户导航到特定屏幕后显示横幅。在屏幕底部显示了一个带有调用行动的UI组件。点击横幅将显示活动。
- 弹窗:在用户导航到特定屏幕后显示弹窗(可选延迟后)。弹窗没有UI组件,Touchpoint活动直接显示。
- 自定义组件:自定义组件是在任意生命周期事件期间触发Touchpoint活动的方式,并且完全在开发者的控制之下。活动可以由按钮点击、用户第二次访问屏幕或其他任何自定义逻辑触发。
- 屏幕:屏幕是您移动应用中的一个页面或视图,例如主屏、商品列表屏幕、商品详情屏幕、设置屏幕等。您可以将移动应用中的任何屏幕指定为可以显示触点活动。
- 组件:组件是您移动应用屏幕上的某些UI元素。这可能像按钮这样的东西,也可能像用户访问特定屏幕的次数这样的更抽象的东西。
- 定位:Touchpoint管理员可以根据当前应用程序用户的某些用户属性对特定的Touchpoint活动进行定位。当前用户的用户属性可以在移动应用程序中定义,然后由Touchpoint用于定位。
横幅和弹出式触发器的类型是触发活动的开箱即用的方法。这样做是为了使集成更加容易,但牺牲了灵活性。自定义组件需要一些自定义逻辑来触发,但它提供了灵活性,因为逻辑完全受应用程序开发者的控制。
用户完成活动后,活动将被关闭,用户将保持在同一屏幕上,状态不变。
整合一切
作为移动应用程序的开发者,您可以指定哪些屏幕以及哪些屏幕组件能够触发Touchpoint活动。您不需要担心哪些特定的Touchpoint活动被分配给特定的屏幕,因为这由Touchpoint管理员控制。
如果需要在特定屏幕上有横幅或弹出式活动,您只需定义和提供屏幕名称。如果您使用自定义组件,您需要提供屏幕名称和组件名称。例如
let screenComponents = [
[ "screenName": "Home" ],
[ "screenName": "Settings", "componentName": "Lightbulb" ],
[ "screenName": "ProductList" ],
]
在这里,我们定义了Home
屏幕和ProductList
屏幕能够显示横幅和弹出式触发器的类型。然后在我们的Settings
屏幕上,我们有一个命名为Lightbulb
的按钮,现在能够显示自定义组件触发器。屏幕可以支持多个组件。
实现
初始设置
在AppDelegate类中,使用import TouchPointKit
导入SDK。然后,在didFinishLaunchingWithOptions
函数中添加以下初始化代码。
// API key and secret are provided by the Touchpoint UI
let apiKey = API_KEY
let apiSecret = API_SECRET
// The pod is the geographical region hosting your instance of Touchpoint.
// Easiest determined from your URL while logged in, e.g. eu2.alida.com
// Valid values are: na1, na2, eu1, eu2, ap2, ap3
let podName = TouchPointPods.eu2
// These are the Screens and Screen Components in your mobile app that you
// designate as being able to render Touchpoint activities.
let screenComponents = [
[ "screenName": "Home" ],
[ "screenName": "Settings", "componentName": "Lightbulb" ],
[ "screenName": "ProductList" ],
]
// The visitor payload describes the current user of the app. The "id"
// is used to help determine if this particular user has already
// seen certain activities and should be a unique identifier.
// "userAttributes" are the targeting parameters. "type" is the data type
// found in the "value". The data type is required as Touchpoint has
// various operators that make sense for certain data types and not
// others, such as "greater than" or "less than" for numbers.
// Valid values are number, boolean, string and date.
let visitor = [
"id": "12345",
"userAttributes": [ // These are used in targeting
[
"key": "age",
"type": "number",
"value": "53"
],
[
"key": "isLoyaltyMember",
"type": "boolean",
"value": "true"
],
[
"key": "city",
"type": "string",
"value": "Springfield"
],
[
"key": "previousVisitDate",
"type": "date",
"value": "2022-04-11T21:51:34+0000"
]
]
] as [String : Any]
TouchPointActivity.shared.configure(
apiKey: apiKey,
apiSecret: apiSecret,
podName: podName,
screenComponents: screenComponents,
visitor: visitor)
// The following are optional properties for developer convenience
// Should be "false" in production and is "false" by default.
// Touchpoint generally won't show an activity to the same user twice which
// can make it tricky to test. Setting this to "true" makes it
// possible for a user to be served the same activity more than once.
TouchPointActivity.shared.disableAPIFilter = false
// To disable wkwebview caching, set to "true". "false" by default.
TouchPointActivity.shared.disableCaching = false
// Logs at debug level are silenced by default. To enable debug logs,
// set this to "true". "false" by default.
TouchPointActivity.shared.enableDebugLogs = true
// Prevents any logs being generated, default is "false".
TouchPointActivity.shared.disableAllLogs = false
// If status bar style is dark, set this property to "false". "true" by
// default.
TouchPointActivity.shared.isStatusBarStyleLight = false
// The height of the banner UI element in pixels. Default is "70".
TouchPointActivity.shared.defaultBannerHeight = 50
注意
请勿使用访客属性将个人信息(PII)传递到Touchpoint。为了保护访客隐私,请勿传递可能被视为个人信息(PII)的数据到Touchpoint。PII包括但不限于社会安全号码、个人家庭地址、信用卡号码、金融账户号码、街道地址等信息。
触发横幅和弹出窗口
对于横幅和弹出窗口触发器,您只需要告诉SDK当前是哪个屏幕可见。要做到这一点,请使用以下方法在View
或ViewController
中导入SDK:使用import TouchPointKit
,并在下面设置屏幕,最好在viewDidLoad
函数中
override func viewDidLoad() {
super.viewDidLoad()
TouchPointActivity.shared.setScreenName(screenName: SCREEN_NAME)
}
分配给指定屏幕的任何横幅或弹出窗口都将被触发并自动显示。
由于弹出窗口可以由Touchpoint管理员配置,仅在指定时间过后触发,因此用户可能会在弹出窗口显示前快速切换到其他屏幕。这时,弹出窗口将在第二个屏幕上显示,这可能是我们不希望的。为了防止这种情况,可以在删除第一个屏幕时取消弹出窗口。
TouchPointActivity.shared.cancelPopupForScreen(screenName: SCREEN_NAME)
触发自定义组件
对于自定义组件的触发,您可以将钩子钩入任何生命周期事件并直接调用Touchpoint活动。
TouchPointActivity.shared.openActivityForScreenComponent(screenName: SCREEN_NAME, componentName: COMPONENT_NAME, delegate: self)
在调用openActivityForScreenComponent
函数之前,可以使用以下方法检查是否需要显示Touchpoint活动:
if TouchPointActivity.shared.shouldShowActivity(screenName: SCREEN_NAME, componentName: COMPONENT_NAME) {
// Call openActivityForScreenComponent
}
这可以帮助您管理如何渲染屏幕,例如根据Touchpoint活动是否可用来隐藏或显示按钮。
刷新活动列表
在每次调用TouchPointActivity.shared.configure
时,SDK将向Touchpoint请求获取当前用户的有效活动列表并将其缓存。通常此函数在应用加载时运行,之后不再调用,这可能会导致活动列表过时。有一个辅助函数可以为您刷新活动列表。
TouchPointActivity.shared.refreshActivities()
这可以在调用TouchPointActivity.shared.configure
之后的任何时候调用,并从Touchpoint获取新的活动列表。
注意:通常在一个Touchpoint活动中,只有当在应用的启动过程中调用了一次TouchPointActivity.shared.configure
时,才会向应用分发一个活动,在同一个“会话”中每组活动只会显示一个活动。使用TouchPointActivity.shared.refreshActivities
可能导致在相同用户会话中显示活动的下一个活动。
回调事件
Touchpoint SDK提供了两种不同的回调,可以在用户与活动交互时触发自定义逻辑。
- 收起:当用户关闭或收起活动时。在这种情况下,活动刚刚从视图中移除,并开始与用户重逢。
- 完成:当用户完成活动中的最后一个问题,活动被视为完成时。在此之后,该活动被视为在Touchpoint中的“完成”,用户仍在查看活动。
为了实现这些回调,您的类应继承自 TouchPointActivityDelegate
并实现两个函数 onTouchPointActivityCollapse
和 onTouchPointActivityComplete
。请注意,onTouchPointActivityComplete
是可选的。
示例
class CustomComponentViewController: UIViewController, TouchPointActivityDelegate {
// ...
func onTouchPointActivityComplete() {
let activityCompleteAlert: UIAlertView = UIAlertView(title: "Thanks for completing the activity!", message: "We really appreciate your feedback",
delegate: self, cancelButtonTitle: "OK")
activityCompleteAlert.show()
}
func onTouchPointActivityCollapse() {
curSelectedButton?.isEnabled = false
}
// ...
}
React Native 的 iOS 集成
在您的 React Native 项目中会有一个名为 ios
的文件夹。在这个文件夹中,将包含您应用的 iOS 原生部分。要将此 SDK 安装到您的应用中,请使用上述描述的方法之一,通过 Podfile 或 SPM 进行安装
在 AppDelegate.m
文件中,通过在文件顶部添加一行导入语句来导入 SDK:@import TouchPointKit;
并在 AppDelegate.m
的 didFinishLaunchingWithOptions
函数中添加以下代码片段。
请参见上面的 初始设置 部分,以了解如何使用下面代码片段中的各种参数及其用法。
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
//...
NSString *apiKey = @"API_KEY";
NSString *apiSecret = @"API_SECRET";
// Possible values: TouchPointPodsNA1, TouchPointPodsNA2, TouchPointPodsEU1,
// TouchPointPodsEU2, TouchPointPodsAP2, TouchPointPodsAP3
TouchPointPods pod = TouchPointPodsEU2;
// Example user attributes used for targeting, see the integration details
// above for more details.
NSArray *userAttributes = @[
@{ @"key": @"age", @"type": @"number", @"value": @"53" },
@{ @"key": @"isLoyaltyMember", @"type": @"boolean", @"value": @"true" },
@{ @"key": @"city", @"type": @"string", @"value": @"Springfield" },
@{ @"key": @"previousVisitDate", @"type": @"date", @"value": @"2022-04-11T21:51:34+0000" },
];
NSDictionary *visitor = [[NSDictionary alloc] initWithObjectsAndKeys:@"12345", @"id", userAttributes, @"userAttributes", nil];
// Example screens and components
NSArray *screenComponents = @[
@{ @"screenName": @"Banner Screen" },
@{ @"screenName": @"Popup Screen" },
@{ @"screenName": @"Custom Component Screen", @"componentName": @"Button 1" },
@{ @"screenName": @"Custom Component Screen", @"componentName": @"Button 2" },
@{ @"screenName": @"Custom Component Screen", @"componentName": @"Button 3" },
];
[[TouchPointActivity shared] configureWithApiKey: apiKey apiSecret: apiSecret podName: pod screenComponents: screenComponents visitor: visitor];
// Optional configuration elements, see integration details above for more details
[TouchPointActivity shared].disableAPIFilter = false;
[TouchPointActivity shared].disableCaching = false;
[TouchPointActivity shared].enableDebugLogs = true;
[TouchPointActivity shared].disableAllLogs = false;
[TouchPointActivity shared].isStatusBarStyleLight = false;
[TouchPointActivity shared].defaultBannerHeight = 50;
//...
}
现在,在您的 iOS 项目中创建两个文件,分别命名为 TouchPointKitBridge.h
和 TouchPointKitBridge.m
。将以下代码添加到这些文件中。
// TouchPointKitBridge.h
#import <Foundation/Foundation.h>
#import <React/RCTBridgeModule.h>
#import <React/RCTEventEmitter.h>
@import TouchPointKit;
NS_ASSUME_NONNULL_BEGIN
@interface TouchPointKitBridge : RCTEventEmitter <RCTBridgeModule, TouchPointActivityDelegate>
@end
NS_ASSUME_NONNULL_END
// TouchPointKitBridge.m
#import "TouchPointKitBridge.h"
@implementation TouchPointKitBridge
{
bool hasListeners;
}
// Will be called when this module's first listener is added.
-(void)startObserving {
hasListeners = YES;
}
// Will be called when this module's last listener is removed, or on dealloc.
-(void)stopObserving {
hasListeners = NO;
}
RCT_EXPORT_MODULE();
RCT_EXPORT_METHOD(configure:(NSString *)apiKey apiSecret:(NSString *)apiSecret pod:(int)pod screens:(NSArray *)screens visitor:(NSDictionary *)visitor ) {
[[TouchPointActivity shared] configureWithApiKey:apiKey apiSecret:apiSecret podName:pod screenComponents:screens visitor:visitor];
}
RCT_EXPORT_METHOD(refreshActivities) {
[[TouchPointActivity shared] refreshActivities];
}
RCT_EXPORT_METHOD(setVisitor:(NSDictionary<NSString *, id> *)visitor)
{
[TouchPointActivity shared].visitor = visitor;
}
RCT_EXPORT_METHOD(enableDebugLogs:(BOOL)enable)
{
[TouchPointActivity shared].enableDebugLogs = enable;
}
RCT_EXPORT_METHOD(disableAllLogs:(BOOL)disable)
{
[TouchPointActivity shared].disableAllLogs = disable;
}
RCT_EXPORT_METHOD(disableAPIFilter:(BOOL)disableAPIFilter)
{
[TouchPointActivity shared].disableAPIFilter = disableAPIFilter;
}
RCT_EXPORT_METHOD(disableCaching:(BOOL)caching)
{
[TouchPointActivity shared].disableCaching = caching;
}
RCT_EXPORT_METHOD(isStatusBarStyleLight:(BOOL)isStatusBarStyleLight)
{
[TouchPointActivity shared].isStatusBarStyleLight = isStatusBarStyleLight;
}
RCT_EXPORT_METHOD(defaultBannerHeight:(CGFloat)defaultBannerHeight)
{
[TouchPointActivity shared].defaultBannerHeight = defaultBannerHeight;
}
RCT_EXPORT_METHOD(setScreen:(NSString *)screenName)
{
[[TouchPointActivity shared] setScreenNameWithScreenName:screenName delegate: self];
}
RCT_EXPORT_METHOD(openActivity:(NSString *)screenName componentName:(NSString *)componentName)
{
if ([[TouchPointActivity shared] shouldShowActivityWithScreenName: screenName componentName: componentName]) {
[[TouchPointActivity shared] openActivityForScreenComponentWithScreenName: screenName componentName:componentName delegate: self];
}
}
RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD(shouldShowActivity:(NSString *)screenName) {
BOOL val = [[TouchPointActivity shared] shouldShowActivityWithScreenName: screenName componentName: NULL];
return [NSNumber numberWithBool:val];
}
RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD(shouldShowActivity:(NSString *)screenName componentName:(NSString *)componentName) {
BOOL val = [[TouchPointActivity shared] shouldShowActivityWithScreenName: screenName componentName: componentName];
return [NSNumber numberWithBool:val];
}
RCT_EXPORT_METHOD(openActivityForUrl:(NSString *)url alwaysShow:(BOOL)alwaysShow)
{
[[TouchPointActivity shared] openActivityForUrlWithDistUrl:url useBannerStyling:false delegate:self alwaysShow:alwaysShow];
}
RCT_EXPORT_METHOD(clearCache)
{
NSUserDefaults * userDefaults = [NSUserDefaults standardUserDefaults];
NSDictionary * dict = [userDefaults dictionaryRepresentation];
for (id key in dict) {
[userDefaults removeObjectForKey:key];
}
[userDefaults synchronize];
}
- (dispatch_queue_t)methodQueue
{
return dispatch_get_main_queue();
}
- (void) onTouchPointActivityComplete {
if (hasListeners) {
[self sendEventWithName:@"onTouchPointActivityComplete" body:@"TouchPointActivityCompleted"];
}
}
- (void) onTouchPointActivityCollapse {
if (hasListeners) {
[self sendEventWithName:@"onTouchPointActivityCollapse" body:@"TouchPointActivityCollapsed"];
}
}
- (NSArray<NSString *> *)supportedEvents {
return @[@"onTouchPointActivityComplete", @"onTouchPointActivityCollapse"];
}
@end
从您的 App.js 文件中,使用 NativeModules
调用 TouchPointKitBridge
方法。
import {
NativeModules,
NativeEventEmitter,
} from 'react-native';
// Register for event listening from SDK (activity complete event)
const { TouchPointKitBridge } = NativeModules;
const eventEmitter = new NativeEventEmitter(TouchPointKitBridge);
eventEmitter.addListener(
'onTouchPointActivityComplete',
onTouchPointActivityComplete,
);
eventEmitter.addListener(
'onTouchPointActivityCollapse',
onTouchPointActivityCollapse,
);
const onTouchPointActivityComplete = (event) => {
console.log('onTouchPointActivityComplete called');
console.log(event);
};
const onTouchPointActivityCollapse = (event) => {
console.log('onTouchPointActivityCollapse called');
console.log(event);
};
// To trigger a Pop-up or Banner
NativeModules.TouchPointKitBridge.setScreen('SCREEN_NAME');
// To trigger a custom component
NativeModules.TouchPointKitBridge.openActivity('SCREEN_NAME', 'COMPONENT_NAME');