Skip to content

Commit 76041cc

Browse files
authored
Fix(preventScreenshot): fix logic on iOS (#39)
**Bug fixed** : Opening Control Center during app launch causes the screenshot prevention overlay to get stuck visible on iOS. The overlay remains on screen while the app is fully active, producing a white screen over the app content. Subsequent background/foreground transitions are also affected. **File**: RNASAppLifecyleDelegate.swift Change: Added `private var becomeActiveObserver: NSObjectProtocol?` Why: Holds a reference to the NotificationCenter observer for proper cleanup in deinit ──────────────────────────────────────── Change: Added `_ = launchScreen.view` in the lazy `launchScreenWindow` initializer Why: Forces the storyboard view controller to load its view eagerly, so the overlay renders its content immediately instead of showing a blank white window on first display ──────────────────────────────────────── Change: In `didFinishLaunchingWithOptions`: subscribe to `UIApplication.didBecomeActiveNotification` via NotificationCenter Why: This notification is posted directly by UIKit at the OS level, independently of Expo's delegate chain. It fires reliably when the app becomes active even during launch, where the delegate method is dropped ──────────────────────────────────────── Change: Added deinit to remove the NotificationCenter observer Why: Prevents a memory leak if the delegate is ever deallocated ──────────────────────────────────────── Change: Replaced `launchScreenWindow?.makeKeyAndVisible()` with `launchScreenWindow?.isHidden = false` in `applicationWillResignActive` Why: `makeKeyAndVisible()` steals key window status from the main app window, which triggers cascading `willResignActive` events from internal app activity, causing the overlay to re-appear unexpectedly. The window's .alert + 2 level already guarantees it appears on top without needing key window status ──────────────────────────────────────── Change: Added a comment to `applicationDidBecomeActive` Why: Kept as belt-and-suspenders fallback in case the NotificationCenter observer is not set up (e.g. preventRecentScreenshots is toggled at runtime)
1 parent e5040f4 commit 76041cc

1 file changed

Lines changed: 27 additions & 6 deletions

File tree

ios/RNASAppLifecyleDelegate.swift

Lines changed: 27 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,43 +2,64 @@ import ExpoModulesCore
22
import UIKit
33

44
public class RNASAppLifecycleDelegate: ExpoAppDelegateSubscriber {
5+
private var becomeActiveObserver: NSObjectProtocol?
6+
57
private lazy var launchScreenWindow: UIWindow? = {
68
let window = UIWindow(frame: UIScreen.main.bounds)
79
let launchScreen = UIStoryboard(name: "SplashScreen", bundle: nil).instantiateInitialViewController()!
810
window.rootViewController = launchScreen
911
window.windowLevel = .alert + 2 // React Native alert uses .alert + 1
12+
_ = launchScreen.view // Force view load so it renders correctly on first show
1013
return window
1114
}()
1215

1316
public func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
1417
if(isPreventRecentScreenshotsEnabled()) {
1518
application.ignoreSnapshotOnNextApplicationLaunch()
19+
20+
// Subscribe directly to UIKit's notification instead of relying solely on the
21+
// Expo delegate chain, which can delay or drop didBecomeActive during launch.
22+
becomeActiveObserver = NotificationCenter.default.addObserver(
23+
forName: UIApplication.didBecomeActiveNotification,
24+
object: nil,
25+
queue: .main
26+
) { [weak self] _ in
27+
self?.launchScreenWindow?.isHidden = true
28+
}
1629
}
1730

1831
if(isDisablingCacheEnabled()){
1932
clearAndDisableCache()
2033
}
2134

22-
2335
return true
2436
}
2537

38+
deinit {
39+
if let observer = becomeActiveObserver {
40+
NotificationCenter.default.removeObserver(observer)
41+
}
42+
}
43+
2644
private func clearAndDisableCache() {
2745
URLCache.shared.removeAllCachedResponses()
2846
URLCache.shared = URLCache(memoryCapacity: 0, diskCapacity: 0, diskPath: nil)
2947
}
30-
48+
3149
public func applicationWillResignActive(_ application: UIApplication) {
3250
if(!isPreventRecentScreenshotsEnabled()) {
3351
return
3452
}
35-
53+
3654
// https://developer.apple.com/documentation/uikit/app_and_environment/scenes/preparing_your_ui_to_run_in_the_background
37-
38-
launchScreenWindow?.makeKeyAndVisible()
55+
// Use isHidden instead of makeKeyAndVisible to avoid stealing key window status,
56+
// which can cause cascading willResignActive events and a stuck visible overlay.
57+
launchScreenWindow?.isHidden = false
3958
}
40-
59+
4160
public func applicationDidBecomeActive(_ application: UIApplication) {
61+
// Belt-and-suspenders: also hide via delegate in case the notification observer
62+
// wasn't set up in time (e.g. preventRecentScreenshots disabled then re-enabled).
4263
launchScreenWindow?.isHidden = true
4364
}
4465
}

0 commit comments

Comments
 (0)