Observe capture state, save PNGs, and block screenshots
One.ScreenCapture watches whether the screen is recorded or mirrored,
notifies after a screenshot lands, saves a PNG of the app window or one
view, and hides content from screenshots and recordings. Reach for it
for sensitive screens: receipts, tickets, health data, and audit trails.
On iOS state comes from the scene capture state and screenshots hide
behind a secure-layer window move, with rendering through
UIGraphicsImageRenderer. On Android state comes from window recording
visibility on API 35+, screenshots notify through the API 34 activity
callback, prevention sets FLAG_SECURE, and captures copy through
PixelCopy on API 26+. On web state reads unspecified, prevention does
nothing, and captures reject.
import { useEffect, useState } from 'react'import { One } from 'one'
function useCaptureState() { const [captured, setCaptured] = useState(false)
useEffect(() => { One.ScreenCapture.getState().then((state) => { setCaptured(state === 'active') }) return One.ScreenCapture.addStateListener((state) => { setCaptured(state === 'active') }) }, [])
return captured}State is active while recorded or mirrored, inactive otherwise, and unspecified where the platform cannot tell. The listener reports changes after registration; the one-shot read seeds the first render.
getState resolves from the active window scene and rejects without
one. The screenshot listener receives a Unix millisecond timestamp
stamped when the notification arrives, after the image is captured, so
it can audit but never prevent or redact that shot. On Android the
callback is activity-scoped: ADB and instrumentation captures do not
trigger it, and below API 34 it stays silent.
captureWindow saves the current app window as a PNG while the app is
foregrounded: a file:// URI under the screen-capture cache folder,
pixel dimensions at the window’s native scale, and the PNG byte size.
The PNG holds the window’s rendered content only, excluding system UI
from other windows such as the status bar. It never triggers the
screenshot listener. captureView renders one mounted view subtree at
call time from its native tag: set collapsable={false} on the target,
since Fabric can otherwise mount children beside it and silently omit
them. On Android the tag must resolve to a view attached to the current
activity’s window. Both reject without a scene, on render, encode, or
file failures, and below API 26 on Android; the view call adds an
unmounted-tag error. Delete the file when finished.
prevent hides content once every held key agrees: on iOS the window
moves behind a secure layer so shots come out blank, with a black
overlay while captured; on Android the window gains FLAG_SECURE,
which also blanks the app switcher preview. Keys default to default;
prevention arms on the first held key and releases when the last key is
allowed, so overlapping screens never conflict. Allowing an unheld key
does nothing. While mounted, prefer usePreventScreenCapture(key),
which prevents on mount and allows on unmount.
| Methods: observing | Type | Description |
|---|---|---|
getState() => Promise<ScreenCaptureState>Active, inactive, or unspecified, right now. | () => Promise<ScreenCaptureState> | Active, inactive, or unspecified, right now. |
addStateListener(onChange: (state: ScreenCaptureState) => void) => () => voidHear state changes. Returns a remover. | (onChange: (state: ScreenCaptureState) => void) => () => void | Hear state changes. Returns a remover. |
addScreenshotListener(onScreenshot: (timestampMs: number) => void) => () => voidHear after a screenshot. Returns a remover. | (onScreenshot: (timestampMs: number) => void) => () => void | Hear after a screenshot. Returns a remover. |
| Methods: capturing | Type | Description |
|---|---|---|
captureWindow() => Promise<ScreenCaptureResult>Save the app window as a PNG file. | () => Promise<ScreenCaptureResult> | Save the app window as a PNG file. |
captureView(viewTag: number) => Promise<ScreenCaptureResult>Save one mounted view subtree as a PNG file. | (viewTag: number) => Promise<ScreenCaptureResult> | Save one mounted view subtree as a PNG file. |
| Methods: preventing | Type | Description |
|---|---|---|
prevent(key?: string) => Promise<void>Hide content. Arms on the first held key. | (key?: string) => Promise<void> | Hide content. Arms on the first held key. |
allow(key?: string) => Promise<void>Release one key. Last key shows content. | (key?: string) => Promise<void> | Release one key. Last key shows content. |
isPrevented() => Promise<boolean>Whether content hides right now. | () => Promise<boolean> | Whether content hides right now. |
The usePreventScreenCapture hook is a named export from one.
| Capture result | Type | Description |
|---|---|---|
uristringThe PNG file, under the capture cache folder. | string | The PNG file, under the capture cache folder. |
widthnumberPixels at the native scale. | number | Pixels at the native scale. |
heightnumberPixels at the native scale. | number | Pixels at the native scale. |
sizenumberPNG file size in bytes. | number | PNG file size in bytes. |
| iOS | Android | Web | |
|---|---|---|---|
| State | Scene value | API 35 plus | Unspecified |
| Shot notice | After capture | API 34 plus | Never |
| Capture | Window render | Pixel copy | Rejects |
| Prevent | Secure layer | Secure flag | No effect |
import { usePreventScreenCapture } from 'one'
function Receipt() { usePreventScreenCapture('receipt') return null}Mounting arms prevention for the receipt key and unmounting releases it. Overlapping guarded screens hold their own keys without conflict.
import { useEffect } from 'react'import { One } from 'one'
function useScreenshotAudit(log: (at: number) => void) { useEffect(() => { return One.ScreenCapture.addScreenshotListener((timestampMs) => { log(timestampMs) }) }, [log])}The timestamp marks when the notification arrived, which trails the capture by a small margin. Pair the log with the visible screen name for a useful audit row.
import { useRef } from 'react'import { Button, findNodeHandle, View } from 'react-native'import { One } from 'one'
function ShareCard({ share }: { share: (uri: string) => Promise<void> }) { const target = useRef<View>(null)
async function capture() { const tag = findNodeHandle(target.current) if (tag == null) throw new Error('card is not mounted') const image = await One.ScreenCapture.captureView(tag) try { await share(image.uri) } finally { await One.FileSystem.delete(image.uri) } }
return ( <> <View ref={target} collapsable={false}> {/* card content */} </View> <Button title="Share card" onPress={capture} /> </> )}collapsable={false} keeps Fabric from hoisting the children out of
the captured subtree. The temp PNG is shared once, then deleted.
Edit this page on GitHub.