---
title: Session Replay - iOS
description: Learn how to integrate and configure Session Replay capabilities in your iOS app with the CSQ SDK
lastUpdated: 14 January 2026
source_url:
  html: https://docs.contentsquare.com/en/csq-sdk-ios/experience-analytics/session-replay/
  md: https://docs.contentsquare.com/en/csq-sdk-ios/experience-analytics/session-replay/index.md
---

Session Replay enables replaying real sessions to observe how users interact with your product, by capturing screen content - including text and images - as users navigate your mobile app. For more details, see [Introduction to Session Replay for Apps ↗](https://support.contentsquare.com/hc/en-us/articles/37271667148561-Introduction-to-Session-Replay-for-Apps) in the Help Center.

## Starter guide

### Prerequisites

#### Update to latest SDK version

In order to enable Session Replay in your app and get the most stable version, it is required to [upgrade the SDK](https://docs.contentsquare.com/en/csq-sdk-ios/experience-analytics/#install-the-sdk) to its latest version.

#### Implement screen tracking

Ensure screen tracking is implemented, since Session Replay collection starts at the first screenview event. See [Track screens](../track-screens/).

#### Handle User consent

Ensure user consent is handled for Contentsquare data collection, especially if you're implementing the SDK for the first time. See [Privacy](../privacy/) section.

Note

Mandatory data collection settings are configured by Contentsquare in your project parameters. [Learn more about Data collection configurations ↗](https://support.contentsquare.com/hc/en-us/articles/37271667148561-Introduction-to-Session-Replay-for-Apps) on the Help Center.

### Configure Session Replay masking

#### Masking behavior

By default, the Contentsquare SDK is configured to mask all content displayed on the user interface (UI) to prevent unnecessary data collection. For more details, see what a fully masked screen looks like in [Masking mechanisms](#masking-mechanisms).

**For testing purposes**, use this setup:

* Either unmasking all the content by setting the [default masking](#default-masking) status to false.
* Or unmasking most content with the [Text and Image unmasking configuration ↗](https://support.contentsquare.com/hc/en-us/articles/37271682878353-Data-masking-in-Session-Replay-Mobile-Apps) in your Console parameters.

**For more granular control in deployment mode**, you can use the [Text and Image unmasking configuration ↗](https://support.contentsquare.com/hc/en-us/articles/17800059306396-Data-masking-in-Session-Replay-Mobile-Apps) in your Console parameters in combination with [SDK public masking APIs](#public-masking-apis) to fine-tune exactly which elements remain masked.

Warning

Always keep sensitive data masked to ensure compliance with privacy regulations and to protect users' personal data.

### Configure Event-Triggered Replays (optional)

If Event-Triggered Replays is included in your contract, you can set up ETR events to enable specific screen and session collection in Session Replay. See [Event-Triggered Replays (ETR)](#event-triggered-replays-etr).

### Configure Webviews (optional)

If your mobile app includes Webviews, specific implementation steps are required to ensure they are properly reflected in Session Replay:

* Implement [Webview tracking](https://docs.contentsquare.com/en/webview-tracking-tag/)
* Handle [personal data within WebViews](https://docs.contentsquare.com/en/webview-tracking-tag/session-replay/#personal-data-handling)

### Configure custom fonts (optional)

To ensure UIKit text elements are rendered in Session Replay exactly as they appear in your app, you can add fonts in `otf`, `ttf`, `woff`, or `woff2` formats directly from the player. See [Managing iOS Fonts In The Player ↗](https://support.contentsquare.com/hc/en-us/articles/37271807874577) in the Help Center documentation.

### Test your setup

#### Enable Session Replay on your device

If the Session Replay sampling rate is less than 100%, only a portion of sessions are collected. To ensure data collection on your device for testing and debugging purposes, you can force it from the In-app Features settings by following these steps:

1. [Enable In-app Features](../in-app-features/)
2. Open In-app Features settings with a long press on the screenshot button
3. Under "Session Replay", toggle "Enable Session Replay" on
4. Kill the app
5. Start app, a new session is starting with Session Replay enabled

![](https://docs.contentsquare.com/_astro/ios-enable-sr-2.0sYFDfbq.gif)

#### How do I know if Session Replay is enabled?

There are 2 places where you can check if Session Replay is enabled:

**In the logs:** The log `Session Recording is starting` will confirm that Session Replay is enabled.

**In in-app features settings:** Below the "Enable "will start at next app start" *(see below [Access the replay](#access-the-replay))*, you will either see:

* `No replay link available` which means Session Replay is not running for the current session
* `Get Replay link` which means Session Replay is running for the current session

#### Access the Replay

The session can be accessed by tapping on `Get replay link` button from the in-app features settings:

![](https://docs.contentsquare.com/_astro/ios-get-replay-link.tN1tVH-m_Z1EodDY.webp)

The replay will be **available within 5 minutes**. Only the "ended screen views" are processed *(we know a screenview is ended when we start receiving data for the next screenview)*. This means that you will be able to replay your session up to the previous screenview if the session is still running.

## Personal Data Masking

The Session Replay feature replays every interaction of your users with your app. To respect the user’s right to privacy, the Contentsquare SDK:

* Masks everything by default
* Allows you to control which part of the user interface is collected via our [Masking rules](#masking-rules)

Warning

**Privacy Manifest:** If you decide to use unmasked elements leading to collection of new data or use already collected data but for a different purpose, it is up to you to update the privacy practices described in your app manifest accordingly. Here are some common examples of data collected via unmasked session Replay: Search history, Purchase history, Advertising data, etc. See [Types of data and purpose](../privacy/) already described in the Contentsquare iOS SDK Privacy Manifest.

**Important:** Contentsquare is not intended nor designed to collect sensitive personal data (e.g. health, financial, racial data). It is the customer’s responsibility not to send any sensitive personal data to Contentsquare.

### Masking mechanisms

Masking depends on the type of element:

* `UIKit.UIImage` and `SwiftUI.Image`: image isn't collected, a placeholder is sent instead of the content; the "IMG" placeholder will be displayed in the frame of the element.
* `UIKit.UILabel` and `UIKit.UITextView`: text is replaced by "la" repeated as many times as needed to equal the original character count. White characters are preserved. For instance `the lazy fox` is collected as `lal lala ala`. All other visual properties are collected (text color, background color, alignment, etc.).
* `UIKit.UITextField`: same as `UIKit.UILabel` or `UIKit.UITextView` unless `isSecureTextEntry` is set to `true`. In this case all characters, including whitespaces are replaced with "•".
* `SwiftUI.Text`: element is replaced by a black rectangle.
* For all other types: no specific data is collected but all visual properties are collected: size, background color, corner radius, etc.

If you think a specific element can reveal personal data from one of these properties you have to mask it using one of the methods presented below. A good way to check how a view is rendered in the Session Replay is to navigate to the desired view with the CS SDK running then use the [quick replay link](#access-the-replay).

#### Original VS Replay fully masked

* UIKit

  ![](https://docs.contentsquare.com/_astro/uikit-sr-masked.4j5fWBpf_1wMYqm.webp)

* SwiftUI

  ![](https://docs.contentsquare.com/_astro/swiftui-sr-masked.vHtiPIlu_Z1zur8Q.webp)

### Masking rules

Masking rules can be applied in two ways:

1. **Through remote masking configuration in the CSQ Console (for admin users):** These configurations are managed directly in the Console and take effect for all sessions as soon as the app is restarted or brought to the foreground See [How to customize masking rules from the Data Masking tab ↗](https://support.contentsquare.com/hc/en-us/articles/37271848716561-How-to-customize-masking-rules-from-the-Data-Masking-tab-Apps) in the Help Center.
2. **Using public masking APIs in the SDK (for mobile application developers):** These configurations require developer implementation and will be applied only after the mobile app has gone through its release cycle.

Masking rules set via the CSQ Console or the public APIs are applied according to different priorities, as described below. The SDK determines whether a view is masked by evaluating the rules in the order specified below. Once a rule is triggered, the state is set, and subsequent rules are not applied:

#### General case

| Rules | Configured via |
| - | - |
| 1. The app or SDK version is fully masked | [Data Masking tab in the CSQ Console ↗](https://support.contentsquare.com/hc/en-us/articles/37271848716561-How-to-customize-masking-rules-from-the-Data-Masking-tab-Apps) |
| 2. Remote Text or Image masking is defined | [Data Masking tab in the CSQ Console ↗](https://support.contentsquare.com/hc/en-us/articles/37271848716561-How-to-customize-masking-rules-from-the-Data-Masking-tab-Apps) |
| 3. An [instance](#maskingun-masking-by-instance) is specifically masked or unmasked | API |
| 4. A [parent](#masking-and-unmasking-behaviors-on-a-parent-view) is specifically masked or unmasked | API |
| 5. A [type](#maskingun-masking-by-type) is masked or unmasked | API |
| 6. A parent [type](#maskingun-masking-by-type) is masked or unmasked | API |
| 7. Remote Text or Image unmasking is defined | [Data Masking tab in the CSQ Console ↗](https://support.contentsquare.com/hc/en-us/articles/37271848716561-How-to-customize-masking-rules-from-the-Data-Masking-tab-Apps) |
| 8. Otherwise the [default masking state](#default-masking) is applied | API |

#### Exception: Text input fields

Special consideration is needed for text inputs, as the risk of leaking personal data is higher with these elements. As a result, the SDK applies slightly different rules to `UIKit.UITextField`, `UIKit.UITextView` and `SwiftUI.TextField`:

| Rules | Configured via |
| - | - |
| 1. The app or SDK version is fully masked | [Data Masking tab in the CSQ Console ↗](https://support.contentsquare.com/hc/en-us/articles/37271848716561-How-to-customize-masking-rules-from-the-Data-Masking-tab-Apps) |
| 2. Remote Text inputs masking is defined | [Data Masking tab in the CSQ Console ↗](https://support.contentsquare.com/hc/en-us/articles/37271848716561-How-to-customize-masking-rules-from-the-Data-Masking-tab-Apps) |
| 3. An [instance](#maskingun-masking-by-instance) is specifically masked or unmasked | API |
| 4. A [type](#maskingun-masking-by-type) is masked or unmasked | API |
| 5. Otherwise a text input field remains masked | default |

Specifically, note that default masking and parent masking won't change the masking state of text inputs.

### Public masking APIs

#### Default masking

All iOS views and subclasses of `UIView` are fully masked by default. The SDK provides an API to change the default masking state:

```swift
/// Change the masking state of all types of views.
/// - Parameter mask: true restores the default masking state.
///                   false unmasks every type of views.
static func setDefaultMasking(_ mask: Bool)
```

Note

If you change the default masking state to `false`, `UITextField`, `SwiftUI.TextField` & `WKWebView` will stay masked by default.

#### Masking/Un-masking by instance

* UIKit

  Use `mask(view:)` and `unmask(view:)` methods to deal with a specific instance. Masking is applied recursively to all subviews unless specified otherwise (cf. [Masking and Unmasking behaviors on a parent view](#masking-and-unmasking-behaviors-on-a-parent-view)).

  **Following the previous example on un-masking UIButtons:** you are in default masking, the `UIButton` are unmasked but a screen shows a button, `myButton`, that contains sensitive information. You will be able to mask it with `mask(view: myButton)`.

* SwiftUI

  You can mask or unmask a SwiftUI `View` instance by using the following view modifier. Masking is applied recursively to all subviews unless specified otherwise (cf. [Masking and Unmasking behaviors on a parent view](#masking-and-unmasking-behaviors-on-a-parent-view)).

  ```swift
  @ViewBuilder
  func csMasking(_ shouldMask: Bool) -> some View
  ```

#### Masking/Un-masking by type

The Contentsquare SDK allows masking elements by type for convenience but it isn't the recommended masking mechanism because it will impact all screens of your application. For instance, use this when you have a specific class for presenting user information or for displaying the user profile picture to make sure it always stays mask.

* UIKit

  Use `mask(viewsOfType:)` and `unmask(viewsOfType:)` methods if you want to handle a specific type (whether being a system element or a custom element).

  **Example: un-masking UIButtons** If you are in default masking but you don’t want any button to be masked, you will only need to call `unmask(viewsOfType: UIButton.self):` to specify that all instances of UIButton and its subclasses should not be masked.

* SwiftUI

  Masking/unmasking by type can be achieved using 1 or more of the 3 following methods.

  ```swift
  /// Masks or unmasks all text elements.
  ///
  /// The text elements are `Text` for SwiftUI and `UILabel` for UIKit
  static func maskTexts(_ mask: Bool)
  ```

  ```swift
  /// Masks or unmasks all image elements.
  ///
  /// The image elements are `Image` for SwiftUI and `UIImageView` for UIKit.
  static func maskImages(_ mask: Bool)
  ```

  ```swift
  /// Masks or unmasks all text input elements.
  ///
  /// The text input elements are `TextField`, `SecureField`, `TextEditor` for SwiftUI
  /// and `UITextField`, `UITextView` for UIKit.
  ///
  static func maskTextInputs(_ mask: Bool)
  ```

Note

Beware, using **these APIs will also affect UIKit masking**. If you wish, you can use only these APIs and mask/unmask both UIKit and SwiftUI elements.

#### Masking and Unmasking behaviors on a parent view

Masking is applied recursively to all children unless a specific rule has been applied to 1 of them. In this case the rule will again be applied recursively.

**Examples**

Let's say we have the following structure:

```plaintext
   (parent view)
   ------------------------
   |    "Parent Label"    |
   |                      |
   | (child view)         |
   | -------------------- |
   | |   "Child Label"  | |
   | |                  | |
   | -------------------- |
   |                      |
   ------------------------
```

* If no rule is applied: everything is masked including "Parent Label" and "Child Label"
* If parent view is unmasked and no other rule is applied: both "Parent Label" and "Child Label" are unmasked
* If parent view is unmasked and child view is masked: "Parent Label" is unmasked and "Child Label" is masked
* If default masking is set to false and no other rule is applied: "Parent Label" and "Child Label" are unmasked
* If default masking is set to false and parent view is masked: both "Parent Label" and "Child Label" are masked

And so on as explained in the [Masking rules](#masking-rules).

**Unmask an instance when its type is masked** If you've masked `UILabel` **types** specifically or are in default masking, and you call `unmask(view: myLabel)`, all `UILabel` **instances** will be masked except `myLabel`.

**Mask an instance when its type is unmasked** If you've unmasked `UILabel` type specifically or are using the default masking, and you call `mask(view: myLabel)`, no `UILabel` will be masked except `myLabel`.

### Implementation recommendations

* UIKit

  **Masking operations should always be performed before the target view is added to the window to avoid any Personal Data leak**. For instance, you can set up masking in the `didFinishLaunching` callback of your app or if you need to change masking behavior while your app is launched, it can be done in `loadView`, `viewDidLoad` or `viewWillAppear` if you use a `ViewController`.

* SwiftUI

  Masking operations should be done as early as possible, in the setup of your app for masking by types for instance. For masking by instance, you can do it in the body of your `View` by calling the modifier `csMasking`.

### Keeping track of what is masked

The SDK doesn't provide a list of what is currently masked, if you need to keep track, you probably will have to write your specific wrapper.

### Masking operations performance impact

Repeating the same operation has little to no impact. For example, you can call `unmask(viewsOfType: UIButton.self)` multiple times without impacting the SDK or your app performance.

## Event-Triggered Replays (ETR)

Event-Triggered Replays (ETR) is an add-on feature that allows you to selectively collect specific screens or sessions for Session Replay, based on predefined custom events. Once configured, ETR events can be used to filter specific replays, or view these events as they happen in the player event stream.

To enable ETR, contact your CSM or Implementation Manager. For more information, see [Event-Triggered Replays ↗](https://support.contentsquare.com/hc/en-us/articles/4412027865874) in the Help Center.

Note

When ETR is enabled, the SDK collects data for the entire session even if none of the ETR methods described below are called. After collection, only the sessions or screens that match the global replay sampling rate or are associated with ETR events are retained.

As a result, you may notice a network activity during all sessions, regardless of whether ETR has been triggered or not.

Warning

To protect end-user personal data, ensure that appropriate masking configurations are applied to all screens even the ones that are not associated with ETR events.

### Trigger Replay for the Session

Use the following function to trigger Session Replay for the entire current session, with the parameter `name` as "ETR event".

```swift
CSQ.triggerReplayForCurrentSession(name: name)
```

### Trigger Replay for the Screen

Use the following function to trigger Session Replay for the entire current screen, with the parameter `name` as "ETR event".

```swift
CSQ.triggerReplayForCurrentScreen(name: name)
```

## Advanced features for Experience Monitoring

Note

These features are only available for Experience Monitoring Mobile customers. Reach out to your Contentsquare contact for more details.

### Send user identifier

Contentsquare provides the ability to search for session(s) associated with a specific visitor, based on an identifier: email, phone number, customer ID… As these values are typically personal data, from the moment the SDK is collecting the User Identifier, we immediately encode the value using a hashing algorithm so that the information is hidden and can never be accessed.

Warning

**Note about Privacy Manifest:** If you decide to use User Identifier to collect new data or use already collected data but for a different purpose, it is up to you to update the privacy practices described in your app manifest accordingly. Here are some common examples of data collected via User Identifier: Email, Other user contact info, etc. See [Purpose of processing and categories of personal data collected](../privacy/#purpose-of-processing-and-categories-of-personal-data-collected) already described in the Contentsquare iOS SDK Privacy Manifest.

Use the following code to send a user identifier:

```swift
CSQ.sendUserIdentifier("any_identifier")
```

When called, the SDK will log:

```plaintext
CSLIB ℹ️ Info: User identifier hashed sent {value}
```

### Sending a user identifier for each session

You may want to send the user identifier for each session. While triggering the user identifier at app launch will cover most cases, it will not be enough. A session ends start 30 minutes after the last event. See Session definition section for more information.

That is why we also recommend sending the user identifier every time the app enters foreground.

You can use [`didBecomeActiveNotification` notification ↗](https://developer.apple.com/documentation/uikit/uiapplication/1622953-didbecomeactivenotification) and [`sceneDidBecomeActive(_:)` callback ↗](https://developer.apple.com/documentation/uikit/uiscenedelegate/3197915-scenedidbecomeactive) to detect foreground and trigger a `sendUserIdentifier()`.

### Limitations

* User identifier max length is 100 characters (if the limit is exceeded, the user identifier will not be handled and you will see an error message in the Console/Logcat)
* Only the first 15 user identifiers per view will be processed on server side
* The SDK will trim and lowercase user identifier
* User identifier event are not visible in Log visualizer

## Integrations

Contentsquare provides the ability to retrieve the link of the replay to be attached to other vendors such as Voice of Customer or Crash reporting tools.

### Get current replay Link

Generate a link to the replay of the current session. This link can be pushed to any internal or 3rd-party tool as a custom property/variable. For instance, you can add the replay link to:

* Each user request sent to your customer service tool
* Each user voting in your Voice of Customer tool
* Each user session in your App Performance, Observability tool
* ...

Note

The replay link can also be accessed from the in-app features settings. See [Access the Replay](#access-the-replay)

You can register to replay link creation and updates using the following API:

```swift
CSQ.metadata.onChange {
    print("CSQ current replay link is \($0.sessionReplayURL ?? "not set")")
}
```

Warning

You can only have one callback at a time. Calling this API twice will override the previous callback.

When a new link is available, the SDK logs:

```plaintext
CSLIB ℹ️ Info: SessionReplay link updated: https://app.contentsquare.com/quick-playback/index.html?pid={projectId}&uu={userId}&sn={sessionNumber}&recordingType=cs
```

Warning

For the callback to be called, the session has to be tracked (included in tracked users and not opted-out) and a first screenview event must have been sent, since Session Replay only starts after the first screenview event.

If you have a Contentsquare account, you can use this link to directly watch your current Session Replay on the Contentsquare Platform.

The replay will be **available within 5 minutes**. Only the "ended screen views" are processed *(we know a screenview is ended when we start receiving data for the next screenview)*. This means that you will be able to replay the session up to the previous screenview if the session is still running.

## How Session Replay works

### Initialization

Sessions can be collected for Session Replay if the Session Replay feature has been enabled for your project and the session matches the collection criteria.

The following conditions will have an impact on which sessions will be collected:

* **User consent**: The users have given their **consent** (if required)
* **Collection rate**: The session is being **drawn for collection** (see Collection Rate below)
* **Compatibility**: The **OS version** is supported.
* **App version**: The **app version** is not part of the block list (see App version block list below)

Note that, when Session Replay is turned off, no content specific to Session Replay is collected whatsoever. Also note that the standard Contentsquare analytics tracking remains unaffected by this.

#### Collection rate

Session Replay collection is based on a percentage of the total sessions. By default Session Replay collection is disabled.

During the early access phase, the percentage or collected sessions will be set to 1% at the beginning. It will then be adjusted according to:

* The traffic on your app
* The volume of collected sessions set in the contract

#### Compatibility

See [Compatibility](../compatibility/).

#### App version block list

Contentsquare team can add versions of your app in the block list to make sure Session Replay does not start on these versions. This is done when a problem is discovered on a specific version of your app, such as a Personal Data for which the masking was forgotten. This allows to keep Session Replay to work on the other app versions (especially the new ones with the fix).

### Data Collection

The SDK monitors the application lifecycle events and the view hierarchy, and generates Session Replay data from the behavior of the app, the content of the screen and the interaction of the user. These events are then locally stored, and eventually sent to our servers in batches. We then aggregate that data to create usable visual information into our Web Application, which you use to gather insights.

Tip

Session collection will start at the 1st screenview event.

### Quality levels

Session Replay has three quality levels available: High, Medium, Low. Quality level is defined by the frame rate and the image quality. The quality level is defined independently for **Wi-Fi** and **Cellular Network**. The default level for both is **Medium**. For more information on network data consumption see: [Performance Impact section](#performance-impact).

Note

If you require a particular setting, reach out to your Contentsquare contact that will make the adjustment in the project configuration.

#### Quality level examples

Select the image to open it in full size and GIF format.

[![](https://docs.contentsquare.com/_astro/sr-quality-ios-static.BEoNTwj0_1upc5L.webp)](https://docs.contentsquare.com/_astro/ios-quality-level.CjUY6Jo0.gif)

### Network and Storage

By default, Session Replay data can be sent over **cellular network**. If the particular context of your app or users requires a very limited impact on cellular data consumption, **sending data over cellular network can be completely disabled**. Once disabled, data will be sent when the device is using Wi-Fi.

Reach out to your Contentsquare contact that will make the adjustment in the project configuration.

#### Storage

Before being sent, data is stored in local files on disk up to 30MB on iOS and 20MB on Android. If the limit is reached, Session Replay is stopped. It will restart at the next app launch once the SDK is able to send the data.

### Requests

The maximum request size is 1Mbyte.

Requests are sent:

* Every 10 seconds
* At app hide
* When [Replay Link API](#get-current-replay-link) is called

## Performance impact

We always strive to be non-intrusive, and transparent to the developers of the client app. We apply this rule on the performance as well. These are the technical specifics we can share on performance, if you have any questions feel free to reach out to us.

### Performance impact mitigation

Most operations related to Session Collection are performed on background threads, for the impact on the main thread to be minimal. To make sure of this, we run performance tests on testing applications, using XCTMetric and Hitch Time Ratio measures.

We also set up mechanisms that stop the Session Collection if we detect the feature is using too much memory.

We defined our network strategy to have the lesser impact on CPU and battery of the users devices. We measure these impacts, using Apple Dev Tools, for each release of our SDK.

Session Collection will also stop if we use up to 30Mbytes of local storage.

You can reduce the quality level if you want to favor performance impact over quality.

### Performance test results

The following performance results were obtained under the following conditions:

| Condition | Value |
| - | - |
| Device model | iPhone 7 |
| iOS version | 15.7.8 |
| Test App built using Xcode version | 14.3.1 |
| Test App built with Swift version | 5.8 |
| Quality level | High |
| Default Masking State | Disabled |

We conducted the tests using a default `Master-Detail` app built using AdHoc distribution with no app thinning and with Swift symbols stripped. In the app, the SDK was making calls to the Public APIs, running and collection data in its default state.

Note

The numbers provided below will vary depending on the user's device, iOS version, SDK version, as well as if you use Swift or not, which Swift version you use if you do, if you enabled bitcode or not and which options you used when building your IPA for App Store distribution.

Note

Screens built with SwiftUI produce, on average, 120% of the SR data produced by similar screens built with UIKit.

| Property | Value |
| - | - |
| Max CPU overhead | <10% |
| Max RAM usage | <5MB |
| Session Replay data transmitted over network during 1 minute of use | |
| - with "High quality" level | <2Mbytes |
| - with "Medium quality" level | <1Mbytes |
| - with "Low quality" level | <900Kbytes |

## Known limitations

### Color

[Init `UIColor` with Color ↗](https://developer.apple.com/documentation/uikit/uicolor/3550899-init) doesn't work in dark mode.

**Workaround:** Use [`UIColor.init(named:)` ↗](https://developer.apple.com/documentation/uikit/uicolor/2877380-init).

### Maps

Maps can potentially contain Personal Data and are therefore replaced by a placeholder with no ability to un-mask them currently.

### NSAttributedString handling

The extracted text doesn’t support the `expansion`, `obliqueness`, `paragraphStyle` and `textEffect` attributes.

### Multiline labels

UIKit labels configured for multiple lines may overflow their bounding box, depending on the font and text length. A longer text increases the risks of causing an overlap with elements below (e.g.: other text, or image).

### Multiple windows

Multi-window is not supported.

### System elements capture

System elements are not collected. For instance, we do not collect:

* `UIImagePickerController`

* `SKStoreReviewController`

* All `AVFoundation` components such as:

  * `AVPlayerViewController` for videos
  * `AVCaptureVideoPreviewLayer` for camera

### System elements masking

Bottom line is that masking/unmasking any element is possible only if you have a reference or you know the type of the element that you want to mask.

Since the access to the instance/type of Alert and Menu components is not documented in [Apple documentation ↗](https://developer.apple.com/documentation/uikit/uialertcontroller#overview), developers cannot directly mask/unmask these components or parts of them with CS public masking APIs. However, there are workarounds to mask/unmask them, as described below.

#### Alerts

##### UIKit

A possible workaround to fully mask a UIKit Alert is to pass the `UIAlertController` `view` property to the `mask(view:)` and `unmask(view:)` APIs.

##### SwiftUI

SwiftUI Alerts cannot be specifically masked or unmasked.

If the [default masking](#default-masking) is `false` and you want to mask an alert, a possible workaround is to mask all the app by changing the default masking to `true` when the alert appears and back to `false` when it disappears.

Warning

There is no workaround to unmask only an alert if the default masking is `true`.

#### Menus

Menus cannot be specifically masked or unmasked.

If the [default masking](#default-masking) is `false` and you want to mask a menu, a possible workaround is to mask all the app by changing the default masking to `true` when the menu appears and back to `false` when it disappears.

Warning

There is no workaround to unmask only a menu if the default masking is `true`.

### System images

```swift
Image(systemName: "pencil")
```

On iOS 17.0 and later, system images are considered as text within the masking rules. Any system-provided images used in your app's UI is subject to the same privacy and masking regulations as text content.

### Animations

Animations lasting longer than 35 ms may be partially rendered in the replay, potentially resulting in a broken or incomplete layout for the rest of the animation. This does not affect the rest of the session, and the app will render correctly again on the next user interaction or detected animation.

### iOS 26 rendering

Rendering limitation of apps compiled with Xcode 26 and running on iOS 26 include:

* Some SwiftUI button backgrounds are not displayed correctly
* Liquid Glass design and transparency effects are not faithfully rendered

### Apple Pay button

The Apple Pay button is not rendered in the replay and appears as blank space.

## Troubleshooting

### Requests are not sent from an iOS simulator

If you struggle to watch a replay collected on an emulator/simulator, it may be due to some network constraints applied on your computer (VPN, company network restrictions, etc.). Check your configuration or use a real device.

See [Requests are failing](../troubleshooting/#requests-are-failing) for more information about our endpoints and requests.

### Very long session on an iOS simulator

If you leave the simulator/emulator running with the app in foreground, the session will not end, even if you are inactive. This can lead to artificially long sessions (several hours). To better emulate actual user behavior, put the app in background or kill it.

Note

An app kill **does not end a session**.\
See [Session definition](../data-collection/#session-definition).

### Unmasking with CS InApp features is broken

When activating the toggle in CS InApp to unmask elements in Session Replay,it can sometimes give a strange result with some elements still masked, it's **especially true for SwiftUI**. This is due to the fact that unmasking with InApp features acts differently than when done in the code by the app developer. To ensure a session is fully unmasked as it would be if it was done in the code of the app, follow these steps:

* Toggle the "Unmask ..." switch
* Navigate to a different screen
* Go back to the previous screen
* Put the app in background
* Wait for the session timeout
* Re-open the app

From this point on, you will have a fully unmasked session.

### Uncaught exception due to NSAttributedString

If the following exception is encountered: `NSInvalidArgumentException: -[__NSCFType CGColor]: unrecognized selector sent to instance`,

It is probably due to an Objective-C bridging issue in `Foundation`, which makes it possible to pass `CGColor` instances as values for the `foregroundColor` and `strikeThroughColor` attributes of a `NSAttributedString`.

While a `UILabel` with such an attributed string will be displayed correctly, its `textColor` attribute will have the wrong type, and the SDK won't be able to handle it, causing a crash in Session Replay.

Because of this, it is important to ensure that actual `UIColor` instances are passed as values, as per the [official documentation ↗](https://developer.apple.com/documentation/foundation/nsattributedstring/key/1533563-foregroundcolor).
