RORK LABJP
BUILD — Rork Max runs real Macs in the cloud loaded with Xcode and the iOS SDK, writing SwiftUI, compiling, reading the errors and building again. That loop, not the code generation, is what lifts the outputNATIVE — What comes out is pure Swift and SwiftUI, not React Native. Reaching AR, Metal graphics and widgets that React Native cannot touch is the real gap between this and other buildersPLATFORMS — Coverage spans iPhone, iPad, Apple Watch, Apple TV and Vision Pro, plus iMessage. Worth a look if you want to start from a watch app or an extension rather than a phone screenCOMPANION — The Rork Companion app lets you check a generated build on a real iPhone without a paid Apple Developer account, lowering the bar for trying a first project end to endPRICING — Free to start, paid plans from $25 a month, and Rork Max on the $200 Max plan. Worth working out up front how many projects it takes to earn that backDEADLINE — From August 31, 2026, Google Play requires target API level 36 or higher for new apps and updates alike. Ten days out, and the targetSdkVersion of what you generate is yours to verifyBUILD — Rork Max runs real Macs in the cloud loaded with Xcode and the iOS SDK, writing SwiftUI, compiling, reading the errors and building again. That loop, not the code generation, is what lifts the outputNATIVE — What comes out is pure Swift and SwiftUI, not React Native. Reaching AR, Metal graphics and widgets that React Native cannot touch is the real gap between this and other buildersPLATFORMS — Coverage spans iPhone, iPad, Apple Watch, Apple TV and Vision Pro, plus iMessage. Worth a look if you want to start from a watch app or an extension rather than a phone screenCOMPANION — The Rork Companion app lets you check a generated build on a real iPhone without a paid Apple Developer account, lowering the bar for trying a first project end to endPRICING — Free to start, paid plans from $25 a month, and Rork Max on the $200 Max plan. Worth working out up front how many projects it takes to earn that backDEADLINE — From August 31, 2026, Google Play requires target API level 36 or higher for new apps and updates alike. Ten days out, and the targetSdkVersion of what you generate is yours to verify
Articles/Dev Tools
Dev Tools/2026-06-23Advanced

The Private Screen That Lingers in the App Switcher — Hiding the Snapshot iOS Takes the Moment You Background Your App

When you send a React Native app generated by Rork to the background, iOS photographs the current screen for the App Switcher and writes it to disk. Journals and personal input screens linger there in plain sight. This walks through the iOS privacy overlay (why inactive, not background), Android's FLAG_SECURE, scoping it to sensitive screens only, and screenshot detection — all in working code.

Rork539React Native227Expo175Privacy8Security Design

Premium Article

One morning I opened my own journaling app, then reached to answer a call and double-tapped the home button. There, among the thumbnails in the App Switcher, was the personal line I'd been writing a moment earlier — fully legible. It wasn't anything I'd be mortified for family to see, but it hit me: this was a screen I never designed to be seen by anyone but me, and it had just been left out in the open.

The instant you send an app to the background, iOS photographs the current screen and uses it as the App Switcher preview. That image isn't held only in memory — it's written to disk. So the contents of your app sit on a home screen, before any passcode lock, for a while after you leave. Journals, manifestation notes, health data, the amount on a screen right before payment — the more a screen is meant for the owner's eyes only, the more it hurts to have it linger there.

I run several journaling and intention-tracking apps as an indie developer, and this defense of "the moment of leaving" isn't present by default in the React Native code Rork generates, nor in the Swift that Rork Max emits. This article builds the design that slips a shield in at exactly that moment — including the ordering pitfall that trips most people up first.

What you defend is the moment of leaving, not the time on screen

We tend to think of security as something that happens while a screen is displayed. But what we want to protect here is the split second of the transition as the user leaves. When your app drops out of the foreground, iOS takes a snapshot of the UI for the App Switcher. And it's captured not "after the app has fully gone to the background," but at the inactive state just before that.

React Native's AppState represents this transition with three values: active while running in the foreground, the transitional inactive that accepts no input (iOS only), and background. Pulling down Control Center, receiving a call, opening the App Switcher — all of these pass through inactive first.

Here's the first trap. If you show the shield only after the state becomes background, you miss the snapshot entirely. It was already taken back at inactive. So lock this in from the start: the entry point for your defense is inactive, not background.

iOS: show a privacy overlay on inactive

First, place an overlay at the root of the app that's transparent (effectively hidden) while active, and covers the whole screen otherwise. We keep state management lightweight — no external library, just a small AppState subscription hook.

// hooks/usePrivacyShield.ts
import { useEffect, useRef, useState } from "react";
import { AppState, AppStateStatus } from "react-native";
 
// Returns true whenever the state is NOT active (inactive / background).
// iOS captures its snapshot during inactive, so we must not wait for background.
export function usePrivacyShield(enabled: boolean) {
  const [shielded, setShielded] = useState(false);
  const appState = useRef<AppStateStatus>(AppState.currentState);
 
  useEffect(() => {
    if (!enabled) {
      setShielded(false);
      return;
    }
 
    const handle = (next: AppStateStatus) => {
      // Reveal only when we return to active.
      // Every other transition (inactive / background) falls to the covered side.
      setShielded(next !== "active");
      appState.current = next;
    };
 
    // Guard against the initial frame (launch can begin in inactive).
    setShielded(AppState.currentState !== "active");
 
    const sub = AppState.addEventListener("change", handle);
    return () => sub.remove();
  }, [enabled]);
 
  return shielded;
}

Use this hook in exactly one place at the very top of the app and lay the covering UI over everything. Don't put it per-screen: at the moment of transition there's no room to decide "which screen am I on right now," so it belongs at the root.

// components/PrivacyShield.tsx
import { View, StyleSheet, Image } from "react-native";
import { usePrivacyShield } from "../hooks/usePrivacyShield";
 
export function PrivacyShield({ enabled }: { enabled: boolean }) {
  const shielded = usePrivacyShield(enabled);
  if (!shielded) return null;
 
  // A single brand logo, centered. Draw none of the actual content.
  return (
    <View style={styles.cover} pointerEvents="none">
      <Image
        source={require("../assets/shield-logo.png")}
        style={{ width: 96, height: 96, opacity: 0.9 }}
        resizeMode="contain"
      />
    </View>
  );
}
 
const styles = StyleSheet.create({
  cover: {
    ...StyleSheet.absoluteFillObject,
    backgroundColor: "#0E1116",
    alignItems: "center",
    justifyContent: "center",
    zIndex: 9999,
  },
});

At the root, wire it up like this. Keeping enabled driven by "are we on a sensitive screen right now" lets you grow this into per-screen control later.

// app/_layout.tsx (Expo Router example)
import { Stack } from "expo-router";
import { PrivacyShield } from "../components/PrivacyShield";
 
export default function RootLayout() {
  return (
    <>
      <Stack />
      {/* Start by enabling it app-wide; scope it per-screen later */}
      <PrivacyShield enabled={true} />
    </>
  );
}

With this in place, the instant you return to the home screen or open the App Switcher, the screen flips to the single-logo shield, and the snapshot that gets captured is of the shielded state.

Thank you for reading this far.

Continue Reading

What follows includes implementation code, benchmarks, and practical content we hope you'll find useful. This site runs without ads — server and development costs are supported entirely by members like you. If it's been helpful, we'd be truly grateful for your support.

WHAT YOU'LL LEARN
If you've worried that a personal input screen stays visible in the App Switcher, you can ship an overlay that slips in at the exact moment of backgrounding — today
You'll understand why background is too late and inactive is the right trigger, and why iOS and Android defend this in completely different ways, so you never protect one platform and assume you're covered
You'll implement a production pattern that scopes FLAG_SECURE to sensitive screens only and uses screenshot detection to quietly inform the user
Secure payment via Stripe · Cancel anytime

Unlock This Article

Get full access to the rest of this article. Buy once, read anytime. This site is ad-free — your support goes directly toward keeping it running.

or
Unlock all articles with Membership →
Share

Thank You for Reading

Rork Lab is ad-free, supported entirely by members like you. We publish practical guides daily with implementation code, benchmarks, and production-ready patterns. If you've found it useful, we'd love to have you on board.

  • Copy-paste ready implementation code
  • New advanced guides published daily
  • $5/mo or $10 for lifetime access
View Membership →

Related Articles

Dev Tools2026-07-30
What Renovate may bump in an Expo app, and what it must never touch
Turning on automated dependency updates in a Rork-generated app also hands Renovate the 123 packages Expo SDK 57 pins. Measured on 2026-07-30, six of them sit a full major version behind npm latest. Here is how to generate the ignore list from the SDK instead of maintaining it by hand.
Dev Tools2026-07-28
Counting what prebuild --clean will erase before you upgrade to Expo SDK 57
A raw diff between two generated ios/ trees showed 649 changed lines; only 3 were real edits. How to count what prebuild --clean erases, and move it into a config plugin.
Dev Tools2026-07-27
When to Raise Your Minimum iOS Version — Count Leftover Branches, Not User Percentages
Judging a minimum OS bump by usage share produces the same answer every year, so the decision never happens. Here is the annotation convention, the sweep script that counts how many branches each candidate floor would retire, and what to watch for 30 days after.
📚RECOMMENDED BOOKS
Build a Large Language Model (From Scratch)
Sebastian Raschka
LLM Dev
Prompt Engineering for LLMs
Berryman & Ziegler
Prompting
AI Engineering
Chip Huyen
AI Eng
* Contains affiliate links
See all →