The first time an iOS app needs to protect on-screen content, patient records in a telemedicine app, account balances in a banking app, draft legal documents in a discovery viewer, the engineer who picks up the ticket usually finds the same pattern in three Stack Overflow answers and ships it: subscribe to UIScreen.capturedDidChangeNotification, observe UIScreen.main.isCaptured, blur the protected view when capture is true, unblur when it returns false.
That pattern catches one specific case: a user who initiates an iOS screen recording from Control Center while the protected view is on screen, and only that case. Four other capture vectors leave the pattern intact and the content visible. This post is the tour of all five vectors, what each one bypasses, and what a defensible screen-capture defense actually looks like in 2026. For how these vectors map onto the attacker capabilities the SDK is designed around, see the ScreenGuard threat model.
Vector 1, User-initiated screen recording (the case the naive pattern catches)
User taps the Control Center, taps the screen-recording button, returns to your app, and ReplayKit starts capturing the framebuffer. UIScreen.main.isCaptured flips to true and your notification fires. Your overlay obscures the sensitive view. This vector is closed.
For most apps, this is the only vector they will hear about in support tickets, because it is the only one that produces a visible artifact (the red status bar) the user notices. The other four vectors leak silently.
Vector 2, Screenshot bursts during the overlay animation
A determined user taps the Volume + Power buttons to take a screenshot at the exact moment your overlay is animating into place. The screenshot captures the partially-obscured view. If your overlay is UIView.animate(withDuration: 0.3) (the default), there is a 300ms window where the protected content is visible and the screenshot captures it.
The fix is twofold: (a) animate to fully-opaque overlay in duration: 0 for the initial state and only animate on dismiss, and (b) listen for UIApplication.userDidTakeScreenshotNotification separately and immediately wipe in-memory state. The screenshot is in Photos either way, but at least you can detect it happened.
A more aggressive fix, used by the wallets and financial apps with the highest threat models, is to render the protected content inside a UITextField’s secure rendering canvas. This is the same path used for password fields, and iOS scrubs the canvas from the screenshot blob before the screenshot is taken. The screenshot saved to Photos shows a black rectangle where the protected content was. ScreenGuard’s StealthContainerView is exactly this: a UITextField subclass that hosts protected content inside its private canvas surface.
Vector 3, ReplayKit initiated from another app
If a user has any app installed that uses RPScreenRecorder (broadcast apps, screen-recording utilities, accessibility tools), that app can start system-wide screen capture programmatically. The user goes to that app, taps “start broadcasting”, returns to your app. UIScreen.main.isCaptured flips to true, same as Vector 1.
But here’s the gap: RPScreenRecorder.shared().isRecording is a property of the third-party app’s process. Your app cannot see it. You only see UIScreen.isCaptured. And on some iOS versions, the notification fires after a delay of up to 800ms, enough for a few frames of unobscured content to be captured before your overlay appears.
The defense is to render protected content inside the hardware-shielded canvas continuously, not just reactively. If the canvas is always on, there is no delay-window where unobscured content is captured. The reactive overlay becomes a backup defense, useful when the canvas approach regresses across iOS versions (which it has, repeatedly).
Vector 4, AirPlay mirroring
A user enables AirPlay mirroring to an Apple TV (legitimate use case for telemedicine consultations), or to a hostile receiver that has been set up to look like an Apple TV (less common but documented). UIScreen.main.isCaptured is not always true for AirPlay mirroring, the property’s semantics changed between iOS 14 and iOS 16, and on iOS 14 AirPlay mirroring leaves it false. UIScreen.screens.count > 1 is the reliable signal across iOS versions.
The fix: subscribe to both UIScreen.capturedDidChangeNotification and UIScreen.didConnectNotification / didDisconnectNotification, treat any second screen as a capture event for protected views, and apply the same overlay/canvas protection.
Vector 5, Physical-camera photo of the screen
A user, or someone over the user’s shoulder, or a colleague the user trusted, or a maintenance worker passing through a hospital ward, takes a photograph of the screen with a phone camera. This vector bypasses every software defense. The framebuffer is not involved; the pixels are read by a CMOS sensor in someone else’s hand.
There is no software fix for Vector 5. There is, however, an attribution mechanism: invisible forensic watermarks rendered on top of the protected content. The watermark is opacity-8% text tiled across the view, encoding the user’s ID, session ID, device ID, IP, and a timestamp, signed with an HMAC key. The user looking at the screen does not see the watermark (or sees it as faint noise their brain edits out). A camera photo captures the watermark with enough fidelity that Vision-framework OCR can recover the text and a constant-time HMAC compare can attribute the leak to a specific user-session.
The watermark does not prevent the leak. It changes the cost of the leak: the leaker knows that the photo can be traced back. For high-stakes content, patient records under HIPAA, transactions under FINRA, M&A documents under privilege, the deterrent value is the entire point. The leak still happens occasionally. The leaker is identifiable; the next leak is rarer; the audit log shows who was looking at what when.
ScreenGuard’s SGWatermarkGenerator is the primitive; SGWatermarkVerifier.scan(image:) is the OCR-and-attribute call you make from your back-office when a screenshot shows up on social media.
The defensible stack, end-to-end
A defensible screen-capture defense in 2026 layers all of the above:
- Hardware shield (continuous): render protected content inside a
UITextField-secure canvas so the framebuffer captured by ReplayKit / AirPlay / screenshots is already scrubbed at the OS level. ScreenGuard’sStealthContainerView. - Reactive overlay (backup): on capture detection, animate a black/blur overlay on top. Belt and suspenders for the cases where the hardware shield regresses.
- Capture event monitoring:
UIScreen.capturedDidChangeNotification+userDidTakeScreenshotNotification+didConnectNotificationfor all five vectors. Logged to the audit chain. - Forensic watermarks (continuous): per-user HMAC-signed identity tiled across the protected view. Invisible to the user, recoverable from a leaked photo.
- OCR verifier (post-leak): when a leak surfaces, run
verifier.scan(image:)to attribute it to a specific user-session.
The naive pattern catches Vector 1. The defensible stack catches Vectors 1–4 with the hardware shield and reactive overlay, and attributes Vector 5 forensically. The cost difference is a single SDK integration line plus a view.makeProtected() wrapper around your sensitive view.
See /sdk/screenguard for the marketing summary, /docs/screenguard for integration, and the companion post on forensic HMAC watermarks for the full watermark threat model.
Comments
Nothing yet. Say the first thing.
Sign in to join the conversation.