Screenshots

Learn more about taking screenshots when an error occurs. Sentry pairs the screenshot with the original event, giving you additional insight into issues.

Sentry makes it possible to automatically take a screenshot and include it as an attachment when a user experiences an error, an exception or a crash. When a crash occurs on Apple operating systems, there is no guarantee that the Cocoa SDK can still capture a screenshot since you're technically not allowed to call any async unsafe code. While it still works in most cases, we can't guarantee screenshots for all types of crashes.

This feature is only available for SDKs with a user interface, like the ones for mobile and desktop applications. It's also limited by whether taking a screenshot is possible or not. For example, in some environments, like native iOS, taking a screenshot requires the UI thread, which often isn't available in the event of a crash. Another example where a screenshot might not be available is when the event happens before the screen starts to load. So inherently, this feature is a best effort solution.

Because screenshots may contain PII, they are an opt-in feature. You can enable screenshots as shown below:

Copied
import Sentry

SentrySDK.start { options in
  options.attachScreenshot = true
}

The beforeCaptureScreenshot also allows you to customize the behavior based on event data, so you can decide when to capture a screenshot and when not to. The callback doesn't work for crash events.

Copied
import Sentry

SentrySDK.start { options in
    options.dsn = "___PUBLIC_DSN___"
    options.beforeCaptureScreenshot = { event in
        // Return false to not capture a screenshot for the event.
        return false
    }
}

By default, screenshot masking aggressively masks potentially sensitive data to help ensure no sensitive information is captured. You can customize this behavior to fit your application's needs.

By default, screenshots mask all text content and non-bundled images by drawing a black rectangle over them. You can disable this default masking behavior (not recommended for applications with sensitive data):

Copied
options.screenshot.maskAllText = false
options.screenshot.maskAllImages = false

You can choose which type of view you want to mask or unmask by using the maskedViewClasses or unmaskedViewClasses options.

By default, Sentry already masks text and image elements from UIKit. Every child of a view that is redacted will also be redacted. The unmaskedViewClasses property has precedence over maskedViewClasses, meaning if a view class appears in both lists, it will be unmasked.

Let's say you have a custom view that you want to mask and a UILabel subclass (which normally would be masked) that you don't want to mask. You can set the options like this:

Copied
options.screenshot.maskedViewClasses = [MyCustomView.self]
options.screenshot.unmaskedViewClasses = [MyCustomLabel.self]

Note that views of classes in unmaskedViewClasses will not be redacted, but their children may still be masked.

You can also choose to mask or unmask a specific view instance by using the replay API (SentrySDK.replay) or view extensions like this:

Copied
SentrySDK.replay.maskView(view: view)
SentrySDK.replay.unmaskView(view: label)

or

Copied
view.sentryReplayMask()
label.sentryReplayUnmask()

Because of the way SwiftUI is transformed into UIKit, it will often be over-masked. A modifier like background uses the same element as an Image. In order to control the SwiftUI masking process, you need to use the sentryReplayUnmask and/or sentryReplayMask modifiers.

In this example we want to show the message, but not the user name.

Copied
@Binding var user: String

var body: some View {
  VStack {
    Text("Hello")
      .sentryReplayUnmask()
    Text("\(user)")
  }
}

In this example, we need to unmask the VStack because its background element will be masked by default. To hide the username, we need to mask it.

Copied
@Binding var user: String

var body: some View {
  VStack {
    Text("Hello")
    Text("\(user)")
      .sentryReplayMask()
  }
  .background(.blue)
  .sentryReplayUnmask()
}

To see how elements are being masked, enable the masking preview from anywhere in your app. It will display an overlay on top of the masked elements. This works on the simulator and on device, as well as within Xcode Preview.

Copied
SentrySDK.replay.showMaskPreview()

By default, the overlay will be opaque. To configure the opacity, pass the desired opacity as a parameter:

Copied
SentrySDK.replay.showMaskPreview(0.5) // 0.5 opacity to render the preview semi-transparent

Make sure not to accidentally include this in your release build—for example, by wrapping it in a #if DEBUG block.

Copied
#if DEBUG
  SentrySDK.replay.showMaskPreview()
#endif

To preview masking during the design phase of your SwiftUI views, use the sentryReplayPreviewMask modifier.

This view modifier works on the simulator and on device, as well as within Xcode Preview. Therefore we recommend to apply the modifier only in your preview code, to ensure proper masking without affecting the final release build.

Note that when you apply this modifier to a view, it will show the masking preview for the entire window containing that view, not just the view itself.

Copied
struct ContentView_Previews: PreviewProvider {
  static var previews: some View {
    ContentView()
      .sentryReplayPreviewMask()
  }
}

Screenshot rendering can be configured to improve performance. These options control how views are rendered when capturing screenshots.

Starting with SDK v8.50.0, the up-to-5x-faster view renderer V2 is used by default, reducing the impact of screenshot capture on the main thread and potential frame drops. This reduces the amount of time it takes to render the screenshot on the main thread, reducing interruptions and visual lag. Benchmarks have shown significant improvement of up to 4-5x faster rendering (reducing ~160ms to ~36ms) on older devices.

While we recommend the view renderer V2, if you are experiencing issues, you can opt out of using it:

Copied
options.screenshot.enableViewRendererV2 = false // Defaults to true

Enables up to 5x faster but incomplete view rendering. This flag controls the way the view hierarchy is drawn into a graphics context. By default, the view hierarchy is drawn using UIView.drawHierarchy(in:afterScreenUpdates:), which is the most complete way to render. Enabling this flag switches to render the underlying CALayer instead, which is faster but may lead to rendering issues with custom views.

Benchmarks have shown up to 5x faster render times (reducing ~160ms to ~30ms) on older devices.

Copied
options.screenshot.enableFastViewRendering = false // Disabled by default

If one is available, you'll see a thumbnail of the screenshot when you click on a specific issue from the Issues page.

Once you've clicked on the event ID of a specific issue, you'll be able to see an overview of all the attachments as well as associated events in the "Attachments" tab.

Was this helpful?
Help improve this content
Our documentation is open source and available on GitHub. Your contributions are welcome, whether fixing a typo (drat!) or suggesting an update ("yeah, this would be better").