Version 1.0.0

This commit is contained in:
2026-10-31 23:39:07 +03:00
parent 91c51f8a36
commit ecadaf24f7
22 changed files with 2586 additions and 1808 deletions
+424 -333
View File
@@ -11,91 +11,103 @@ Insight iOS SDK collects location information and event data to track user inter
- [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)
- [1. Quick start](#1-quick-start)
- [Add the SDK](#add-the-sdk)
- [Add required frameworks](#add-required-frameworks)
- [Configure location permissions (optional)](#configure-location-permissions-optional)
- [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)
- [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)
- [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)
- [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)
- [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)
- [Connect AppDelegate to the SwiftUI app](#connect-appdelegate-to-the-swiftui-app)
- [Configuration options](#configuration-options)
- [SdkConfiguration parameters](#sdkconfiguration-parameters)
- [User parameters](#user-parameters)
- [Completion handler](#completion-handler)
- [Stopping, restarting and changing configuration](#stopping-restarting-and-changing-configuration)
- [Stopping](#stopping)
- [Changing the user](#changing-the-user)
- [Changing the API key](#changing-the-api-key)
- [Restarting](#restarting)
- [Clearing stored data](#clearing-stored-data)
- [Network performance tracking](#network-performance-tracking)
- [URLSession integration](#urlsession-integration)
- [What gets tracked](#what-gets-tracked)
- [Which hosts get measured](#which-hosts-get-measured)
- [Screen recording (preview)](#screen-recording-preview)
- [Overview](#overview)
- [What you need to do](#what-you-need-to-do)
- [API reference](#api-reference)
- [Masking](#masking)
- [Touches](#touches)
- [Known limits](#known-limits)
- [5. Deprecations](#5-deprecations)
- [IDFA](#idfa)
- [Device Name](#device-name)
- [Carrier Info](#carrier-info)
- [Device name](#device-name)
- [Carrier info](#carrier-info)
- [6. Troubleshooting](#6-troubleshooting)
- [Common Issues](#common-issues)
- [Common issues](#common-issues)
- [7. Changelog](#7-changelog)
## 1. Quick Start
## 1. Quick start
Get up and running with Insight SDK in 5 minutes:
#### Add the SDK
### Add the SDK
Via CocoaPods:
```ruby
pod 'InsightSDK', :git => 'https://insight-dl.apps.sccc.sa/sccc/iOS-SDK-test.git'
```
#### Add required frameworks
### Add required frameworks
(see [Integration](#3-integration) section)
#### Configure location permissions
(see [Configuring Permissions](#configuring-permissions))
### Configure location permissions (optional)
(see [Configuring permissions](#configuring-permissions))
#### Initialize the SDK
### Initialize the SDK
##### UIKit:
#### 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"
)
let sdkConfig = SdkConfiguration(
apiKey: "YOUR_SECRET_KEY_HERE"
)
Insight.shared.start(config: sdkConfig, user: nil)
Insight.shared.start(config: sdkConfig, user: nil)
return true
}
```
##### SwiftUI:
#### SwiftUI
Create AppDelegate.swift with initialization code
@@ -107,13 +119,11 @@ 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 iOS version 15 or higher
* Targeting the Swift 5 compiler
* Registered on the *Insight Portal*
@@ -127,24 +137,24 @@ In order to integrate the Insight iOS SDK, make sure you are:
The Insight iOS SDK can be added via Swift Package Manager, CocoaPods, or manually.
#### Swift Package Manager Installation
#### Swift Package Manager installation
##### Add Package Dependency
##### Add package dependency
In Xcode, go to **File** → **Add Package Dependencies...**
##### Enter Repository URL
##### Enter repository URL
Enter the following URL in the search field:
```
https://insight-dl.apps.sccc.sa/sccc/iOS-SDK-test.git
```
##### Configure Package
##### Configure package
Select the version and version rule (recommended: "Up to Next Major Version") and add the package to your target.
#### CocoaPods Installation
#### CocoaPods installation
Referencing the `InsightSDK` pod in the `Podfile` will be sufficient to add the Insight iOS SDK to your project.
@@ -172,7 +182,7 @@ Install pods by running the following command on the terminal:
pod install
```
#### Manual Installation
#### Manual installation
##### Download the SDK
@@ -189,7 +199,7 @@ Copy "InsightSDK.xcframework" folder by dragging and dropping into your app usin
Go to project settings "Frameworks, Libraries and Embedded Content" section. Choose "Embed & Sign" for InsightSDK.xcframework.
### Adding Required Frameworks
### Adding required frameworks
Insight iOS SDK utilizes the following frameworks. Please make sure that your project references all of them:
@@ -198,22 +208,18 @@ Insight iOS SDK utilizes the following frameworks. Please make sure that your pr
* CoreTelephony.framework
* SystemConfiguration.framework
### Configuring Permissions
### 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.
The SDK asks the user for no permission. Your app decides whether to ask for location and notification permissions, and the SDK uses a permission only after the user grants it. Location is optional: without it the SDK works and sends no location.
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.
If your app asks for location permission, Apple requires two things:
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`.
1. **A purpose string in `Info.plist`** for each kind of location permission your app requests: `NSLocationWhenInUseUsageDescription`, and `NSLocationAlwaysAndWhenInUseUsageDescription` if you also request Always. Without it, iOS shows the user no prompt, and App Store Connect can reject the build.
2. **Location in your app's privacy declarations.** The SDK sends the device location to Insight once the user allows it. Declare Precise Location, linked to the user, for analytics, in your app's privacy manifest (`PrivacyInfo.xcprivacy`) and in the App Privacy details in App Store Connect.
Your application should include both descriptors given below for location permission:
With Always permission, the SDK also receives location updates while your app is in the background. In the subsections below, we provide sample description text.
* 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
#### 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.
@@ -226,7 +232,7 @@ You can insert it into your `Info.plist` file in the following syntax.
<string><# insert always and when in use usage description text #></string>
```
#### When In Use
#### 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.
@@ -239,8 +245,6 @@ You can insert it into your `Info.plist` file in the following syntax.
<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.
@@ -256,7 +260,7 @@ Insight iOS SDK requires the **Insight Platform Access Key**. You may need to cr
Alternatively, you can provide the **Insight Platform Access Key** via the SDK `start` method as documented in the next section.
### UIKit Apps
### UIKit apps
For traditional UIKit applications, initialize the SDK in your `AppDelegate.swift`:
@@ -312,18 +316,20 @@ class AppDelegate: NSObject, UIApplicationDelegate {
}
```
#### Simple Initialization
#### Simple initialization
You can start the Insight iOS SDK by simply invoking the `start` method of the shared instance:
```swift
import InsightSDK
let sdkConfig = SdkConfiguration(
apiKey: "YOUR_SECRET_KEY_HERE"
)
Insight.shared.start(config: sdkConfig, user: nil)
```
### SwiftUI Apps
### SwiftUI apps
For SwiftUI applications (iOS 14+), you need to create an `AppDelegate.swift` file and use `@UIApplicationDelegateAdaptor` to ensure the SDK initializes properly.
@@ -388,7 +394,7 @@ class InsightSDKManager: ObservableObject {
}
```
#### Connect AppDelegate to SwiftUI App
#### Connect AppDelegate to the SwiftUI app
In your main SwiftUI App file, add the `@UIApplicationDelegateAdaptor`:
@@ -398,7 +404,7 @@ import SwiftUI
@main
struct MyApp: App {
@UIApplicationDelegateAdaptor private var appDelegate: AppDelegate
var body: some Scene {
WindowGroup {
ContentView()
@@ -408,9 +414,9 @@ struct MyApp: App {
}
```
### Configuration Options
### Configuration options
#### SdkConfiguration Parameters
#### SdkConfiguration parameters
The `SdkConfiguration` class contains the following properties:
@@ -420,7 +426,7 @@ The `SdkConfiguration` class contains the following properties:
| pushToken | String? | Push notification token for the device. |
| logLevel | LogLevel| Logging verbosity level (default: .error). |
#### User Parameters
#### 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:
@@ -437,259 +443,351 @@ The `user` parameter allows you to enhance the audience data by providing inform
> **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
#### Completion handler
The `completion` parameter allows you to execute your business logic after the Insight iOS SDK initialization succeeds or fails.
### Screen Recording (preview)
### Stopping, restarting and changing configuration
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.
`start(config:user:completion:)` covers the first launch. A settings screen that changes the user or the API key needs the calls below. Call each of them on `Insight.shared`.
**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.
#### Stopping
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).
`stop()` and `stop(completion:)` return at once. From the call, the SDK collects nothing. Event logging, crash reporting, location monitoring, screen tracking, network performance tracking and screen recording stop. An event that you log answers its callback with an `APIError` whose `code` is `-4`, and the SDK does not store it.
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()`.
Then the SDK sends what it holds for at most 5 seconds, in the background. It sends the stored crash reports first, then the queued events and the queued screen recordings together. Whatever did not leave stays queued and goes out after your next `start`. The stop deletes nothing.
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.
`stop(completion:)` calls `completion` once, on the main queue, when the SDK has sent what it could. This happens at most 5 seconds after the call, or at once when the SDK was not started. The SDK does not cancel a request that is still in progress at 5 seconds. A second `stop` before the report joins the first and gets the same report. A `start` or `restart` that you call before the report waits for it and runs after it. After the report, the SDK sends nothing until your next start succeeds.
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:
For screen recording, a stop ends the open recording and adds its last part to the queue that the stop sends. The next `start` turns screen recording on again.
```swift
import InsightSDK
Insight.shared.stop(completion: {
debugPrint("InsightSDK stopped")
})
```
#### Changing the user
The SDK sends the `User` when a session starts. To send a changed user, such as a new display name, call `restart(user:config:)` with the new `User`. The SDK stops, waits for the stop's report and starts again with the new `User`. A call to `stop()` and then to `start(config:user:completion:)` does the same, because the start waits for the stop's report.
```swift
import InsightSDK
let sdkUser = User(
userId: "YOUR_APP_USERS_UNIQUE_IDENTIFIER_FOR_YOUR_PLATFORM_HERE",
gender: YOUR_APP_USERS_GENDER_HERE, // .female or .male
displayName: "YOUR_APP_USERS_NEW_USERNAME_HERE"
)
Insight.shared.restart(user: sdkUser)
```
#### Changing the API key
To use a new API key, pass a new `SdkConfiguration` to `restart(user:config:)`. The SDK stops, then starts with the new configuration once the stop has reported. The change deletes nothing. New events, crash reports and screen recordings go out with the new key. What the SDK queued under the old key keeps going out with the old key.
```swift
import InsightSDK
let sdkConfig = SdkConfiguration(
apiKey: "YOUR_NEW_SECRET_KEY_HERE"
)
Insight.shared.restart(config: sdkConfig)
```
#### Restarting
`restart(user:config:)` stops the SDK and starts it again once the stop has reported. Both parameters are optional. A `nil` user or configuration means the one from the most recent start. When the SDK is not running, `restart` only starts it. The stop sends what the SDK holds for at most 5 seconds and keeps the rest, as [Stopping](#stopping) describes.
Events that your app logs between the `restart` call and the end of the new start wait in memory, in order. When the start ends, the SDK logs them. If event logging is off at that time, each of them gets the error `-4`. A plain `stop()` still refuses events with `-4`.
```swift
import InsightSDK
Insight.shared.restart()
```
### Clearing stored data
Each method below erases one part of what the SDK keeps on the device. None of them calls `stop()`. Call each of them on `Insight.shared`:
| Method | What it clears | What it keeps |
|--------|----------------|---------------|
| `clearStoredData()` | The stored crash reports and every stored screen recording. It also stops a recording upload in progress. | Queued events and health messages. |
| `clearCrashReports()` | The stored crash reports. | Everything else, screen recordings included. |
| `clearHealthMessages()` | The messages that you logged with `logHealthMessage(_:)`. | Everything else. |
| `clearScreenRecordings()` | Every stored screen recording and its upload queue. It also stops an upload in progress. It works before `start`. | Everything else. |
| `clearRequestQueues()` | Every queued event that waits for upload, for every API key. | Everything else, screen recordings included. |
`clearStoredData()` and `clearScreenRecordings()` wait for the main thread when you call them from another thread. Data that the server already has needs a server-side deletion.
`flushBatchRequests()` erases nothing. It asks the SDK to send the queued events now and returns at once. Before a start succeeds, and between `stop()` and the next successful start, it sends nothing.
```swift
import InsightSDK
// Screen recordings only: the stored recordings, their queue and any upload in progress
Insight.shared.clearScreenRecordings()
// Crash reports and screen recordings
Insight.shared.clearStoredData()
```
### Network performance tracking
Insight iOS SDK tracks the performance of the HTTP requests that your app makes with `URLSession`. You can monitor API response times and status codes.
#### URLSession integration
You write no code. The SDK tracks requests while your server settings turn network performance tracking on. It measures the requests that your app starts with these `URLSession` methods:
- `dataTask(with:)` and `dataTask(with:completionHandler:)`, with a `URLRequest` or a `URL`
- `uploadTask(with:from:completionHandler:)`
- `downloadTask(with:completionHandler:)`, with a `URLRequest`
The SDK adds its measurement to these methods at run time, through method swizzling. It measures requests on `URLSession.shared` and on every session that your app or a library creates. Your session delegate still gets each call. After `stop()`, the SDK measures nothing until the next start.
#### What gets tracked
The network performance tracking captures:
- **Request URL and method** (GET, POST, etc.)
- **Response time** (total duration from request start to completion)
- **HTTP status codes** (200, 404, 500, etc.)
- **Request and response sizes**
The SDK removes the user name, password, query and fragment from each URL. For a download task, the response size is 0. The SDK records a request only when it gets an HTTP response, so a request that fails with no response leaves no record.
#### Which hosts get measured
The Insight platform settings can name the hosts to measure. When they name any, the SDK measures requests to those hosts alone and skips the rest. When they name none, the SDK measures requests to every host. In both cases, the SDK skips the Insight host that it sends its own data to. It also skips every URL whose scheme is not `http` or `https`.
> **Note:** The SDK sends each measurement to the Insight platform.
### Screen recording (preview)
> **Preview:** Screen recording is in preview. Its behavior can change in a
> later release.
#### Overview
**Screen recording** captures masked frames of your app's screens, so your
team can replay a user session. It runs on iOS 15 or later, in UIKit and
SwiftUI apps. A frame costs about 70 ms of main-thread time on an iPhone 15
Pro Max. The SDK limits that work to 100 ms in each second.
Screen recording is off until your app's server settings turn it on. The
same settings control the privacy level and the frame quality. Your app has
nothing to configure, and the SDK has no local switch. When the settings
turn recording on, the SDK starts to record at once. Some other changes to
the server settings apply at the next app launch.
#### What you need to do
In most apps, you do not need to write code. The SDK captures a new screen
when a view controller appears, a presentation closes, or the key window
changes. In SwiftUI, it also captures one when a `NavigationStack` pushes or
pops, a `TabView` changes tab, or a sheet or cover appears.
Some changes give the SDK no signal. An example is SwiftUI content that your
app swaps on state, with an `if` or a `switch` in a view body. The SDK then
records the new content on its normal schedule, at most once per second by
default. A screen that the user leaves within that second can have no frame.
To record a new screen at once, log a screen view event when it appears. The
SDK treats the next frame as a new screen and records it outside the
once-per-second limit:
```swift
import InsightSDK
.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:
To record a change inside one screen, such as a view that your app swaps on
a state change, call `requestScreenRecordingCapture()`:
```swift
Text(cardNumber).screenRecordingMask().rotationEffect(.degrees(20)) // hides the rotated text
Text(cardNumber).rotationEffect(.degrees(20)).screenRecordingMask() // hides the unrotated frame; the ends show
import InsightSDK
Insight.shared.requestScreenRecordingCapture()
```
**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`.
The SDK captures the screen at once. The normal capture limits and privacy
rules still apply. A call before `start`, while the SDK is stopped, or while
the app is in the background does nothing.
**What SwiftUI hides that UIKit shows.** Each is safe and costs you pixels:
#### API reference
- 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.
The table lists every public screen recording API:
**What SwiftUI shows that UIKit hides.** Mark these yourself:
| API | Use it to | Notes |
|---|---|---|
| `var screenRecordingMask: Bool` | Mask a UIKit view and its subviews. | A property on `UIView`, also in Objective-C. The default is `false`. The SDK also withholds taps on the view. |
| `var screenRecordingUnmask: Bool` | Show the text and images of a UIKit view and its subviews. | A property on `UIView`, also in Objective-C. The default is `false`. It never unmasks always-masked content. |
| `screen-recording-mask` in `accessibilityIdentifier` | Mask a UIKit view through its identifier. | The identifier can hold other text too. It works as `screenRecordingMask` does. |
| `screen-recording-unmask` in `accessibilityIdentifier` | Unmask a UIKit view through its identifier. | The identifier can hold other text too. It works as `screenRecordingUnmask` does. |
| `func screenRecordingMask() -> some View` | Mask a SwiftUI view and its children. | A SwiftUI modifier. The SDK also withholds taps on the view. |
| `func screenRecordingUnmask() -> some View` | Show the text and images of a SwiftUI view and its children. | A SwiftUI modifier. It never unmasks always-masked content. |
| `func requestScreenRecordingCapture()` | Capture the screen after a change that the SDK cannot see. | Call it on `Insight.shared` from any thread. It returns at once. |
| `func clearScreenRecordings()` | Delete every screen recording that the device stores. | Call it on `Insight.shared` from any thread. It also stops an upload in progress, and it works before `start`. |
| `func clearStoredData()` | Delete the SDK's stored user data on the device. | It deletes every stored recording and the pending crash reports. Data that the server already has needs a server-side deletion. |
| `InsightEventOtherScreenView(screenClass: String, screenName: String, parameters: [String: Value] = [:])` | Mark a new screen that the SDK cannot see. | Log it with `Insight.shared.logEvent(_:callback:)`. The recording does not show `screenName`, but a server rule for named screens matches it. |
- 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.
> **Note:** `clearScreenRecordings()` and `clearStoredData()` wait for the
> main thread when you call them from another thread.
**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.
Use the `screenRecordingMask` or `screenRecordingUnmask` property to mark a
UIKit view. For example:
#### Touches on masked content
```swift
import InsightSDK
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.
cardNumberField.screenRecordingMask = true
headlineLabel.screenRecordingUnmask = true
```
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:
You can also set the property from Objective-C:
`self.cardNumberField.screenRecordingMask = YES;`.
- 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.
Put a mask token in the `accessibilityIdentifier` to mark a view without the
property. The sample uses the identifier `checkout.card`:
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.
```swift
cardNumberField.accessibilityIdentifier = "checkout.card screen-recording-mask"
```
<div style="page-break-after: always;"></div>
Use the `screenRecordingMask()` and `screenRecordingUnmask()` modifiers to
mark a SwiftUI view. For example:
```swift
import InsightSDK
Text(cardNumber).screenRecordingMask()
Text(headline).screenRecordingUnmask()
```
The SDK masks the card number and shows the headline in each frame.
#### Masking
By default, the SDK records text and images as drawn. If the server sets a
stricter privacy level, the SDK masks more.
The SDK always masks the following content, whatever the server sends. No
mark or rule can unmask it:
- Secure text entry fields
- 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 that another process draws
- Custom views that the SDK does not recognize, such as a `UIView` that
paints itself in `draw(_:)`
A scan for personal data (PII) also masks e-mail addresses, card numbers,
and similar data in UIKit text that the SDK records in clear.
To keep other content out of a recording, mask it with a property, an
identifier token, or a modifier. To show text and images that the privacy
level masks, unmask the view. Three rules decide when marks and server rules
meet:
- An unmask in code on a view beats a server rule that masks the same
view.
- A mask wins when a view matches both a mask and an unmask.
- A mask covers the whole subtree. An unmask inside a masked view does
not unmask its content.
The server can also mask or unmask by class name or by rule. A rule matches a
UIKit view by its `accessibilityIdentifier`, its `accessibilityLabel`, its
text, or its kind of view. A rule can apply on every screen or on named
screens only. A screen name is the `screenName` of the latest
`InsightEventOtherScreenView` that your app logged. Without one, it is the
class name of the top view controller. Your app does not set class lists or
rules. Ask for them in your app's server-side settings.
Give each class as its runtime class name. The SDK matches the name exactly:
- A Swift class has its module name: `MyApp.BalanceLabel`.
- An Objective-C or UIKit class has no prefix: `UILabel`.
- The SDK does no substring match, so `Label` matches no class.
- In an entry with `*`, the `*` stands for any run of characters. For
example, `MyApp.Wallet*` matches every class whose name starts that way.
- A match includes the subclasses of the class.
- The SDK ignores an exact name that matches no class. To check a rule, look
at a recording.
Class names and server rules reach UIKit views only. A UIKit view that you
host with `UIViewRepresentable` is still a UIKit view. For an element inside
SwiftUI, use `screenRecordingMask()` and `screenRecordingUnmask()`.
#### Touches
The SDK withholds a tap when it lands on one of the following:
- A secure or sensitive input
- A web view
- A video, camera, or GPU-backed view
- A map
- A PencilKit canvas
- An element that your app masks with the property, the identifier token, or
the modifier
A withheld tap leaves no coordinates in the recording, and the SDK drops the
whole gesture. The SDK records taps on other masked content. That includes content
that only the privacy level, a server rule, or the scan for personal data
masks, and unrecognized custom views. To withhold taps on such
content, mark it in your code. Two cases need a mark:
- A signature pad that your app draws itself, in `draw(_:)` or as a SwiftUI
`Canvas`. The SDK masks its pixels but records its taps, so a viewer can
replay the stroke. Mark the pad. `PKCanvasView` withholds its taps without
a mark.
- A video or GPU layer (`AVPlayerLayer`, `CAMetalLayer`) that you add to an
ordinary view. The SDK hides the layer in the frame but records a tap on
it. Mark the view that holds the layer.
In a UIKit screen, the SDK asks only the topmost drawn view under the finger.
A view drawn over a marked view, clear or not, takes the tap back. In a
SwiftUI screen, the SDK asks every drawn element under the finger, so a
clear overlay does not take the tap. If the server turns touch recording
off, the SDK records no touches.
#### Known limits
- The SDK does not scan SwiftUI `Text` for personal data. A card number in a
`Text` shows in the frame. Mark such text with `screenRecordingMask()`.
- A mark covers the element's layout frame. A child that you move out with
`.offset` shows where it draws. Mark that child too.
- A mark applied after a rotation covers the unrotated frame, so the ends of
the content show. Apply the mark before the transform:
`Text(cardNumber).screenRecordingMask().rotationEffect(.degrees(20))`.
- When the server masks images, every SwiftUI image, SF Symbol, and shape
hides. No workaround exists.
- The SDK always hides a SwiftUI `Canvas`, content under `.drawingGroup()`,
and charts drawn the same way. `screenRecordingUnmask()` does not show
them. No workaround exists.
- A plain text field hides what the user typed at the default text level.
No workaround exists.
- The selection of a menu-style `Picker` shows when the server masks input
text. Mark the picker with `screenRecordingMask()`.
- On iOS 26, `screenRecordingUnmask()` also shows text from an overlapping
sibling whose center falls inside the element. Keep unmasked elements off
secret text, or mark the secret text.
- The SDK does not record a window of your own that is not the key window,
such as a toast window. No workaround exists.
- In SwiftUI, every destination of one `NavigationStack` shares one screen
name in the recording, and so does every tab of one `TabView`. No
workaround exists.
## 5. Deprecations
@@ -701,44 +799,39 @@ Since iOS 14, Apple has introduced new privacy features that require user permis
Here's how to do that:
- Add the `AppTrackingTransparency` framework to your project and import it:
- Add the `AppTrackingTransparency` framework to your project and import it, together with `AdSupport` for the identifier:
```swift
import AppTrackingTransparency
import AdSupport
```
- 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")
}
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)")
}
```
@@ -753,7 +846,7 @@ Remember that users can choose to deny this permission, in which case you won't
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
### 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.
@@ -764,19 +857,17 @@ If you don't have this entitlement, your app will only get generic device names
If you have acquired permissions, Insight SDK will collect this information.
### Carrier Info
### 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
### 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