authorgravatar for 91240327+shivamhwp@users.noreply.github.comshivam <91240327+shivamhwp@users.noreply.github.com> 2026-10-12 20:00:52+05:30
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-10-12 20:00:52+05:30
log7b6d90757415f7897bac1a39e42cfb1331c97ee5
treeba9fd02496a963acfd9ebcf25d1a2621c649cdc7
parent5753207d53362608f281738eda3a9faf4e13b6be
signature Signed by PGP key B5690EEEBB952194

fix(mobile): iPhones on iOS 27.2 beta no longer open to a black screen (#18654)

Co-authored-by: T3 Code Test <t3code-test@example.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>

2 files changed, 30 insertions(+), 5 deletions(-)

apps/mobile/modules/t3-native-controls/ios/T3LayoutMetricsView.swift+12
...@@ -35,9 +35,21 @@ final class T3LayoutMetricsView: ExpoView {...@@ -35,9 +35,21 @@ final class T3LayoutMetricsView: ExpoView {
35 override func didMoveToWindow() {35 override func didMoveToWindow() {
36 super.didMoveToWindow()36 super.didMoveToWindow()
37 lastMetrics = nil37 lastMetrics = nil
38 assumeNoHingeIfSilent()
38 publishMetrics()39 publishMetrics()
39 }40 }
4041
42 /// UIKit only sends hinge updates on devices that have a hinge. Without this,
43 /// an ordinary iPhone never publishes and the navigator never attaches.
44 private func assumeNoHingeIfSilent() {
45 guard window != nil, hasHinge == nil else { return }
46 DispatchQueue.main.asyncAfter(deadline: .now() + 0.25) { [weak self] in
47 guard let self, self.window != nil, self.hasHinge == nil else { return }
48 self.hasHinge = false
49 self.publishMetrics()
50 }
51 }
52
41 override func layoutSubviews() {53 override func layoutSubviews() {
42 super.layoutSubviews()54 super.layoutSubviews()
43 publishMetrics()55 publishMetrics()
apps/mobile/src/native/NativeWorkspaceColumns.ios.tsx+18-5
...@@ -1,4 +1,5 @@...@@ -1,4 +1,5 @@
1import { requireOptionalNativeModule } from "expo";1import { requireOptionalNativeModule } from "expo";
2import { useEffect, useState } from "react";
2import { Platform } from "react-native";3import { Platform } from "react-native";
3import { useNativeLayoutMetrics } from "./native-layout-metrics";4import { useNativeLayoutMetrics } from "./native-layout-metrics";
45
...@@ -9,15 +10,27 @@ const nativeControls = requireOptionalNativeModule<{...@@ -9,15 +10,27 @@ const nativeControls = requireOptionalNativeModule<{
9export const NATIVE_WORKSPACE_COLUMNS_SUPPORTED =10export const NATIVE_WORKSPACE_COLUMNS_SUPPORTED =
10 Platform.OS === "ios" && Platform.isPad && nativeControls?.supportsWorkspaceColumns === true;11 Platform.OS === "ios" && Platform.isPad && nativeControls?.supportsWorkspaceColumns === true;
1112
13// Binaries without the native no-hinge fallback never report metrics on ordinary
14// iPhones running iOS 27.1 or later. Use the compact navigator rather than staying blank.
15const NATIVE_METRICS_TIMEOUT_MS = 1000;
16
12export function useNativeWorkspaceColumnsReady() {17export function useNativeWorkspaceColumnsReady() {
13 const metrics = useNativeLayoutMetrics();18 const metrics = useNativeLayoutMetrics();
19 const [timedOut, setTimedOut] = useState(false);
14 // Choose the phone's native host before attaching any screens. Older clients20 // Choose the phone's native host before attaching any screens. Older clients
15 // without the observer can immediately use the compact navigator.21 // without the observer can immediately use the compact navigator.
16 return (22 const waiting =
17 (Platform.OS === "ios" && Platform.isPad) ||23 !(Platform.OS === "ios" && Platform.isPad) &&
18 !nativeControls?.ViewPrototypes?.T3NativeControls_LayoutMetrics ||24 !!nativeControls?.ViewPrototypes?.T3NativeControls_LayoutMetrics &&
19 metrics !== null25 metrics === null;
20 );26
27 useEffect(() => {
28 if (!waiting) return;
29 const timeout = setTimeout(() => setTimedOut(true), NATIVE_METRICS_TIMEOUT_MS);
30 return () => clearTimeout(timeout);
31 }, [waiting]);
32
33 return !waiting || timedOut;
21}34}
2235
23export function useNativeWorkspaceColumnsSupported() {36export function useNativeWorkspaceColumnsSupported() {