795 lines
38 KiB
Markdown
795 lines
38 KiB
Markdown
|
|
# Insight iOS SDK Developer Guide
|
||
|
|
|
||
|
|
This document describes the integration of the Insight iOS SDK with your iOS application.
|
||
|
|
|
||
|
|
## Introduction
|
||
|
|
|
||
|
|
Insight iOS SDK collects location information and event data to track user interactions within the iOS application where it's installed. This data can be used for analyzing user behavior, improving user experience, and generating insights.
|
||
|
|
|
||
|
|
## Table of Contents
|
||
|
|
|
||
|
|
- [Insight iOS SDK Developer Guide](#insight-ios-sdk-developer-guide)
|
||
|
|
- [Introduction](#introduction)
|
||
|
|
- [Table of Contents](#table-of-contents)
|
||
|
|
- [1. Quick Start](#1-quick-start)
|
||
|
|
- [Add the SDK](#add-the-sdk)
|
||
|
|
- [Add required frameworks](#add-required-frameworks)
|
||
|
|
- [Configure location permissions](#configure-location-permissions)
|
||
|
|
- [Initialize the SDK](#initialize-the-sdk)
|
||
|
|
- [UIKit:](#uikit)
|
||
|
|
- [SwiftUI:](#swiftui)
|
||
|
|
- [2. Requirements](#2-requirements)
|
||
|
|
- [3. Integration](#3-integration)
|
||
|
|
- [Adding the Insight iOS SDK](#adding-the-insight-ios-sdk)
|
||
|
|
- [Swift Package Manager Installation](#swift-package-manager-installation)
|
||
|
|
- [Add Package Dependency](#add-package-dependency)
|
||
|
|
- [Enter Repository URL](#enter-repository-url)
|
||
|
|
- [Configure Package](#configure-package)
|
||
|
|
- [CocoaPods Installation](#cocoapods-installation)
|
||
|
|
- [Create Podfile](#create-podfile)
|
||
|
|
- [Reference InsightSDK in Podfile](#reference-insightsdk-in-podfile)
|
||
|
|
- [Install pods](#install-pods)
|
||
|
|
- [Manual Installation](#manual-installation)
|
||
|
|
- [Download the SDK](#download-the-sdk)
|
||
|
|
- [Copy the xcframework](#copy-the-xcframework)
|
||
|
|
- [Mark Embed and Sign](#mark-embed-and-sign)
|
||
|
|
- [Adding Required Frameworks](#adding-required-frameworks)
|
||
|
|
- [Configuring Permissions](#configuring-permissions)
|
||
|
|
- [Always And When In Use](#always-and-when-in-use)
|
||
|
|
- [When In Use](#when-in-use)
|
||
|
|
- [4. Usage](#4-usage)
|
||
|
|
- [Entering the Insight Platform Access Key](#entering-the-insight-platform-access-key)
|
||
|
|
- [UIKit Apps](#uikit-apps)
|
||
|
|
- [Simple Initialization](#simple-initialization)
|
||
|
|
- [SwiftUI Apps](#swiftui-apps)
|
||
|
|
- [Create AppDelegate.swift](#create-appdelegateswift)
|
||
|
|
- [Connect AppDelegate to SwiftUI App](#connect-appdelegate-to-swiftui-app)
|
||
|
|
- [Configuration Options](#configuration-options)
|
||
|
|
- [SdkConfiguration Parameters](#sdkconfiguration-parameters)
|
||
|
|
- [User Parameters](#user-parameters)
|
||
|
|
- [Completion Handler](#completion-handler)
|
||
|
|
- [Screen Recording (preview)](#screen-recording-preview)
|
||
|
|
- [New screens the SDK cannot see](#new-screens-the-sdk-cannot-see)
|
||
|
|
- [Masking whole view classes](#masking-whole-view-classes)
|
||
|
|
- [SwiftUI screens](#swiftui-screens)
|
||
|
|
- [Touches on masked content](#touches-on-masked-content)
|
||
|
|
- [5. Deprecations](#5-deprecations)
|
||
|
|
- [IDFA](#idfa)
|
||
|
|
- [Device Name](#device-name)
|
||
|
|
- [Carrier Info](#carrier-info)
|
||
|
|
- [6. Troubleshooting](#6-troubleshooting)
|
||
|
|
- [Common Issues](#common-issues)
|
||
|
|
- [7. Changelog](#7-changelog)
|
||
|
|
|
||
|
|
## 1. Quick Start
|
||
|
|
|
||
|
|
Get up and running with Insight SDK in 5 minutes:
|
||
|
|
|
||
|
|
#### Add the SDK
|
||
|
|
Via CocoaPods:
|
||
|
|
|
||
|
|
```ruby
|
||
|
|
pod 'InsightSDK', :git => 'https://insight-dl.apps.sccc.sa/sccc/iOS-SDK-test.git'
|
||
|
|
```
|
||
|
|
|
||
|
|
#### Add required frameworks
|
||
|
|
(see [Integration](#3-integration) section)
|
||
|
|
|
||
|
|
#### Configure location permissions
|
||
|
|
(see [Configuring Permissions](#configuring-permissions))
|
||
|
|
|
||
|
|
#### Initialize the SDK
|
||
|
|
|
||
|
|
##### UIKit:
|
||
|
|
```swift
|
||
|
|
// In AppDelegate.swift
|
||
|
|
import InsightSDK
|
||
|
|
|
||
|
|
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
|
||
|
|
let sdkConfig = SdkConfiguration(
|
||
|
|
apiKey: "YOUR_SECRET_KEY_HERE"
|
||
|
|
)
|
||
|
|
|
||
|
|
Insight.shared.start(config: sdkConfig, user: nil)
|
||
|
|
return true
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
##### SwiftUI:
|
||
|
|
|
||
|
|
Create AppDelegate.swift with initialization code
|
||
|
|
|
||
|
|
Add
|
||
|
|
```swift
|
||
|
|
@UIApplicationDelegateAdaptor private var appDelegate: AppDelegate
|
||
|
|
```
|
||
|
|
to your App struct.
|
||
|
|
|
||
|
|
For complete setup instructions, continue reading below.
|
||
|
|
|
||
|
|
<div style="page-break-after: always;"></div>
|
||
|
|
|
||
|
|
## 2. Requirements
|
||
|
|
|
||
|
|
In order to integrate the Insight iOS SDK, make sure you are:
|
||
|
|
|
||
|
|
* Targeting iOS version 13 or higher
|
||
|
|
* Targeting the Swift 5 compiler
|
||
|
|
|
||
|
|
* Registered on the *Insight Portal*
|
||
|
|
* You need to create a *Insight Access Key* for the iOS platform if not created already.
|
||
|
|
|
||
|
|
> **Note:** Make sure that you declare that "your app uses IDFA" on App Store Connect if you are using IDFA integration. Otherwise, your app may be rejected on review. Insight iOS SDK collects IDFA from the device when permitted by the user, in full compliance with the Apple [requirements](https://developer.apple.com/documentation/apptrackingtransparency). Insight doesn't trigger the permission dialog; you need to trigger this if needed.
|
||
|
|
|
||
|
|
## 3. Integration
|
||
|
|
|
||
|
|
### Adding the Insight iOS SDK
|
||
|
|
|
||
|
|
The Insight iOS SDK can be added via Swift Package Manager, CocoaPods, or manually.
|
||
|
|
|
||
|
|
#### Swift Package Manager Installation
|
||
|
|
|
||
|
|
##### Add Package Dependency
|
||
|
|
|
||
|
|
In Xcode, go to **File** → **Add Package Dependencies...**
|
||
|
|
|
||
|
|
##### Enter Repository URL
|
||
|
|
|
||
|
|
Enter the following URL in the search field:
|
||
|
|
```
|
||
|
|
https://insight-dl.apps.sccc.sa/sccc/iOS-SDK-test.git
|
||
|
|
```
|
||
|
|
|
||
|
|
##### Configure Package
|
||
|
|
|
||
|
|
Select the version and version rule (recommended: "Up to Next Major Version") and add the package to your target.
|
||
|
|
|
||
|
|
#### CocoaPods Installation
|
||
|
|
|
||
|
|
Referencing the `InsightSDK` pod in the `Podfile` will be sufficient to add the Insight iOS SDK to your project.
|
||
|
|
|
||
|
|
##### Create Podfile
|
||
|
|
|
||
|
|
If your project doesn't have a `Podfile`, then you can create one by running the following command on the terminal:
|
||
|
|
|
||
|
|
```bash
|
||
|
|
pod init
|
||
|
|
```
|
||
|
|
|
||
|
|
##### Reference InsightSDK in Podfile
|
||
|
|
|
||
|
|
Reference `InsightSDK` in the `Podfile`:
|
||
|
|
|
||
|
|
```ruby
|
||
|
|
pod 'InsightSDK', :git => 'https://insight-dl.apps.sccc.sa/sccc/iOS-SDK-test.git'
|
||
|
|
```
|
||
|
|
|
||
|
|
##### Install pods
|
||
|
|
|
||
|
|
Install pods by running the following command on the terminal:
|
||
|
|
|
||
|
|
```bash
|
||
|
|
pod install
|
||
|
|
```
|
||
|
|
|
||
|
|
#### Manual Installation
|
||
|
|
|
||
|
|
##### Download the SDK
|
||
|
|
|
||
|
|
You can download the SDK from the following repository:
|
||
|
|
```
|
||
|
|
https://insight-dl.apps.sccc.sa/sccc/iOS-SDK-test.git
|
||
|
|
```
|
||
|
|
|
||
|
|
##### Copy the xcframework
|
||
|
|
|
||
|
|
Copy "InsightSDK.xcframework" folder by dragging and dropping into your app using project navigator in Xcode. After copying the xcframework into your app, the color of the xcframework shall be blue.
|
||
|
|
|
||
|
|
##### Mark Embed and Sign
|
||
|
|
|
||
|
|
Go to project settings "Frameworks, Libraries and Embedded Content" section. Choose "Embed & Sign" for InsightSDK.xcframework.
|
||
|
|
|
||
|
|
### Adding Required Frameworks
|
||
|
|
|
||
|
|
Insight iOS SDK utilizes the following frameworks. Please make sure that your project references all of them:
|
||
|
|
|
||
|
|
* AdSupport.framework
|
||
|
|
* CoreLocation.framework
|
||
|
|
* CoreTelephony.framework
|
||
|
|
* SystemConfiguration.framework
|
||
|
|
|
||
|
|
### Configuring Permissions
|
||
|
|
|
||
|
|
In order to provide proper notifications and proper beacon tracking, you need to get permissions from the application user, after your application is installed. Per iOS user guides, applications can ask for required permissions with their own sentences. The permissions can be configured in the `Info.plist` file.
|
||
|
|
|
||
|
|
Insight iOS SDK uses the iBeacon protocol which requires location permission by default. Insight iOS SDK needs to detect beacons and geofences even when the app is in the background or killed. Therefore, you have to ask your users for *"Always usage of location"* permission.
|
||
|
|
|
||
|
|
Users are able to give location usage permission only when the application is in use. As a result, applications have to ask for two different types of location permissions: `WhenInUse` or `AlwaysAndWhenInUse`.
|
||
|
|
|
||
|
|
Your application should include both descriptors given below for location permission:
|
||
|
|
|
||
|
|
* NSLocationAlwaysAndWhenInUseUsageDescription
|
||
|
|
* NSLocationWhenInUseUsageDescription
|
||
|
|
|
||
|
|
In the description field of location permission entries, we recommend that you encourage your users to allow *"Always use my location"*, in order to have a better performance of the system. In the below subsections, we provide some sample description text. Please check and consider them.
|
||
|
|
|
||
|
|
#### Always And When In Use
|
||
|
|
|
||
|
|
This descriptor is used for iOS 11 and later. It provides permission for both when in use and when killed.
|
||
|
|
|
||
|
|
Sample Text: *"This application uses your location in order to inform you about interesting offers nearby. We advise you to choose the `Always` option to get the offers even when you are not using the application!"*
|
||
|
|
|
||
|
|
You can insert it into your `Info.plist` file in the following syntax.
|
||
|
|
|
||
|
|
```xml
|
||
|
|
<key>NSLocationAlwaysAndWhenInUseUsageDescription</key>
|
||
|
|
<string><# insert always and when in use usage description text #></string>
|
||
|
|
```
|
||
|
|
|
||
|
|
#### When In Use
|
||
|
|
|
||
|
|
This descriptor is used in all iOS versions. It provides permission for only when the application is in use. We advise you to warn your users about very low performance when receiving nearby offers.
|
||
|
|
|
||
|
|
Sample Text: *"This application uses your location in order to inform you about interesting offers nearby. Allowing location when in use only may result in poor performance in finding nearby offers!"*
|
||
|
|
|
||
|
|
You can insert it into your `Info.plist` file in the following syntax.
|
||
|
|
|
||
|
|
```xml
|
||
|
|
<key>NSLocationWhenInUseUsageDescription</key>
|
||
|
|
<string><# insert when in use usage description text #></string>
|
||
|
|
```
|
||
|
|
|
||
|
|
<div style="page-break-after: always;"></div>
|
||
|
|
|
||
|
|
## 4. Usage
|
||
|
|
|
||
|
|
In order to utilize the capabilities of the Insight iOS SDK, it needs to be started at some point in your application's lifecycle.
|
||
|
|
|
||
|
|
### Entering the Insight Platform Access Key
|
||
|
|
|
||
|
|
Insight iOS SDK requires the **Insight Platform Access Key**. You may need to create one for the iOS platform at the *Insight Publisher Portal*. If you do not have an account at the *Insight Publisher Portal*, please contact us at sdk@datauniverse.ai. You can set your key in your application's `Info.plist` file with the `Insight Platform Access Key` key:
|
||
|
|
|
||
|
|
```xml
|
||
|
|
<key>Insight Platform Access Key</key>
|
||
|
|
<string>YOUR_SECRET_KEY_HERE</string>
|
||
|
|
```
|
||
|
|
|
||
|
|
Alternatively, you can provide the **Insight Platform Access Key** via the SDK `start` method as documented in the next section.
|
||
|
|
|
||
|
|
### UIKit Apps
|
||
|
|
|
||
|
|
For traditional UIKit applications, initialize the SDK in your `AppDelegate.swift`:
|
||
|
|
|
||
|
|
```swift
|
||
|
|
import UIKit
|
||
|
|
import InsightSDK
|
||
|
|
|
||
|
|
@UIApplicationMain
|
||
|
|
class AppDelegate: NSObject, UIApplicationDelegate {
|
||
|
|
|
||
|
|
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
|
||
|
|
// ....
|
||
|
|
startInsightSDK()
|
||
|
|
// ...
|
||
|
|
return true
|
||
|
|
}
|
||
|
|
|
||
|
|
func startInsightSDK() {
|
||
|
|
let sdkConfig = SdkConfiguration(
|
||
|
|
apiKey: "YOUR_SECRET_KEY_HERE"
|
||
|
|
)
|
||
|
|
|
||
|
|
// If you have a "User" class/struct you need to use "InsightSDK.User" to prevent the conflict
|
||
|
|
let sdkUser = User(
|
||
|
|
userId: "YOUR_APP_USERS_UNIQUE_IDENTIFIER_FOR_YOUR_PLATFORM_HERE",
|
||
|
|
gender: YOUR_APP_USERS_GENDER_HERE, // .female or .male
|
||
|
|
yearOfBirth: YOUR_APP_USERS_BIRTH_YEAR_HERE,
|
||
|
|
displayName: "YOUR_APP_USERS_USERNAME_HERE",
|
||
|
|
email: "YOUR_APP_USERS_EMAIL_HERE",
|
||
|
|
phoneNumber: "YOUR_APP_USERS_PHONE_NUMBER_HERE",
|
||
|
|
other: [
|
||
|
|
"sp1" : "special property 1 value",
|
||
|
|
"sp2" : "special property 2 value"
|
||
|
|
]
|
||
|
|
)
|
||
|
|
|
||
|
|
Insight.shared.start(config: sdkConfig, user: sdkUser, completion: { sdkState in
|
||
|
|
switch sdkState {
|
||
|
|
case .success:
|
||
|
|
debugPrint("InsightSDK version: \(Insight.shared.version) started")
|
||
|
|
|
||
|
|
case .skipped:
|
||
|
|
debugPrint("InsightSDK version: \(Insight.shared.version) start skipped")
|
||
|
|
|
||
|
|
case .failure:
|
||
|
|
debugPrint("InsightSDK version: \(Insight.shared.version) failed")
|
||
|
|
|
||
|
|
@unknown default:
|
||
|
|
debugPrint("InsightSDK version: \(Insight.shared.version) start state unknown")
|
||
|
|
}
|
||
|
|
})
|
||
|
|
}
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
#### Simple Initialization
|
||
|
|
|
||
|
|
You can start the Insight iOS SDK by simply invoking the `start` method of the shared instance:
|
||
|
|
|
||
|
|
```swift
|
||
|
|
let sdkConfig = SdkConfiguration(
|
||
|
|
apiKey: "YOUR_SECRET_KEY_HERE"
|
||
|
|
)
|
||
|
|
Insight.shared.start(config: sdkConfig, user: nil)
|
||
|
|
```
|
||
|
|
|
||
|
|
### SwiftUI Apps
|
||
|
|
|
||
|
|
For SwiftUI applications (iOS 14+), you need to create an `AppDelegate.swift` file and use `@UIApplicationDelegateAdaptor` to ensure the SDK initializes properly.
|
||
|
|
|
||
|
|
#### Create AppDelegate.swift
|
||
|
|
|
||
|
|
Create a new file called `AppDelegate.swift` with your SDK initialization:
|
||
|
|
|
||
|
|
```swift
|
||
|
|
import UIKit
|
||
|
|
import InsightSDK
|
||
|
|
|
||
|
|
// AppDelegate to handle lifecycle events
|
||
|
|
class AppDelegate: NSObject, UIApplicationDelegate {
|
||
|
|
let insightSDK = InsightSDKManager()
|
||
|
|
|
||
|
|
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
|
||
|
|
insightSDK.startInsightSDK()
|
||
|
|
return true
|
||
|
|
}
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
Create an SDK manager to access SDK easier:
|
||
|
|
|
||
|
|
```swift
|
||
|
|
import SwiftUI
|
||
|
|
import InsightSDK
|
||
|
|
|
||
|
|
class InsightSDKManager: ObservableObject {
|
||
|
|
|
||
|
|
@Published var insightSDKStatus: String = "..."
|
||
|
|
|
||
|
|
func startInsightSDK() {
|
||
|
|
let sdkConfig = SdkConfiguration(
|
||
|
|
apiKey: "YOUR_SECRET_KEY_HERE",
|
||
|
|
pushToken: "" // when you don't have a push token use empty string
|
||
|
|
)
|
||
|
|
|
||
|
|
Insight.shared.start(config: sdkConfig, user: nil, completion: { [weak self] sdkState in
|
||
|
|
guard let self else { return }
|
||
|
|
DispatchQueue.main.async {
|
||
|
|
switch sdkState {
|
||
|
|
case .success:
|
||
|
|
self.insightSDKStatus = "Started"
|
||
|
|
debugPrint("InsightSDK version: \(Insight.shared.version) started")
|
||
|
|
|
||
|
|
case .skipped:
|
||
|
|
self.insightSDKStatus = "Skipped"
|
||
|
|
debugPrint("InsightSDK version: \(Insight.shared.version) start skipped")
|
||
|
|
|
||
|
|
case .failure:
|
||
|
|
self.insightSDKStatus = "Failed"
|
||
|
|
debugPrint("InsightSDK version: \(Insight.shared.version) failed")
|
||
|
|
|
||
|
|
@unknown default:
|
||
|
|
self.insightSDKStatus = "Unknown"
|
||
|
|
debugPrint("InsightSDK version: \(Insight.shared.version) start state unknown")
|
||
|
|
}
|
||
|
|
}
|
||
|
|
})
|
||
|
|
}
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
#### Connect AppDelegate to SwiftUI App
|
||
|
|
|
||
|
|
In your main SwiftUI App file, add the `@UIApplicationDelegateAdaptor`:
|
||
|
|
|
||
|
|
```swift
|
||
|
|
import SwiftUI
|
||
|
|
|
||
|
|
@main
|
||
|
|
struct MyApp: App {
|
||
|
|
@UIApplicationDelegateAdaptor private var appDelegate: AppDelegate
|
||
|
|
|
||
|
|
var body: some Scene {
|
||
|
|
WindowGroup {
|
||
|
|
ContentView()
|
||
|
|
.environmentObject(appDelegate.insightSDK)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
### Configuration Options
|
||
|
|
|
||
|
|
#### SdkConfiguration Parameters
|
||
|
|
|
||
|
|
The `SdkConfiguration` class contains the following properties:
|
||
|
|
|
||
|
|
| Property | Type | Description |
|
||
|
|
|-------------------------|---------|-------------------------------------------------------------------------------------------|
|
||
|
|
| apiKey | String | API Key generated in Insight Platform for the app. |
|
||
|
|
| pushToken | String? | Push notification token for the device. |
|
||
|
|
| logLevel | LogLevel| Logging verbosity level (default: .error). |
|
||
|
|
|
||
|
|
#### User Parameters
|
||
|
|
|
||
|
|
The `user` parameter allows you to enhance the audience data by providing information about the primary user (subscriber) of your application. You can give any information that makes the subscriber unique in your application's understanding. The `User` class contains the following:
|
||
|
|
|
||
|
|
| Property | Type | Example | Description |
|
||
|
|
|--------------|----------------------------|------------------|-----------------------------------------|
|
||
|
|
| userId | String? | "42" | Unique identifier of the user |
|
||
|
|
| gender | UserGender? | .female | Gender of the user (.female or .male) |
|
||
|
|
| yearOfBirth | Int? | 1999 | Year of birth of the user |
|
||
|
|
| email | String? | "mail@me.com" | Email address of the user |
|
||
|
|
| displayName | String? | "John Doe" | Display name of the user |
|
||
|
|
| phoneNumber | String? | "+96653612345" | Mobile phone number of the user |
|
||
|
|
| isPIIHashed | Bool? | true | Whether to hash user info |
|
||
|
|
| other | Dictionary<String,String>? | ["key": "value"] | Extra information for the user |
|
||
|
|
|
||
|
|
> **Note:** `email` and `phoneNumber` details are never sent in plain-text to the *Insight Platform* if the `isPIIHashed` property is set to true (default true). These values are always irreversibly hashed so that no personally identifiable information is stored.
|
||
|
|
|
||
|
|
#### Completion Handler
|
||
|
|
|
||
|
|
The `completion` parameter allows you to execute your business logic after the Insight iOS SDK initialization succeeds or fails.
|
||
|
|
|
||
|
|
### Screen Recording (preview)
|
||
|
|
|
||
|
|
Screen recording captures the screen as a series of masked frames so user
|
||
|
|
sessions can be replayed for diagnostics. It runs on iOS 13 and newer, in
|
||
|
|
UIKit and SwiftUI apps alike. A screenshot frame costs about 70 ms of
|
||
|
|
main-thread time on an iPhone 15 Pro Max, and the SDK caps that work at
|
||
|
|
100 ms per second.
|
||
|
|
|
||
|
|
**There is nothing to configure in your app.** Every screen recording setting
|
||
|
|
comes from the server, on the same configuration response the rest of the SDK
|
||
|
|
already reads, and until the server sends any the SDK runs on the defaults in
|
||
|
|
the table below. That includes whether it records at all: there is no local
|
||
|
|
switch, and turning it off for an app is a server-side change.
|
||
|
|
|
||
|
|
Navigation needs no code in most apps. The SDK captures a new screen when a
|
||
|
|
view controller appears, when a presentation closes, and when a SwiftUI
|
||
|
|
`NavigationStack` pushes or pops, a `TabView` changes tab or a sheet appears.
|
||
|
|
Two cases need one event from you; see
|
||
|
|
[New screens the SDK cannot see](#new-screens-the-sdk-cannot-see).
|
||
|
|
|
||
|
|
By default the SDK records text and images as drawn: `privacyText` and
|
||
|
|
`privacyImages` both default to `allow`. The SDK masks the following at every
|
||
|
|
privacy level, whatever the server sends: secure text entry fields and inputs
|
||
|
|
whose `textContentType` names a password, a one-time code, a card, a phone
|
||
|
|
number, an e-mail address, a name, an address or a birthdate; `WKWebView`;
|
||
|
|
video, camera, Metal and OpenGL layers; `MKMapView`; PencilKit canvases;
|
||
|
|
content another process draws; and custom views the SDK does not recognize,
|
||
|
|
such as a `UIView` that paints itself in `draw(_:)`. A PII scan also masks
|
||
|
|
e-mail addresses, card numbers and other personal data in text recorded in
|
||
|
|
clear; see `piiRules` below. To exclude any view from capture, set
|
||
|
|
`view.screenRecordingMask = true` (also available from Objective-C), or put
|
||
|
|
the string `screen-recording-mask` in its `accessibilityIdentifier`; in
|
||
|
|
SwiftUI, apply `.screenRecordingMask()`.
|
||
|
|
|
||
|
|
The SDK uploads recordings in batches from a queue of its own on the device.
|
||
|
|
`SdkConfiguration.networking.batchingEnabled` picks the endpoint for
|
||
|
|
recordings as it does for events: with batching on, the default, each batch
|
||
|
|
goes to `/sdk/ingest/bundles` as one bundle message; with batching off, it
|
||
|
|
goes to `/sdk/screen-recording/shots`. Recordings travel through the same
|
||
|
|
network stack, session token and certificate pinning as every other request.
|
||
|
|
|
||
|
|
Each recording carries the session id the server issued. A recording made
|
||
|
|
before the device has one, on a fresh install or an offline launch, stays on
|
||
|
|
the device and uploads once the SDK receives an id, in the same process or a
|
||
|
|
later launch. The seven-day `maxRecordAgeHours` limit below applies while it
|
||
|
|
waits. `Insight.shared.clearScreenRecordings()` deletes every stored
|
||
|
|
recording, buffered or queued, and abandons an upload in flight;
|
||
|
|
`clearStoredData()` does the same as part of a wider erasure.
|
||
|
|
|
||
|
|
What the server can send, and what applies until it does:
|
||
|
|
|
||
|
|
| Setting | Default | Description |
|
||
|
|
|--------------------------|------------|----------------------------------------------------------------|
|
||
|
|
| enabled | true | Master switch. Nothing is captured or stored when false. |
|
||
|
|
| mode | screenshot | `screenshot` or `wireframe`. A change applies at the next app launch. Records name the new mode from the next time the app comes to the foreground, so until that launch the mode a record names and the mode of its frames can differ. In wireframe mode a SwiftUI screen records as one grey placeholder. |
|
||
|
|
| privacyText | allow | `maskAll`, `maskInputs`, or `allow`. |
|
||
|
|
| privacyImages | allow | `maskAll`, `maskNonBundled`, or `allow`. `maskNonBundled` shows a `UIImageView` only when its image is a symbol image (`UIImage(systemName:)` or a custom symbol). It does NOT show your compiled-in PNGs: nothing in `UIImage` says which asset a bitmap came from, so an app asset and a downloaded photo are indistinguishable and both mask. Every SwiftUI image masks at this setting too, SF Symbols included, and so does every SwiftUI shape. |
|
||
|
|
| privacyTouch | show | `show` or `hide`. `hide` emits no touch data at all. If `hide` was in effect when the app launched, a change to `show` waits for the next launch. |
|
||
|
|
| imageQualityPercent | 30 | JPEG quality of each frame, 1 to 100. A change applies at the next app launch. |
|
||
|
|
| maxFrameLongEdgePx | 1280 | Frames are rendered so the long edge stays within this. A change applies at the next app launch. |
|
||
|
|
| throttleMs | 0 | Minimum interval between ordinary captures. 0 means the mode default: 1000 ms in screenshot mode, 250 ms in wireframe mode. The SDK captures at once, outside the interval, when a new screen appears (a view controller appears, a presentation closes, the key window changes, or a SwiftUI `NavigationStack` or `TabView` navigates), the keyboard settles, the screen rotates or the app returns to the foreground. A change applies at the next app launch. |
|
||
|
|
| privacyMaskViewClasses | empty | Runtime class names whose instances are masked with their whole subtree. See below. |
|
||
|
|
| privacyUnmaskViewClasses | empty | The same, in the other direction: the text and image settings are relaxed for those views. |
|
||
|
|
| privacyMaskTags | empty | Accepted and stored. This version applies it to nothing on iOS, because SwiftUI content exposes no tag, role or text to the SDK while it records. Use the marks in your code instead. |
|
||
|
|
| privacyUnmaskTags | empty | The same, in the other direction. Stored and not applied on iOS. |
|
||
|
|
| privacyRules | empty | Rules by tag, role, text or description, optionally per screen. Stored and not applied on iOS in this version. |
|
||
|
|
| piiRules | all on | PII detectors for text recorded in clear: `email`, `iban`, `card`, `phone`, `nationalId`, `ip`, and `custom`, which switches `piiCustomPatterns`. Each key takes `true` or `false`, and a key the server leaves out stays on. The scan reads UIKit text such as a `UILabel`; it does not read a SwiftUI `Text` (see below). In screenshot mode a match masks the whole text element; in wireframe mode each match becomes `***`. |
|
||
|
|
| piiCustomPatterns | empty | Extra patterns for the PII scan. `NSRegularExpression` compiles them on iOS, so keep to the subset both platforms accept: literals, character classes, `.`, anchors, groups, alternation, greedy and lazy quantifiers, `\d`, `\w`, `\s` and their negations; no lookbehind, named groups or backreferences. They apply while `custom` in `piiRules` is on. The SDK reads at most 16 entries of at most 256 characters each; it drops and counts an entry past the sixteenth, a longer entry and an entry that does not compile. |
|
||
|
|
| frustrationEnabled | true | Detects rage taps and dead taps. `false` turns detection off and leaves the touch stream as it is. |
|
||
|
|
| frustrationKinds | both on | The kinds to detect: `rageTap` and `deadTap`, each `true` or `false`. `errorTap` is accepted and reserved; nothing in this version emits it. |
|
||
|
|
| frustrationRageTapCount | 3 | Taps that make a rage tap, the first one included. Each later tap lands within `frustrationRageRadiusDp` and `frustrationRageWindowMs` of the first. |
|
||
|
|
| frustrationRageRadiusDp | 44 | Rage tap radius, read as points on iOS, measured from the first tap. |
|
||
|
|
| frustrationRageWindowMs | 1000 | Rage tap window in milliseconds, counted from the first tap. |
|
||
|
|
| frustrationDeadTapMs | 1500 | A tap is a dead tap when the screen shows no visible change within this many milliseconds. |
|
||
|
|
| wifiOnly | false | When true, uploads wait for an unmetered network, such as Wi-Fi, and records stay queued until then. |
|
||
|
|
| maxRecordAgeHours | 168 | The SDK deletes a queued record captured more than this many hours ago instead of uploading it. 168 hours is seven days. |
|
||
|
|
|
||
|
|
A privacy change that masks more, such as a stricter `privacyText` or an added
|
||
|
|
mask rule, applies at once. One that masks less applies the next time the app
|
||
|
|
comes to the foreground.
|
||
|
|
|
||
|
|
An unrecognized setting, an unrecognized value, an unrecognized map key and a
|
||
|
|
value of the wrong type each leave that one setting or map entry at its
|
||
|
|
default and are counted as
|
||
|
|
`screen_recording_degraded_total{reason=carrier_unrecognized}`; none of them
|
||
|
|
rejects the rest of the response. The map keys are camelCase: a `piiRules` key
|
||
|
|
spelled `national_id` or a `frustrationKinds` key spelled `rage_tap` is
|
||
|
|
dropped, and that entry stays on.
|
||
|
|
|
||
|
|
The SDK keeps this counter, like every other counter this section names, in
|
||
|
|
process memory. Nothing uploads the counters yet; server-side reporting comes
|
||
|
|
in a later version. To see them, set `logLevel` to `.trace` in
|
||
|
|
`SdkConfiguration`: the SDK then logs each increment as a
|
||
|
|
`telemetry <name> +1` line, which you can watch live in Xcode's console or
|
||
|
|
with `log stream`. At the default `.error` level screen recording logs only
|
||
|
|
failures: an install that did not complete, a record it could not write or
|
||
|
|
read, a request it could not build.
|
||
|
|
|
||
|
|
#### New screens the SDK cannot see
|
||
|
|
|
||
|
|
The SDK captures a new screen as soon as it appears when a view controller is
|
||
|
|
shown, a presentation is dismissed, the key window changes, or a SwiftUI
|
||
|
|
`NavigationStack` pushes or pops, a `TabView` changes tab, or a sheet or cover
|
||
|
|
appears. Two navigations leave no such trace: SwiftUI content your app swaps
|
||
|
|
on state (an `if` or a `switch` in a view body), and a SwiftUI screen hosted
|
||
|
|
as a child of a UIKit navigation or tab bar controller. The SDK then records
|
||
|
|
the new content on its ordinary schedule, at most once per second by default,
|
||
|
|
so a screen the user leaves within that second may have no frame at all.
|
||
|
|
|
||
|
|
Log a screen view event when the new content appears. The SDK treats the next
|
||
|
|
frame as a new screen and records it outside the one-per-second limit:
|
||
|
|
|
||
|
|
```swift
|
||
|
|
.onAppear {
|
||
|
|
Insight.shared.logEvent(InsightEventOtherScreenView(screenClass: "Checkout", screenName: "Checkout"))
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
The recording names screens by view controller class. A SwiftUI screen gets
|
||
|
|
SwiftUI's own controller names, so every destination of one `NavigationStack`
|
||
|
|
shares one name and so does every tab of one `TabView`; the name in your event
|
||
|
|
does not reach the recording.
|
||
|
|
|
||
|
|
#### Masking whole view classes
|
||
|
|
|
||
|
|
Tagging individual views does not scale to "mask every balance label in the
|
||
|
|
app", and an app cannot tag views it does not own, such as a third-party UI
|
||
|
|
library's. `privacyMaskViewClasses` takes the class names instead; every
|
||
|
|
instance of a listed class, and of any subclass, is masked together with
|
||
|
|
everything drawn inside it. Your app does not set the list; ask for the class
|
||
|
|
names to be added to the server-side settings for your app.
|
||
|
|
|
||
|
|
Names are **runtime class names, matched exactly**: a Swift class is
|
||
|
|
module-qualified (`MyApp.BalanceLabel`) and an Objective-C or UIKit class is
|
||
|
|
bare (`UILabel`), which is what `NSClassFromString` accepts. There is no
|
||
|
|
substring matching: `Label` matches nothing, it does not mask every label in
|
||
|
|
the app. An entry containing `*` is a pattern, and `*` stands for any run of
|
||
|
|
characters: `MyApp.Wallet*` matches every class whose name starts that way,
|
||
|
|
subclasses included. The SDK counts an exact name that resolves to no class
|
||
|
|
as `screen_recording_degraded_total{reason=mask_class_unresolved}` and
|
||
|
|
otherwise ignores it, so a typo hides nothing and rejects nothing. A pattern
|
||
|
|
that matches nothing is not counted, because a pattern with no instance on
|
||
|
|
the current screen is normal; check a pattern rule by looking at a recording.
|
||
|
|
|
||
|
|
Two more things worth knowing before you rely on the lists:
|
||
|
|
|
||
|
|
- A view whose own code sets `screenRecordingUnmask = true`, or carries the
|
||
|
|
`screen-recording-unmask` token in its `accessibilityIdentifier`, is not
|
||
|
|
masked by a class rule matching that same view: an explicit local decision
|
||
|
|
in your source wins over a class rule. It does not work the other way
|
||
|
|
through the tree, though, since a class rule masks the whole subtree under
|
||
|
|
the view it matched and an unmask inside that subtree carves no exception
|
||
|
|
out of it.
|
||
|
|
- If a view matches both lists, it is masked. An ambiguous configuration must
|
||
|
|
not be the way content leaks.
|
||
|
|
|
||
|
|
**Class rules match UIKit views, never SwiftUI content.** SwiftUI's own
|
||
|
|
classes are private and a view struct has no class, so no entry in either list
|
||
|
|
can reach a single SwiftUI element. A UIKit view you host with
|
||
|
|
`UIViewRepresentable` is still a UIKit view and a class rule reaches it, and a
|
||
|
|
pattern that matches your hosting view's runtime class hides the whole SwiftUI
|
||
|
|
screen inside it. For single elements inside SwiftUI, use
|
||
|
|
`.screenRecordingMask()` and `.screenRecordingUnmask()` in your code; the
|
||
|
|
server's tag and rule settings are stored on iOS and applied to nothing in
|
||
|
|
this version.
|
||
|
|
|
||
|
|
#### SwiftUI screens
|
||
|
|
|
||
|
|
In screenshot mode the SDK masks, scans and withholds taps on a SwiftUI
|
||
|
|
screen as it does on a UIKit one, with the differences below. Everything
|
||
|
|
here was measured on the iOS 18.6 and 26.5 simulators.
|
||
|
|
|
||
|
|
**Marks.** `.screenRecordingMask()` hides the element and withholds taps on
|
||
|
|
it; `.screenRecordingUnmask()` relaxes the text and image settings for it. A
|
||
|
|
UIKit view you host under a marked element is hidden with it, taps included.
|
||
|
|
A mark covers the element's layout frame: a child you move out with
|
||
|
|
`.offset` keeps its pixels and its taps where it draws, so mark that child
|
||
|
|
too, and apply a mark before a transform so it turns with the content:
|
||
|
|
|
||
|
|
```swift
|
||
|
|
Text(cardNumber).screenRecordingMask().rotationEffect(.degrees(20)) // hides the rotated text
|
||
|
|
Text(cardNumber).rotationEffect(.degrees(20)).screenRecordingMask() // hides the unrotated frame; the ends show
|
||
|
|
```
|
||
|
|
|
||
|
|
**Personal data in a `Text` is not scanned.** The SDK finds card numbers,
|
||
|
|
e-mail addresses and the like in a `UILabel` and masks them at the default
|
||
|
|
text setting, but SwiftUI draws its text without exposing the string, so the
|
||
|
|
same card number in a `Text` shows. Mark such text with
|
||
|
|
`.screenRecordingMask()`, or have the server send `privacyText: maskAll`.
|
||
|
|
|
||
|
|
**What SwiftUI hides that UIKit shows.** Each is safe and costs you pixels:
|
||
|
|
|
||
|
|
- At `privacyImages` `maskAll` or `maskNonBundled`, every SwiftUI image
|
||
|
|
hides, SF Symbols included, and so does every shape (`Path`, `Circle`, a
|
||
|
|
stroked outline), because SwiftUI draws a shape and a symbol the same way.
|
||
|
|
- A `Canvas`, content under `.drawingGroup()` and charts drawn the same way
|
||
|
|
are always hidden, at every setting, and `.screenRecordingUnmask()` does
|
||
|
|
not expose them.
|
||
|
|
- At those two image settings a sheet, a popover or a `Menu` records as a
|
||
|
|
black block. UIKit presentations do the same.
|
||
|
|
- A plain text field hides what the user typed even at the default text
|
||
|
|
setting, in UIKit too.
|
||
|
|
|
||
|
|
**What SwiftUI shows that UIKit hides.** Mark these yourself:
|
||
|
|
|
||
|
|
- The selection of a menu-style `Picker` at `privacyText` `maskInputs`.
|
||
|
|
Wheel pickers, toggles, sliders and date pickers hide as their UIKit twins
|
||
|
|
do.
|
||
|
|
- On iOS 26, `.screenRecordingUnmask()` also relaxes text from an
|
||
|
|
overlapping sibling whose centre falls inside the unmasked element. Keep
|
||
|
|
unmasked elements off secret text, or mark the secret text.
|
||
|
|
|
||
|
|
**Presentations record.** `.sheet`, `.fullScreenCover`, `.popover`,
|
||
|
|
`.alert`, `.confirmationDialog`, `Menu` and `.contextMenu` all live in your
|
||
|
|
app's window, so they are recorded with their masks and their taps pass the
|
||
|
|
same checks. A window of your own that is not the key window, such as a
|
||
|
|
toast in a window of its own, is not recorded.
|
||
|
|
|
||
|
|
#### Touches on masked content
|
||
|
|
|
||
|
|
The SDK withholds a tap only when it lands on a secure or sensitive input, a
|
||
|
|
web view, a video, camera or GPU-backed view, a map, a PencilKit canvas, or
|
||
|
|
an element your app marks with `screenRecordingMask`, the
|
||
|
|
`screen-recording-mask` identifier token or `.screenRecordingMask()`. A
|
||
|
|
withheld tap leaves no coordinates, no rage or dead tap built from it, and
|
||
|
|
the whole gesture goes with it. A tap on a UIKit view hosted under a SwiftUI
|
||
|
|
mark is withheld too.
|
||
|
|
|
||
|
|
The SDK records a tap on content hidden only by a privacy setting, a class
|
||
|
|
rule, the PII scan or the rule that masks unrecognized custom views, and a
|
||
|
|
tap beside a withheld element. To withhold taps on such content, mark it in
|
||
|
|
your code. Two cases to know about:
|
||
|
|
|
||
|
|
- A signature pad your app draws itself, in `draw(_:)` or as a SwiftUI
|
||
|
|
`Canvas`, masks its pixels through the unrecognized-content rule and ships
|
||
|
|
its taps, so a viewer can replay the stroke. Mark the pad. PencilKit's
|
||
|
|
`PKCanvasView` withholds them on its own.
|
||
|
|
- A video or GPU layer (`AVPlayerLayer`, `CAMetalLayer`) you add as a
|
||
|
|
sublayer of an ordinary view is hidden in the frame, but a tap on it is
|
||
|
|
recorded, because the SDK tests taps against views. Mark the view that
|
||
|
|
holds the layer.
|
||
|
|
|
||
|
|
In a UIKit screen the check asks the topmost drawn view under the finger, so
|
||
|
|
a later sibling drawn over a marked view, transparent or not, takes the tap
|
||
|
|
back. Inside a SwiftUI screen every drawn element under the finger is asked,
|
||
|
|
so a clear overlay does not. `privacyTouch = hide` records no touches at
|
||
|
|
all.
|
||
|
|
|
||
|
|
<div style="page-break-after: always;"></div>
|
||
|
|
|
||
|
|
## 5. Deprecations
|
||
|
|
|
||
|
|
Due to Apple's ongoing privacy enhancements, several features have been deprecated or require special permissions:
|
||
|
|
|
||
|
|
### IDFA
|
||
|
|
|
||
|
|
Since iOS 14, Apple has introduced new privacy features that require user permission to access the IDFA. You'll need to request permission from the user before you can access the identifier.
|
||
|
|
|
||
|
|
Here's how to do that:
|
||
|
|
|
||
|
|
- Add the `AppTrackingTransparency` framework to your project and import it:
|
||
|
|
|
||
|
|
```swift
|
||
|
|
import AppTrackingTransparency
|
||
|
|
```
|
||
|
|
|
||
|
|
- Request permission from the user:
|
||
|
|
|
||
|
|
```swift
|
||
|
|
if #available(iOS 14, *) {
|
||
|
|
ATTrackingManager.requestTrackingAuthorization { status in
|
||
|
|
switch status {
|
||
|
|
case .authorized:
|
||
|
|
// Tracking authorization dialog was shown
|
||
|
|
// and we are authorized
|
||
|
|
debugPrint("Authorized")
|
||
|
|
|
||
|
|
// Now that we are authorized we can get the IDFA
|
||
|
|
let idfa = ASIdentifierManager.shared().advertisingIdentifier.uuidString
|
||
|
|
debugPrint("IDFA: \(idfa)")
|
||
|
|
case .denied:
|
||
|
|
// Tracking authorization dialog was
|
||
|
|
// shown and permission is denied
|
||
|
|
debugPrint("Denied")
|
||
|
|
case .notDetermined:
|
||
|
|
// Tracking authorization dialog has not been shown
|
||
|
|
debugPrint("Not Determined")
|
||
|
|
case .restricted:
|
||
|
|
// Tracking is restricted by some policy
|
||
|
|
debugPrint("Restricted")
|
||
|
|
@unknown default:
|
||
|
|
debugPrint("Unknown")
|
||
|
|
}
|
||
|
|
}
|
||
|
|
} else {
|
||
|
|
// iOS 14 not available, just get the IDFA
|
||
|
|
let idfa = ASIdentifierManager.shared().advertisingIdentifier.uuidString
|
||
|
|
debugPrint("IDFA: \(idfa)")
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
- Add the `NSUserTrackingUsageDescription` key to your Info.plist file. This is the message that will be shown to the user when requesting IDFA access permission:
|
||
|
|
|
||
|
|
```xml
|
||
|
|
<key>NSUserTrackingUsageDescription</key>
|
||
|
|
<string>This identifier will be used to deliver personalized ads to you.</string>
|
||
|
|
```
|
||
|
|
|
||
|
|
Remember that users can choose to deny this permission, in which case you won't be able to access their IDFA.
|
||
|
|
|
||
|
|
Insight SDK doesn't immediately collect the IDFA when initialized. It waits for ATT permission to be granted. On the next start of the SDK, it will collect this information if the user has granted the permission.
|
||
|
|
|
||
|
|
### Device Name
|
||
|
|
|
||
|
|
Starting with iOS 16, Apple restricted access to the user-defined device name for privacy reasons. This was done to prevent apps from potentially identifying users or devices without explicit permission.
|
||
|
|
|
||
|
|
For apps that genuinely need access to the device name (like device management or networking apps), Apple provides an entitlement called `Access device name` (com.apple.developer.device-information.user-assigned-device-name). However, this entitlement requires approval from Apple, and you need to justify why your app needs this information.
|
||
|
|
If you don't have this entitlement, your app will only get generic device names like "iPhone" or "iPad".
|
||
|
|
|
||
|
|
[Reference](https://developer.apple.com/documentation/bundleresources/entitlements/com_apple_developer_device-information_user-assigned-device-name) contains relevant information from Apple.
|
||
|
|
|
||
|
|
If you have acquired permissions, Insight SDK will collect this information.
|
||
|
|
|
||
|
|
### Carrier Info
|
||
|
|
|
||
|
|
Starting iOS 16, carrier name and code information are deprecated. [Reference 1](https://developer.apple.com/documentation/coretelephony/cttelephonynetworkinfo/servicesubscribercellularproviders) and [Reference 2](https://forums.developer.apple.com/forums/thread/714876) contains relevant information from Apple.
|
||
|
|
|
||
|
|
## 6. Troubleshooting
|
||
|
|
|
||
|
|
### Common Issues
|
||
|
|
|
||
|
|
**SDK not starting:**
|
||
|
|
- Verify API key is correct
|
||
|
|
- Verify API URL is correct
|
||
|
|
- Check that the device can connect to the API endpoint
|
||
|
|
- Check if SSL pinning is required and the SSL certificate is correctly set
|
||
|
|
|
||
|
|
**Location not working:**
|
||
|
|
- Verify location permissions in Info.plist
|
||
|
|
- Check that user has granted location permission
|
||
|
|
- For background location, ensure "Always" permission is granted
|
||
|
|
|
||
|
|
**Not reporting events in a SwiftUI app:**
|
||
|
|
- Make sure you've created AppDelegate.swift
|
||
|
|
- Verify @UIApplicationDelegateAdaptor is properly configured
|
||
|
|
- Check that SDK initialization code is in didFinishLaunchingWithOptions
|
||
|
|
|
||
|
|
## 7. Changelog
|
||
|
|
|
||
|
|
* **1.0.0** *(Released 2026-05-06)*
|
||
|
|
* Initial release
|