PageSense iOS SDK — React Native Bridge Integration Guide

PageSense iOS SDK — React Native Bridge Integration Guide

Overview  

This guide explains how to integrate the PageSense iOS SDK into a React Native application using the React Native Native Bridge. The bridge enables React Native JavaScript code to invoke iOS SDK functions and leverage FullStack experimentation capabilities within the mobile application.
By the end of this guide, your React Native application will be able to call the following PageSense SDK functions directly from JavaScript:
  • initialiseSDK
  • activateExperimentByContext
  • getVariationNameByContext
  • trackGoalByContext

What is the React Native Native Bridge  

The React Native Native Bridge is the communication layer between the JavaScript runtime and native iOS code. It allows JavaScript to invoke native functions, access platform-specific SDK features, and receive responses from native modules.
Since JavaScript cannot directly interact with Swift, React Native provides the bridge as the intermediary. Native methods are implemented in Swift and exposed to React Native through an Objective-C bridge file.

IdeaReact Native UI
       ↓
React Native Wrapper (TypeScript/JavaScript)
       ↓
React Native Native Bridge
       ↓
Objective-C Bridge Module (.m)
       ↓
Swift Native Module (.swift)
       ↓
PageSense iOS SDK

The native module is implemented in Swift and exposed to the React Native JavaScript layer via an Objective-C bridge file. Once registered, the module is accessible through React Native's NativeModules API.

Prerequisites  

  • Node.js
  • npm
  • React Native CLI
  • Xcode
  • CocoaPods
bash
node -v
npm -v
pod --version

Installing the PageSense iOS SDK  

The PageSense iOS SDK is distributed as an XCFramework.
  1. Download PageSenseFramework.xcframework.zip from the ZohoPageSenseSDK repository under the Frameworks folder and extract its contents.
  2. Place the extracted framework in the iOS project folder:
/ProjectRoot/ios/Frameworks/PageSenseFramework.xcframework
  1. Open the workspace in Xcode and navigate to:
Target → General → Frameworks, Libraries and Embedded Content
  1. Add PageSenseFramework.xcframework and set the option to Embed & Sign.

iOS React Native Libraries  

The following React Native libraries are required and are automatically installed when running pod install from the ios directory:
  • React-Core
  • React-RCTBridge
  • React-RCTNetwork
  • React-RCTText
  • React-RCTImage
The following iOS framework dependencies are required for implementing the React Native Bridge:
  • React
  • RCTBridgeModule
  • Foundation

Creating the Native Bridge Module  

The iOS native bridge is implemented in two parts: the Swift native module and the Objective-C bridge file.

Part 1 — Swift Native Module  

Create PageSenseSDKModule.swift inside the iOS application directory:
  1. /ProjectRoot/ios/SampleApp/PageSenseSDKModule.swift
  2. swift
  3. import Foundation
  4. import React
  5. import PageSenseFramework
  6. @objc(PageSenseSDKModule)
  7. class PageSenseSDKModule: NSObject {
  8.     private var client: PageSenseClient?
  9.     private var isInitialized: Bool = false
  10.     private static let LOG_TAG = "[PageSenseSDK]"
  11.     @objc
  12.     static func requiresMainQueueSetup() -> Bool {
  13.         return false
  14.     }
  15.     @objc
  16.     func initialiseSDK(
  17.         _ accountId: String,
  18.         sdkKey: String,
  19.         projectName: String,
  20.         resolver resolve: @escaping RCTPromiseResolveBlock,
  21.         rejecter reject: @escaping RCTPromiseRejectBlock
  22.     ) {
  23.         if self.isInitialized { resolve(true); return }
  24.         let pageSenseSDKOptions = PageSenseSDKOptions()
  25.         pageSenseSDKOptions.logLevel = .DEBUG
  26.         pageSenseSDKOptions.pollingInterval = 5
  27.         PageSense.shared.createNewPageSenseClient(
  28.             sdkAccountIDForDC: accountId,
  29.             sdkKey: sdkKey,
  30.             projectname: projectName,
  31.             sdkOptions: pageSenseSDKOptions
  32.         ) { pagesenseclient in
  33.             guard let client = pagesenseclient else { resolve(false); return }
  34.             self.client = client
  35.             self.isInitialized = true
  36.             resolve(true)
  37.         }
  38.     }
  39.     @objc
  40.     func activateExperimentByContext(
  41.         _ experimentName: String,
  42.         userContext: NSDictionary,
  43.         resolver resolve: @escaping RCTPromiseResolveBlock,
  44.         rejecter reject: @escaping RCTPromiseRejectBlock
  45.     ) {
  46.         guard let client = self.client,
  47.               let pageSenseUserContext = validateAndBuildUserContext(userContext) else {
  48.             resolve(nil); return
  49.         }
  50.         client.activateExperiment(experimentName: experimentName, userContext: pageSenseUserContext) { variation in
  51.             resolve(variation)
  52.         }
  53.     }
  54.     @objc
  55.     func getVariationNameByContext(
  56.         _ experimentName: String,
  57.         userContext: NSDictionary,
  58.         resolver resolve: @escaping RCTPromiseResolveBlock,
  59.         rejecter reject: @escaping RCTPromiseRejectBlock
  60.     ) {
  61.         guard let client = self.client,
  62.               let pageSenseUserContext = validateAndBuildUserContext(userContext) else {
  63.             resolve(nil); return
  64.         }
  65.         resolve(client.getVariationName(experimentName: experimentName, userContext: pageSenseUserContext))
  66.     }
  67.     @objc
  68.     func trackGoalByContext(_ goalName: String, userContext: NSDictionary) {
  69.         guard let client = self.client,
  70.               let pageSenseUserContext = validateAndBuildUserContext(userContext) else { return }
  71.         client.trackGoal(goalName: goalName, userContext: pageSenseUserContext)
  72.     }
  73.     @objc
  74.     func trackGoalWithPropertiesByContext(
  75.         _ goalName: String,
  76.         userContext: NSDictionary,
  77.         goalProperties: NSDictionary
  78.     ) {
  79.         guard let client = self.client,
  80.               let pageSenseUserContext = validateAndBuildUserContext(userContext),
  81.               let goalPropertiesMap = validateAndConvertGoalPropertiesNonEmpty(goalProperties) else { return }
  82.         client.trackGoal(goalName: goalName, userContext: pageSenseUserContext, goalProperties: goalPropertiesMap)
  83.     }
  84.     private func validateAndBuildUserContext(_ userContextDict: NSDictionary) -> PageSenseUserContext? {
  85.         guard let userId = userContextDict["userId"] as? String,
  86.               !userId.trimmingCharacters(in: .whitespaces).isEmpty else { return nil }
  87.         var userAttributes: [String: String] = [:]
  88.         if let rawAttrs = userContextDict["userAttributes"] as? NSDictionary {
  89.             for (key, value) in rawAttrs {
  90.                 guard let k = key as? String, let v = value as? String else { return nil }
  91.                 userAttributes[k] = v
  92.             }
  93.         }
  94.         return PageSenseUserContext(userId: userId, userAttributes: userAttributes)
  95.     }
  96.     private func validateAndConvertGoalPropertiesNonEmpty(_ goalPropertiesDict: NSDictionary) -> [GoalProperty: String]? {
  97.         if goalPropertiesDict.count == 0 { return nil }
  98.         var result: [GoalProperty: String] = [:]
  99.         for (key, value) in goalPropertiesDict {
  100.             guard let keyString = key as? String,
  101.                   let goalProperty = GoalProperty(rawValue: keyString),
  102.                   let valueString = value as? String else { return nil }
  103.             result[goalProperty] = valueString
  104.         }
  105.         return result
  106.     }
  107. }
Part 2 — Objective-C Bridge File  

Create PageSenseSDKModule.m in the same directory:
  1. /ProjectRoot/ios/SampleApp/PageSenseSDKModule.m
  2. objc
  3. #import <React/RCTBridgeModule.h>
  4. @interface RCT_EXTERN_MODULE(PageSenseSDKModule, NSObject)
  5. RCT_EXTERN_METHOD(initialiseSDK:(NSString *)accountId
  6.                   sdkKey:(NSString *)sdkKey
  7.                   projectName:(NSString *)projectName
  8.                   resolver:(RCTPromiseResolveBlock)resolve
  9.                   rejecter:(RCTPromiseRejectBlock)reject)
  10. RCT_EXTERN_METHOD(activateExperimentByContext:(NSString *)experimentName
  11.                   userContext:(NSDictionary *)userContext
  12.                   resolver:(RCTPromiseResolveBlock)resolve
  13.                   rejecter:(RCTPromiseRejectBlock)reject)
  14. RCT_EXTERN_METHOD(getVariationNameByContext:(NSString *)experimentName
  15.                   userContext:(NSDictionary *)userContext
  16.                   resolver:(RCTPromiseResolveBlock)resolve
  17.                   rejecter:(RCTPromiseRejectBlock)reject)
  18. RCT_EXTERN_METHOD(trackGoalByContext:(NSString *)goalName
  19.                   userContext:(NSDictionary *)userContext)
  20. RCT_EXTERN_METHOD(trackGoalWithPropertiesByContext:(NSString *)goalName
  21.                   userContext:(NSDictionary *)userContext
  22.                   goalProperties:(NSDictionary *)goalProperties)
  23. @end
Adding the Objective-C Bridging Header  

To enable communication between Swift and Objective-C:
  1. Create the bridging header file at:
/ProjectRoot/ios/SampleApp/SampleApp-Bridging-Header.h
  1. Open the project in Xcode and navigate to Build Settings.
  2. Locate Objective-C Bridging Header and set its value to:
SampleApp/SampleApp-Bridging-Header.h

GoalProperty Mapping  


On iOS, the PageSense SDK accepts goal properties as [GoalProperty: String], where GoalProperty is an enum exposed by PageSenseFramework with lowercase raw values. The React Native bridge receives goal properties from JavaScript as an NSDictionary of string key/value pairs and maps each lowercase string key to its corresponding GoalProperty enum case using GoalProperty(rawValue:).
JavaScript Key
String Value
iOS GoalProperty Enum Case
GoalProperty.REVENUE
"revenue"
GoalProperty.revenue

Best Practices  

  • SDK initialisation should occur only once per application lifecycle for a project and should happen before experiments are activated.
  • Do not block UI rendering if SDK initialisation fails. Always use a fallback UI.
  • Keep the PageSenseClient instance alive for the entire application session.
  • Create a unique user ID for each user and reuse it globally.
  • Build the PageSenseUserContext once per user session and reuse it across SDK calls.
  • When tracking Revenue Goals, always pass the revenue value in cents as a string. Multiply the dollar amount by 100 and round to the nearest integer.
  • Use the GoalProperty constants exported by the JavaScript wrapper instead of hard-coded strings. The lowercase string values map directly to the iOS GoalProperty enum raw values.
  • Always handle null variations by falling back to a safe default UI.
  • Native modules should only store SDK-related objects, not UI state.
  • Trigger goals after the specific user action or event completes successfully.
  • For all new integrations, use the context-based SDK functions (activateExperimentByContext, getVariationNameByContext, trackGoalByContext).



We’ve designed this documentation to guide you every step of the way. If you need further assistance or have any questions, don’t hesitate to contact us at support@zohopagesense.com - we’re always here to help!