diff --git a/packages/react-native/Libraries/SwiftExtensions/RCTMainWindow.swift b/packages/react-native/Libraries/SwiftExtensions/RCTMainWindow.swift index 3dff68146cb66e..cec88431f90c2b 100644 --- a/packages/react-native/Libraries/SwiftExtensions/RCTMainWindow.swift +++ b/packages/react-native/Libraries/SwiftExtensions/RCTMainWindow.swift @@ -1,4 +1,5 @@ import SwiftUI +import React /** This SwiftUI struct returns main React Native scene. It should be used only once as it conains setup code. @@ -7,11 +8,11 @@ import SwiftUI ```swift @main struct YourApp: App { - @UIApplicationDelegateAdaptor var delegate: AppDelegate - - var body: some Scene { - RCTMainWindow(moduleName: "YourApp") - } + @UIApplicationDelegateAdaptor var delegate: AppDelegate + + var body: some Scene { + RCTMainWindow(moduleName: "YourApp") + } } ``` @@ -21,25 +22,54 @@ public struct RCTMainWindow: Scene { var moduleName: String var initialProps: RCTRootViewRepresentable.InitialPropsType var onOpenURLCallback: ((URL) -> ())? + var devMenuPlacement: ToolbarPlacement = .bottomOrnament + var contentView: AnyView? + + var rootView: RCTRootViewRepresentable { + RCTRootViewRepresentable(moduleName: moduleName, initialProps: initialProps) + } + + public init( + moduleName: String, + initialProps: RCTRootViewRepresentable.InitialPropsType = nil, + devMenuPlacement: ToolbarPlacement = .bottomOrnament + ) { + self.moduleName = moduleName + self.initialProps = initialProps + self.devMenuPlacement = devMenuPlacement + self.contentView = AnyView(rootView) + } - public init(moduleName: String, initialProps: RCTRootViewRepresentable.InitialPropsType = nil) { + public init( + moduleName: String, + initialProps: RCTRootViewRepresentable.InitialPropsType = nil, + devMenuPlacement: ToolbarPlacement = .bottomOrnament, + @ViewBuilder contentView: @escaping (_ view: RCTRootViewRepresentable) -> Content + ) { self.moduleName = moduleName self.initialProps = initialProps + self.devMenuPlacement = devMenuPlacement + self.contentView = AnyView(contentView(rootView)) } public var body: some Scene { WindowGroup { - RCTRootViewRepresentable(moduleName: moduleName, initialProps: initialProps) + contentView .modifier(WindowHandlingModifier()) .onOpenURL(perform: { url in onOpenURLCallback?(url) }) +#if DEBUG + .toolbar { + DevMenuView(placement: .bottomOrnament) + } +#endif } } } extension RCTMainWindow { - public func onOpenURL(perform action: @escaping (URL) -> ()) -> some Scene { + public func onOpenURL(perform action: @escaping (URL) -> ()) -> Self { var scene = self scene.onOpenURLCallback = action return scene @@ -95,3 +125,27 @@ public struct WindowHandlingModifier: ViewModifier { } } } + +struct DevMenuView: ToolbarContent { + let placement: ToolbarItemPlacement + + var body: some ToolbarContent { + ToolbarItem(placement: placement) { + Button(action: { + RCTTriggerReloadCommandListeners("User Reload") + }, label: { + Image(systemName: "arrow.clockwise") + }) + } + ToolbarItem(placement: placement) { + Button(action: { + NotificationCenter.default.post( + Notification(name: Notification.Name("RCTShowDevMenuNotification"), object: nil) + ) + }, + label: { + Image(systemName: "filemenu.and.selection") + }) + } + } +} diff --git a/packages/react-native/React/Base/RCTUtils.m b/packages/react-native/React/Base/RCTUtils.m index 5f1e13390b37c7..4e47006a52c4d9 100644 --- a/packages/react-native/React/Base/RCTUtils.m +++ b/packages/react-native/React/Base/RCTUtils.m @@ -592,6 +592,7 @@ BOOL RCTRunningInAppExtension(void) if (scene.session.role == UISceneSessionRoleImmersiveSpaceApplication) { continue; } + #endif if (scene.activationState == UISceneActivationStateForegroundActive) { @@ -608,6 +609,24 @@ BOOL RCTRunningInAppExtension(void) UIScene *sceneToUse = foregroundActiveScene ? foregroundActiveScene : foregroundInactiveScene; UIWindowScene *windowScene = (UIWindowScene *)sceneToUse; +#if TARGET_OS_VISION + // Ornaments are supported only on visionOS. + // When clicking on an ornament it becomes the keyWindow. + // Presenting a RN modal from ornament leads to a crash. + UIWindow* keyWindow = windowScene.keyWindow; + BOOL isOrnament = [keyWindow.debugDescription containsString:@"Ornament"]; + if (isOrnament) { + for (UIWindow *window in windowScene.windows) { + BOOL isOrnament = [window.debugDescription containsString:@"Ornament"]; + if (window != keyWindow && !isOrnament) { + return window; + } + } + } + + return keyWindow; +#endif + if (@available(iOS 15.0, *)) { return windowScene.keyWindow; }