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
- Introduction
- Table of Contents
- 1. Quick Start
- 2. Requirements
- 3. Integration
- 4. Usage
- 5. Deprecations
- 6. Troubleshooting
- 7. Changelog
1. Quick Start
Get up and running with Insight SDK in 5 minutes:
Add the SDK
Via CocoaPods:
pod 'InsightSDK', :git => 'https://insight-dl.apps.sccc.sa/sccc/iOS-SDK-test.git'
Add required frameworks
(see Integration section)
Configure location permissions
(see Configuring Permissions)
Initialize the SDK
UIKit:
// 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
@UIApplicationDelegateAdaptor private var appDelegate: AppDelegate
to your App struct.
For complete setup instructions, continue reading below.
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. 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:
pod init
Reference InsightSDK in Podfile
Reference InsightSDK in the Podfile:
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:
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.
<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.
<key>NSLocationWhenInUseUsageDescription</key>
<string><# insert when in use usage description text #></string>
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@insight.apps.sccc.sa. You can set your key in your application's Info.plist file with the Insight Platform Access Key key:
<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:
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:
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:
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:
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:
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 |
| 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:
phoneNumberdetails are never sent in plain-text to the Insight Platform if theisPIIHashedproperty 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.
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:
.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 thescreen-recording-unmasktoken in itsaccessibilityIdentifier, 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:
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
privacyImagesmaskAllormaskNonBundled, 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
Menurecords 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
PickeratprivacyTextmaskInputs. 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 SwiftUICanvas, masks its pixels through the unrecognized-content rule and ships its taps, so a viewer can replay the stroke. Mark the pad. PencilKit'sPKCanvasViewwithholds 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.
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
AppTrackingTransparencyframework to your project and import it:
import AppTrackingTransparency
- Request permission from the user:
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
NSUserTrackingUsageDescriptionkey to your Info.plist file. This is the message that will be shown to the user when requesting IDFA access permission:
<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 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 and Reference 2 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