Skip to content

Commit 6901261

Browse files
authored
feat: add support for ornaments & dev menu trigger (#149)
* feat: add support for ornaments * feat: add ornaments support to second window
1 parent 8013270 commit 6901261

File tree

7 files changed

+226
-74
lines changed

7 files changed

+226
-74
lines changed

packages/react-native/Libraries/SwiftExtensions/RCTMainWindow.swift

+73-3
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import SwiftUI
2+
import React
23

34
/**
45
This SwiftUI struct returns main React Native scene. It should be used only once as it conains setup code.
@@ -21,25 +22,67 @@ public struct RCTMainWindow: Scene {
2122
var moduleName: String
2223
var initialProps: RCTRootViewRepresentable.InitialPropsType
2324
var onOpenURLCallback: ((URL) -> ())?
25+
var devMenuPlacement: ToolbarPlacement = .bottomOrnament
26+
var contentView: AnyView?
2427

25-
public init(moduleName: String, initialProps: RCTRootViewRepresentable.InitialPropsType = nil) {
28+
var rootView: RCTRootViewRepresentable {
29+
RCTRootViewRepresentable(moduleName: moduleName, initialProps: initialProps)
30+
}
31+
32+
/// Creates new RCTMainWindowWindow.
33+
///
34+
/// - Parameters:
35+
/// - moduleName: Name of the module registered using `AppRegistry.registerComponent()`
36+
/// - initialProps: Initial properties for this view.
37+
/// - devMenuPlacement: Placement of the additional controls for triggering reload command and dev menu trigger.
38+
public init(
39+
moduleName: String,
40+
initialProps: RCTRootViewRepresentable.InitialPropsType = nil,
41+
devMenuPlacement: ToolbarPlacement = .bottomOrnament
42+
) {
43+
self.moduleName = moduleName
44+
self.initialProps = initialProps
45+
self.devMenuPlacement = devMenuPlacement
46+
self.contentView = AnyView(rootView)
47+
}
48+
49+
/// Creates new RCTMainWindowWindow.
50+
///
51+
/// - Parameters:
52+
/// - moduleName: Name of the module registered using `AppRegistry.registerComponent()`
53+
/// - initialProps: Initial properties for this view.
54+
/// - devMenuPlacement: Placement of the additional controls for triggering reload command and dev menu trigger.
55+
/// - contentView: Closure which accepts rootView, allows to apply additional modifiers to React Native rootView.
56+
public init<Content: View>(
57+
moduleName: String,
58+
initialProps: RCTRootViewRepresentable.InitialPropsType = nil,
59+
devMenuPlacement: ToolbarPlacement = .bottomOrnament,
60+
@ViewBuilder contentView: @escaping (_ view: RCTRootViewRepresentable) -> Content
61+
) {
2662
self.moduleName = moduleName
2763
self.initialProps = initialProps
64+
self.devMenuPlacement = devMenuPlacement
65+
self.contentView = AnyView(contentView(rootView))
2866
}
2967

3068
public var body: some Scene {
3169
WindowGroup {
32-
RCTRootViewRepresentable(moduleName: moduleName, initialProps: initialProps)
70+
contentView
3371
.modifier(WindowHandlingModifier())
3472
.onOpenURL(perform: { url in
3573
onOpenURLCallback?(url)
3674
})
75+
#if DEBUG
76+
.toolbar {
77+
DevMenuView(placement: .bottomOrnament)
78+
}
79+
#endif
3780
}
3881
}
3982
}
4083

4184
extension RCTMainWindow {
42-
public func onOpenURL(perform action: @escaping (URL) -> ()) -> some Scene {
85+
public func onOpenURL(perform action: @escaping (URL) -> ()) -> Self {
4386
var scene = self
4487
scene.onOpenURLCallback = action
4588
return scene
@@ -95,3 +138,30 @@ public struct WindowHandlingModifier: ViewModifier {
95138
}
96139
}
97140
}
141+
142+
/**
143+
Toolbar which displays additional controls to easily open dev menu and trigger reload command.
144+
*/
145+
struct DevMenuView: ToolbarContent {
146+
let placement: ToolbarItemPlacement
147+
148+
var body: some ToolbarContent {
149+
ToolbarItem(placement: placement) {
150+
Button(action: {
151+
RCTTriggerReloadCommandListeners("User Reload")
152+
}, label: {
153+
Image(systemName: "arrow.clockwise")
154+
})
155+
}
156+
ToolbarItem(placement: placement) {
157+
Button(action: {
158+
NotificationCenter.default.post(
159+
Notification(name: Notification.Name("RCTShowDevMenuNotification"), object: nil)
160+
)
161+
},
162+
label: {
163+
Image(systemName: "filemenu.and.selection")
164+
})
165+
}
166+
}
167+
}

packages/react-native/Libraries/SwiftExtensions/RCTReactViewController.m

+5-1
Original file line numberDiff line numberDiff line change
@@ -64,8 +64,12 @@ - (void)updateProps:(NSDictionary *)newProps {
6464
return;
6565
}
6666

67+
68+
6769
if (newProps != nil && ![rootView.appProperties isEqualToDictionary:newProps]) {
68-
[rootView setAppProperties:newProps];
70+
NSMutableDictionary *newProperties = [rootView.appProperties mutableCopy];
71+
[newProperties setValuesForKeysWithDictionary:newProps];
72+
[rootView setAppProperties:newProperties];
6973
}
7074
}
7175
@end

packages/react-native/Libraries/SwiftExtensions/RCTWindow.swift

+60-8
Original file line numberDiff line numberDiff line change
@@ -13,19 +13,16 @@ public struct RCTWindow : Scene {
1313
var id: String
1414
var sceneData: RCTSceneData?
1515
var moduleName: String
16-
17-
public init(id: String, moduleName: String, sceneData: RCTSceneData?) {
18-
self.id = id
19-
self.moduleName = moduleName
20-
self.sceneData = sceneData
16+
var contentView: AnyView?
17+
18+
func getRootView(sceneData: RCTSceneData?) -> RCTRootViewRepresentable {
19+
return RCTRootViewRepresentable(moduleName: moduleName, initialProps: sceneData?.props ?? [:])
2120
}
2221

2322
public var body: some Scene {
2423
WindowGroup(id: id) {
2524
Group {
26-
if let sceneData {
27-
RCTRootViewRepresentable(moduleName: moduleName, initialProps: sceneData.props)
28-
}
25+
contentView
2926
}
3027
.onAppear {
3128
if sceneData == nil {
@@ -37,9 +34,64 @@ public struct RCTWindow : Scene {
3734
}
3835

3936
extension RCTWindow {
37+
/// Creates new RCTWindow.
38+
///
39+
/// - Parameters:
40+
/// - id: Unique identifier of the window.
41+
/// - moduleName: Name of the module registered using `AppRegistry.registerComponent()`
42+
/// - sceneData: Data of the scene. Used to sync JS state between windows.
43+
public init(id: String, moduleName: String, sceneData: RCTSceneData?) {
44+
self.id = id
45+
self.moduleName = moduleName
46+
self.sceneData = sceneData
47+
self.contentView = AnyView(getRootView(sceneData: sceneData))
48+
}
49+
50+
/// Creates new RCTWindow with additional closure to allow applying modifiers to rootView.
51+
///
52+
/// - Parameters:
53+
/// - id: Unique identifier of the window.
54+
/// - moduleName: Name of the module registered using `AppRegistry.registerComponent()`
55+
/// - sceneData: Data of the scene. Used to sync JS state between windows.
56+
/// - contentView: Closure which accepts rootView, allows to apply additional modifiers to React Native rootView.
57+
public init<Content: View>(
58+
id: String,
59+
moduleName: String,
60+
sceneData: RCTSceneData?,
61+
@ViewBuilder contentView: @escaping (_ view: RCTRootViewRepresentable) -> Content
62+
) {
63+
self.id = id
64+
self.moduleName = moduleName
65+
self.sceneData = sceneData
66+
self.contentView = AnyView(contentView(getRootView(sceneData: sceneData)))
67+
}
68+
69+
/// Creates new RCTWindow with additional closure to allow applying modifiers to rootView.
70+
///
71+
/// - Parameters:
72+
/// - id: Unique identifier of the window. Same id will be used for moduleName.
73+
/// - sceneData: Data of the scene. Used to sync JS state between windows.
74+
/// - contentView: Closure which accepts rootView, allows to apply additional modifiers to React Native rootView.
75+
public init<Content: View>(
76+
id: String,
77+
sceneData: RCTSceneData?,
78+
@ViewBuilder contentView: @escaping (_ view: RCTRootViewRepresentable) -> Content
79+
) {
80+
self.id = id
81+
self.moduleName = id
82+
self.sceneData = sceneData
83+
self.contentView = AnyView(contentView(getRootView(sceneData: sceneData)))
84+
}
85+
86+
/// Creates new RCTWindow.
87+
///
88+
/// - Parameters:
89+
/// - id: Unique identifier of the window. Same id will be used for moduleName.
90+
/// - sceneData: Data of the scene. Used to sync JS state between windows.
4091
public init(id: String, sceneData: RCTSceneData?) {
4192
self.id = id
4293
self.moduleName = id
4394
self.sceneData = sceneData
95+
self.contentView = AnyView(getRootView(sceneData: sceneData))
4496
}
4597
}

packages/react-native/React/Base/RCTUtils.m

+19
Original file line numberDiff line numberDiff line change
@@ -592,6 +592,7 @@ BOOL RCTRunningInAppExtension(void)
592592
if (scene.session.role == UISceneSessionRoleImmersiveSpaceApplication) {
593593
continue;
594594
}
595+
595596
#endif
596597

597598
if (scene.activationState == UISceneActivationStateForegroundActive) {
@@ -608,6 +609,24 @@ BOOL RCTRunningInAppExtension(void)
608609
UIScene *sceneToUse = foregroundActiveScene ? foregroundActiveScene : foregroundInactiveScene;
609610
UIWindowScene *windowScene = (UIWindowScene *)sceneToUse;
610611

612+
#if TARGET_OS_VISION
613+
// Ornaments are supported only on visionOS.
614+
// When clicking on an ornament it becomes the keyWindow.
615+
// Presenting a RN modal from ornament leads to a crash.
616+
UIWindow* keyWindow = windowScene.keyWindow;
617+
BOOL isOrnament = [keyWindow.debugDescription containsString:@"Ornament"];
618+
if (isOrnament) {
619+
for (UIWindow *window in windowScene.windows) {
620+
BOOL isOrnament = [window.debugDescription containsString:@"Ornament"];
621+
if (window != keyWindow && !isOrnament) {
622+
return window;
623+
}
624+
}
625+
}
626+
627+
return keyWindow;
628+
#endif
629+
611630
if (@available(iOS 15.0, *)) {
612631
return windowScene.keyWindow;
613632
}

packages/rn-tester/Podfile.lock

+59-59
Original file line numberDiff line numberDiff line change
@@ -1901,7 +1901,7 @@ CHECKOUT OPTIONS:
19011901
SPEC CHECKSUMS:
19021902
boost: b6392ab8d665ef3aa7069eea9e57f5224ec9970a
19031903
DoubleConversion: 26c660c8d88372cca1a67f8101d2d962a7064361
1904-
FBLazyVector: d434a232713b779f3fa592271f974d581a4e5efe
1904+
FBLazyVector: e34d006c28c01ab97ea89b92ee24164a32b333cd
19051905
fmt: 5d9ffa7ccba126c08b730252123601d514652320
19061906
glog: 63360cdb8e07e9542830fefdd73687e5af0db2ac
19071907
hermes-engine: c87fb20a7588a9a2e5112ca459a0faa58f90c0c6
@@ -1911,67 +1911,67 @@ SPEC CHECKSUMS:
19111911
OSSLibraryExample: d718b079a7ff6bb417fdbb6d98d58b9081b07b88
19121912
RCT-Folly: e75371281be586c821f9614489de0d370146e4a2
19131913
RCTDeprecation: 3808e36294137f9ee5668f4df2e73dc079cd1dcf
1914-
RCTRequired: bd3cb7ab8bb259206a338f85d80a0ce5a0c69342
1915-
RCTTypeSafety: 371a1430e65d2a81dd029fe05c88702b34301a0e
1916-
React: 7746e376e9f9672d4907b143447e5557f06f1f6d
1917-
React-callinvoker: 233f1d1af1e59f50f9d1b1087a102b5403c45e03
1918-
React-Core: 8742f86305a4015972884366e080fc335af7f6ea
1919-
React-CoreModules: f25f234a4a358089b467ae1949741a68cf711af9
1920-
React-cxxreact: c4b02045aef8489770f6ba18cce3705bea338221
1921-
React-debug: 705fdf75d6e8bdd7181fb98d2d9a2c1f82ab5b2e
1922-
React-defaultsnativemodule: 310ef2d184e528a8842ae6835d1c2f3d07767816
1923-
React-domnativemodule: 0aa77e0bf58663d580acb4275fa624c65591824c
1924-
React-Fabric: 964ca5b70ecea26c82afd34c89c6c55f196b3193
1925-
React-FabricComponents: 0a82f5869eaebb00ba1da8d26802ad248ae5d75c
1926-
React-FabricImage: a51adb7fb0fd90772a2a1c92ed6c7637cc647f57
1927-
React-featureflags: 39b6b9923874d625b1dcd5edddab40695f390103
1928-
React-featureflagsnativemodule: e0b8c2e66e78cda76d22c6da578099891e2dff1d
1929-
React-graphics: 2ba6c1d134da57b4d97ae41dab33b6790df13ccc
1930-
React-hermes: 178f48ef2a77ae54647abf1cbbd7fda34b906530
1931-
React-ImageManager: 6faccfcb1895e63a086e7384e3179ffe24003628
1932-
React-jserrorhandler: 7ac3f665b5905703336086b4f32e6396b46ddea1
1933-
React-jsi: 7da88a67aff82f7d76b90f5fe8a6bec852c73234
1934-
React-jsiexecutor: 91240cb6478d422592b4b8769502067042439966
1935-
React-jsinspector: 9064f47464de6cdca6ec57e546cd70eef6d9f366
1936-
React-jsitracing: 724dd967eba7dda025e8be9caaa4b1573502d956
1937-
React-logger: e6a399500b7bbf65184c59360b2c6ad2ee241c91
1938-
React-Mapbuffer: 0ac85f1cd636a9a8fbfb2b5ee5bf009a8f769156
1939-
React-microtasksnativemodule: cbf04e1a4df2fe67fd80432e19966bdef3af9f05
1940-
React-nativeconfig: afba2adbd92757704f1f7a3aaac4a00d0633635a
1941-
React-NativeModulesApple: 272e293844ad7dc3336c9fd0cf7cd17536f96d95
1942-
React-perflogger: 061cf6456acee26ac4f9bfecf748c60c2721ae45
1943-
React-performancetimeline: 9d5316dd384abc051be89d571475a7b42bd9a327
1944-
React-RCTActionSheet: ff66b5f91f5ae71c78fe8a64836cd4115d45791e
1945-
React-RCTAnimation: 0cf807e00f29aeef17c05bd0e9159f19c39ba758
1946-
React-RCTAppDelegate: 4be422a1d397a7a3bb90fd3c37e4a3b37f777d73
1947-
React-RCTBlob: ff9bee678dd6d2d72a71bb800e8545446250999d
1948-
React-RCTFabric: baae0e17553ff6316c2f83eff1cd2081ac5bfce8
1949-
React-RCTImage: 9527e3983434738416a0c70436de710f4c943bd7
1950-
React-RCTLinking: 6f473ca17b6d63bc7f62921d2be5f543c053e4af
1951-
React-RCTNetwork: ec67d871ec029149650c6c8b5c6422f2a74e355b
1952-
React-RCTPushNotification: 4eabcb8ed729200218042864499cb4fe96ad4b34
1953-
React-RCTSettings: cd8990b36bbfd6cadb269d222842213c42385667
1954-
React-RCTSwiftExtensions: 1db3a5e13c30b72a0c3026c1710747c0fcc40e5e
1955-
React-RCTTest: d510ef04bfea9b9754a8a9f1bbbd3369ce5a9ba8
1956-
React-RCTText: 1957c3fc0fbf58e87df985d8db86f3789f2f28b3
1957-
React-RCTVibration: bf8d05a9edda5e1db5a2211aaa41a4f86d7f57d8
1958-
React-RCTWindowManager: 7492cc3591dfce401fed7cc58ead6d1af5f71f69
1959-
React-RCTXR: 6501e95cf47fed880dd55ebffd399417f164b8c7
1960-
React-rendererconsistency: 79c0102d79bf5f81561ec18ef9b37550684cc01c
1961-
React-rendererdebug: d77ea3945a1218e2733e8d263c4abb798a7bac38
1962-
React-rncore: 6a527db66df7d7e1d8d60248558f24665f3a635f
1963-
React-RuntimeApple: 6e129838bcb4b878dd146f121f19d8b819a722d8
1964-
React-RuntimeCore: c87ff5797c22510f8b0cb6c23fc594017354d077
1965-
React-runtimeexecutor: 108116665f2662482edc93895f9608d04d5b3b05
1966-
React-RuntimeHermes: 31458c92a3ac3af0fe6b061aaab9cf5102ce502e
1967-
React-runtimescheduler: 5c8f98d2e1fa112304431f9f47e3ba269c67e49c
1968-
React-utils: e40c193816da18c2fa5b8673985422974ee02039
1914+
RCTRequired: 06090d9724622f58b28478cbb2e87862756df6b5
1915+
RCTTypeSafety: e6e32ba42a0bd3a0bd94971df9a244fe893e8903
1916+
React: cec6f46c46ba4b88128a89db149ecaa08e569716
1917+
React-callinvoker: 3990b1a68c657e45ce77d4d900bf8533af332e9e
1918+
React-Core: b6a78e75ad6bbf706937952b6e29061a038737a8
1919+
React-CoreModules: fd07d3592016c7ce1ad67e3b10109cca95fc2100
1920+
React-cxxreact: 691071426dc297b489d9cd11cae60a494ec8c074
1921+
React-debug: 3f8158c426c724b0ac3308766a0ecd913f49beee
1922+
React-defaultsnativemodule: 671d9be37ea7c0d0e500d8cb70ad1ca5d4869112
1923+
React-domnativemodule: 2dd51d24e3cf6c9af5de12dba8258ed498c7dda0
1924+
React-Fabric: 5dfa7df179c8a2e9734683826c4457a6312df211
1925+
React-FabricComponents: ae2fad2c444ca48efe45cd3915ec70028807803d
1926+
React-FabricImage: c9f019e603a116560723a2f196518ef1a6d03801
1927+
React-featureflags: 8954103335117a8dd7b5a18471808821480a7dfc
1928+
React-featureflagsnativemodule: 8f683d7f4f61002e514de69d5e61622bbc63538f
1929+
React-graphics: 8994b709c5fde91560aafcbac7a3c48ed5401e02
1930+
React-hermes: a2e77fbcd9f579388b1fc40eb7ddf98e439b6a14
1931+
React-ImageManager: 7f4e5710521768e7549d6539fdeea95ec08f3932
1932+
React-jserrorhandler: b9d21486b2b17501f2dd05b43e93ca28335c5727
1933+
React-jsi: 468dd641ca31f7c6cd6fe8ca97db1db6ae6f1f0f
1934+
React-jsiexecutor: fed9ae2a3f855dc419edc54c5d0c25fa9375af54
1935+
React-jsinspector: a4bc5b3664faccd9ba07829d06b8b1e400549c62
1936+
React-jsitracing: ddc7cbc91c4c2c5911330514fd0cb08a4d332cd6
1937+
React-logger: fa52be9ff4f8879d680ed32f228d2944eade37ff
1938+
React-Mapbuffer: 2d146d504f49bf056b78bdc9d2186f13bb9b317d
1939+
React-microtasksnativemodule: bd0d6b760670deace685e6894baf64692fcd5a64
1940+
React-nativeconfig: fed2d84d822b1cea543154e2f060e44d385ea585
1941+
React-NativeModulesApple: 736f1ec19de1f20e43da8e8f1c005a71e85ab740
1942+
React-perflogger: b6fb9386ca0d7ada973c2327866d45a201d69581
1943+
React-performancetimeline: e8467f2451217454d39b0ea06b22444ee3c95fd1
1944+
React-RCTActionSheet: bf125bb38e5d4389f91e540f96397b8e42b940fa
1945+
React-RCTAnimation: d8dbb59180fd9982299cbb0036dc3b1370ba0586
1946+
React-RCTAppDelegate: ebc474ce7a794b04bed7a7b0e2aae93db10aed09
1947+
React-RCTBlob: abf99efa4cb82cafd0f2761939c923cf4d5c5006
1948+
React-RCTFabric: 460c7d2ba391dfcf1a4daec2cf1c010f8fad1b63
1949+
React-RCTImage: bb1a37bb2934b658a1a42cce23b81839cecf9aa0
1950+
React-RCTLinking: ae979fe87f2cf8dd6c33f6d8b37946e839df1363
1951+
React-RCTNetwork: 670ab5244e7e98d1be4fec61d792b91b389d4630
1952+
React-RCTPushNotification: 6e8755b9ed818ebf0ccd09b9d80fc42cca9fbdcf
1953+
React-RCTSettings: e806a6681faca065d0d8d2faf91f0ab8b05a7543
1954+
React-RCTSwiftExtensions: a3cf8b5cf176b6f739df9de0dfb9e97c3003a9c1
1955+
React-RCTTest: 5a4aac44395525c6d17c088fbd24de9a88eea532
1956+
React-RCTText: 5b86e4f701ced8a6800be90110f56b000fa8b056
1957+
React-RCTVibration: 5b98c96b6df537608575d8a073492f002b7af92d
1958+
React-RCTWindowManager: b1749ada52a110b85807d97ba835466589773a56
1959+
React-RCTXR: 7d027d32850b44446a732501f707f33952871483
1960+
React-rendererconsistency: bc5ee25fa0f56310883263cc966c0d3d737a44a9
1961+
React-rendererdebug: 4a46b05ae5fedba2b8542b83ff8cd4d8efe66713
1962+
React-rncore: 82554771b1f7e5976cc9fcbf01a4f49ab9a288d6
1963+
React-RuntimeApple: c23dc3c0ccb7ae491c2e5128f9e480853bb28c01
1964+
React-RuntimeCore: 2e94339b8d2c4a5e56f04ae3fa1c6ae07022b075
1965+
React-runtimeexecutor: 2b4ed6ec4e40b990b83314c72a8be09a7043fe83
1966+
React-RuntimeHermes: d0b3c295f15c0f7dbfa9142a99f0dea7d6c6c063
1967+
React-runtimescheduler: bc86356bfdde63398cf78267848f82106d009978
1968+
React-utils: c1ac77965df60bf575b20b149439874efb1cc366
19691969
ReactCodegen: 2fa2bfb8df604e5bbf2462cf3cced313c3131b96
1970-
ReactCommon: 9ec392ae3a44c2bdaf77448dfbc883cad9487806
1971-
ReactCommon-Samples: 0f25a353dc295796cc6a7f1cf67c4ad92ca2c60f
1970+
ReactCommon: 6ff0c7ea0f195e0c5e3863382f6c21d74ad7704e
1971+
ReactCommon-Samples: 8a2a27ca2c12bd4d5790e7c3233f66bec354b8fc
19721972
ScreenshotManager: 662151998cf5859591e72c76ceb9ebc6d04b425b
19731973
SocketRocket: 0ba3e799f983d2dfa878777017659ef6c866e5c6
1974-
Yoga: 6bd003ca9113d936e38452fe76de858ec02cc6db
1974+
Yoga: 45ad594d98b47f5fafb1592f82c00479d3b15146
19751975

19761976
PODFILE CHECKSUM: ecf8d73b0aefca76e0e218d8845b105ea9282718
19771977

packages/rn-tester/RNTester-visionOS/App.swift

+8-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,14 @@ struct RNTesterApp: App {
1515
RCTLinkingManager.onOpenURL(url: url)
1616
})
1717

18-
RCTWindow(id: "SecondWindow", sceneData: reactContext.getSceneData(id: "SecondWindow"))
18+
RCTWindow(id: "SecondWindow", sceneData: reactContext.getSceneData(id: "SecondWindow")) { rootView in
19+
rootView.ornament(attachmentAnchor: .scene(.bottom)) {
20+
VStack {
21+
Button("Hey!") {}
22+
}
23+
.glassBackgroundEffect()
24+
}
25+
}
1926
.defaultSize(CGSize(width: 400, height: 700))
2027

2128
ImmersiveSpace(id: "TestImmersiveSpace") {}

0 commit comments

Comments
 (0)