Repository navigation
[Pro 1.6.0 / iOS] vw/vh styles collapse to 0 after the app returns from background — getScreenDimensions() caches a 0×0 sample while the scene is backgrounded #664
Description
What happened?
After the app spends a while in the background and comes back, every vw/vh-based style resolves to 0: w-[calc(50vw-18px)] buttons shrink to their labels, w-[calc(100vw-24px)] containers collapse so their children pile up in the middle, size-[60vw] images vanish. Styles sized from React Native's useWindowDimensions on the same screens are unaffected, and the layout snaps back on the next device rotation or theme flip. Reported by several production users on iPhone; Android is unaffected.
Mechanism (read from the shipped 1.6.0 sources):
vwcompiles tort.screen.width * N/100, so it is entirely driven by the native runtime'sscreen.ios/NativePlatform+ios.swiftgetScreenDimensions()readsRCTPresentedViewController().view.window?.frameand returnsDimensions(width: 0, height: 0)when that is nil ("this should never happen").getPixelRatio()falls back to1.0on the same path.RCTPresentedViewController()isRCTKeyWindow().rootViewController, andRCTKeyWindow()only accepts a scene that isforegroundActive/foregroundInactive. While the scene is.backgroundit returns nil, so the sample is 0×0.NativePlatformListener+ios.swiftre-samples onUIDevice.orientationDidChangeNotification,RCTWindowFrameDidChangeNotification,RCTUserInterfaceStyleDidChangeNotification, and (iOS 17+)window.registerForTraitChanges([UITraitUserInterfaceStyle.self]). The last one has no app-state guard and callsonNativePlatformChange()directly. AfterdidEnterBackground, iOS flips the window's appearance light↔dark to render the app-switcher snapshots in both schemes (the same mechanism React Native guards against inRCTSurfaceHostingView traitCollectionDidChange:for Appearance addChangeListener handler is called when app goes to background with wrong color scheme react/react-native#28525), so the trait closure fires with the scene backgrounded and the 0×0 runtime is cached. Physical rotation (including face-up/face-down, since react-native-screens starts device-orientation notifications) reaching a backgrounded process does the same through the orientation publisher.- Nothing re-samples on
UIApplication.didBecomeActiveNotification, so the zero persists until the next orientation or trait event.
This is the same bug unistyles had in jpudysz/react-native-unistyles#527 ("Runtime is equals to 0 on appState change"), fixed there in #540 by returning the cached last-known screen when the presented view controller is unavailable in the background. Pro 1.6.0 still ships the pre-fix code.
Suggested fix: never persist a nil-window sample — return the cached last-known screen/pixelRatio (or the window scene's screen bounds when there is no cache yet) instead of zeros — and add UIApplication.didBecomeActiveNotification to the merged publishers so anything sampled during a transition is re-checked once the app is active. Optionally skip the iOS 17 trait callback while UIApplication.shared.applicationState == .background, mirroring React Native's guard.
Note for reproduction: an explicit Uniwind.setTheme("dark" | "light") calls Appearance.setColorScheme, which sets overrideUserInterfaceStyle on every window, so the window trait never changes and the trait trigger is disabled. The repro therefore starts with setTheme("system").
Captured on an iPhone 17 Pro simulator (iOS 26.5) with NSLog probes added to getScreenDimensions() and the two listeners, after backgrounding the app, changing the system appearance, and foregrounding it. UIKit's own InterfaceStyle lines are interleaved:
08:09:50.660 [com.apple.UIKit:InterfaceStyle] Scene did update interface style to 1
08:09:50.661 [uniwind-probe] window trait change appState=2 <- UIApplicationStateBackground
08:09:50.661 [uniwind-probe] sample ZERO appState=2 keyWindow=0 presentedVC=0
08:09:50.705 [com.apple.UIKit:Application] Deactivation reason removed ... <- scene becomes foreground-inactive
08:09:50.708 [uniwind-probe] notification RCTUserInterfaceStyleDidChangeNotification appState=1
08:09:50.708 [uniwind-probe] sample ok 402x874 appState=1 keyWindow=1
The deferred trait update lands while the app is still in .background, the iOS 17 trait callback samples 0×0, and the runtime is updated with it. On the simulator React Native's RCTUserInterfaceStyleDidChangeNotification happened to follow 47 ms later, after the scene became foreground-inactive, and re-sampled correctly. That second notification is guarded in RCTSurfaceHostingView to be skipped while applicationState == Background, so whenever React Native's trait update lands before the state transition (which is what users see on devices), nothing heals the 0×0 runtime until the next rotation or theme change.
Steps to Reproduce
- Open the vw screen in the linked repro and press
setTheme("system"). - Press Home.
- While backgrounded, run
xcrun simctl ui booted appearance dark, thenlight(on a device: flip appearance in Control Center, or rotate the phone). - Return to the app.
Expected: the w-[100vw] bar stays full width and the on-screen log shows 100vw=<window width>.
Actual: the bar and the w-[50vw] box collapse to 0 while Dimensions.get("window").width on the same line is unchanged. One rotation in the foreground heals it.
Snack or Repository Link (Optional)
https://github.com/invivek26/uniwind-frozen-swap-repro — screen vw, README section "Third issue", probe patch in patches/uniwind-pro-1.6.0-probe.patch
Uniwind version
uniwind-pro 1.6.0
React Native Version
0.85.3 (Expo SDK 56)
Platforms
iOS
Expo
Yes
Additional information 〰
- I've searched for similar issues in this repository and found none
- I am a Uniwind Pro user
Activity
github-actions commentedon Sep 3, 2026
This issue must be reported using the official issue templates: Bug report or Bug report (Uniwind Pro).
invivek26 commentedon Sep 3, 2026
Superseded by #665 — this copy was created without the issue-form marker and auto-closed. Please ignore.