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:
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.
React 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.
Place the extracted framework in the iOS project folder:
/ProjectRoot/ios/Frameworks/PageSenseFramework.xcframework
Open the workspace in Xcode and navigate to:
Target → General → Frameworks, Libraries and Embedded Content
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:
- /ProjectRoot/ios/SampleApp/PageSenseSDKModule.swift
- swift
- import Foundation
- import React
- import PageSenseFramework
- @objc(PageSenseSDKModule)
- class PageSenseSDKModule: NSObject {
- private var client: PageSenseClient?
- private var isInitialized: Bool = false
- private static let LOG_TAG = "[PageSenseSDK]"
- @objc
- static func requiresMainQueueSetup() -> Bool {
- return false
- }
- @objc
- func initialiseSDK(
- _ accountId: String,
- sdkKey: String,
- projectName: String,
- resolver resolve: @escaping RCTPromiseResolveBlock,
- rejecter reject: @escaping RCTPromiseRejectBlock
- ) {
- if self.isInitialized { resolve(true); return }
- let pageSenseSDKOptions = PageSenseSDKOptions()
- pageSenseSDKOptions.logLevel = .DEBUG
- pageSenseSDKOptions.pollingInterval = 5
- PageSense.shared.createNewPageSenseClient(
- sdkAccountIDForDC: accountId,
- sdkKey: sdkKey,
- projectname: projectName,
- sdkOptions: pageSenseSDKOptions
- ) { pagesenseclient in
- guard let client = pagesenseclient else { resolve(false); return }
- self.client = client
- self.isInitialized = true
- resolve(true)
- }
- }
- @objc
- func activateExperimentByContext(
- _ experimentName: String,
- userContext: NSDictionary,
- resolver resolve: @escaping RCTPromiseResolveBlock,
- rejecter reject: @escaping RCTPromiseRejectBlock
- ) {
- guard let client = self.client,
- let pageSenseUserContext = validateAndBuildUserContext(userContext) else {
- resolve(nil); return
- }
- client.activateExperiment(experimentName: experimentName, userContext: pageSenseUserContext) { variation in
- resolve(variation)
- }
- }
- @objc
- func getVariationNameByContext(
- _ experimentName: String,
- userContext: NSDictionary,
- resolver resolve: @escaping RCTPromiseResolveBlock,
- rejecter reject: @escaping RCTPromiseRejectBlock
- ) {
- guard let client = self.client,
- let pageSenseUserContext = validateAndBuildUserContext(userContext) else {
- resolve(nil); return
- }
- resolve(client.getVariationName(experimentName: experimentName, userContext: pageSenseUserContext))
- }
- @objc
- func trackGoalByContext(_ goalName: String, userContext: NSDictionary) {
- guard let client = self.client,
- let pageSenseUserContext = validateAndBuildUserContext(userContext) else { return }
- client.trackGoal(goalName: goalName, userContext: pageSenseUserContext)
- }
- @objc
- func trackGoalWithPropertiesByContext(
- _ goalName: String,
- userContext: NSDictionary,
- goalProperties: NSDictionary
- ) {
- guard let client = self.client,
- let pageSenseUserContext = validateAndBuildUserContext(userContext),
- let goalPropertiesMap = validateAndConvertGoalPropertiesNonEmpty(goalProperties) else { return }
- client.trackGoal(goalName: goalName, userContext: pageSenseUserContext, goalProperties: goalPropertiesMap)
- }
- private func validateAndBuildUserContext(_ userContextDict: NSDictionary) -> PageSenseUserContext? {
- guard let userId = userContextDict["userId"] as? String,
- !userId.trimmingCharacters(in: .whitespaces).isEmpty else { return nil }
- var userAttributes: [String: String] = [:]
- if let rawAttrs = userContextDict["userAttributes"] as? NSDictionary {
- for (key, value) in rawAttrs {
- guard let k = key as? String, let v = value as? String else { return nil }
- userAttributes[k] = v
- }
- }
- return PageSenseUserContext(userId: userId, userAttributes: userAttributes)
- }
- private func validateAndConvertGoalPropertiesNonEmpty(_ goalPropertiesDict: NSDictionary) -> [GoalProperty: String]? {
- if goalPropertiesDict.count == 0 { return nil }
- var result: [GoalProperty: String] = [:]
- for (key, value) in goalPropertiesDict {
- guard let keyString = key as? String,
- let goalProperty = GoalProperty(rawValue: keyString),
- let valueString = value as? String else { return nil }
- result[goalProperty] = valueString
- }
- return result
- }
- }
Part 2 — Objective-C Bridge File
Create PageSenseSDKModule.m in the same directory:
- /ProjectRoot/ios/SampleApp/PageSenseSDKModule.m
- objc
- #import <React/RCTBridgeModule.h>
- @interface RCT_EXTERN_MODULE(PageSenseSDKModule, NSObject)
- RCT_EXTERN_METHOD(initialiseSDK:(NSString *)accountId
- sdkKey:(NSString *)sdkKey
- projectName:(NSString *)projectName
- resolver:(RCTPromiseResolveBlock)resolve
- rejecter:(RCTPromiseRejectBlock)reject)
- RCT_EXTERN_METHOD(activateExperimentByContext:(NSString *)experimentName
- userContext:(NSDictionary *)userContext
- resolver:(RCTPromiseResolveBlock)resolve
- rejecter:(RCTPromiseRejectBlock)reject)
- RCT_EXTERN_METHOD(getVariationNameByContext:(NSString *)experimentName
- userContext:(NSDictionary *)userContext
- resolver:(RCTPromiseResolveBlock)resolve
- rejecter:(RCTPromiseRejectBlock)reject)
- RCT_EXTERN_METHOD(trackGoalByContext:(NSString *)goalName
- userContext:(NSDictionary *)userContext)
- RCT_EXTERN_METHOD(trackGoalWithPropertiesByContext:(NSString *)goalName
- userContext:(NSDictionary *)userContext
- goalProperties:(NSDictionary *)goalProperties)
- @end
Adding the Objective-C Bridging Header
To enable communication between Swift and Objective-C:
Create the bridging header file at:
/ProjectRoot/ios/SampleApp/SampleApp-Bridging-Header.h
Open the project in Xcode and navigate to Build Settings.
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!