Skip to content

Commit b86a6ed

Browse files
committed
feat: make RCTSpatial decoupled from RCTMainWindow()
1 parent 822007e commit b86a6ed

File tree

5 files changed

+85
-32
lines changed

5 files changed

+85
-32
lines changed
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import Foundation
2+
import SwiftUI
3+
4+
@objc public enum ImmersiveSpaceResult: Int {
5+
case opened
6+
case userCancelled
7+
case error
8+
}
9+
10+
public typealias CompletionHandlerType = (_ result: ImmersiveSpaceResult) -> Void
11+
12+
struct ImmersiveBridgeView: View {
13+
@Environment(\.openImmersiveSpace) private var openImmersiveSpace
14+
@Environment(\.dismissImmersiveSpace) private var dismissImmersiveSpace
15+
16+
var spaceId: String
17+
var completionHandler: CompletionHandlerType
18+
19+
var body: some View {
20+
EmptyView()
21+
.onAppear {
22+
Task {
23+
let result = await openImmersiveSpace(id: spaceId)
24+
25+
switch result {
26+
case .opened:
27+
completionHandler(.opened)
28+
case .error:
29+
completionHandler(.error)
30+
case .userCancelled:
31+
completionHandler(.userCancelled)
32+
default:
33+
break
34+
}
35+
}
36+
}
37+
.onDisappear {
38+
Task { await dismissImmersiveSpace() }
39+
}
40+
}
41+
}
42+
43+
@objc public class ImmersiveBridgeFactory: NSObject {
44+
@objc public static func makeImmersiveBridgeView(
45+
spaceId: String,
46+
completionHandler: @escaping CompletionHandlerType
47+
) -> UIViewController {
48+
return UIHostingController(rootView: ImmersiveBridgeView(spaceId: spaceId, completionHandler: completionHandler))
49+
}
50+
}

packages/react-native/Libraries/Spatial/RCTSpatialManager.mm

Lines changed: 33 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,14 @@
44
#import <React/RCTBridge.h>
55
#import <React/RCTConvert.h>
66
#import <React/RCTUtils.h>
7-
8-
static NSString *const kOpenImmersiveSpace = @"RCTOpenImmersiveSpaceNotification";
9-
static NSString *const kDismissImmersiveSpace = @"RCTDismissImmersiveSpaceNotification";
7+
#import "RCTSpatial-Swift.h"
108

119
@interface RCTSpatialManager () <NativeSpatialManagerSpec>
1210
@end
1311

14-
@implementation RCTSpatialManager
12+
@implementation RCTSpatialManager {
13+
UIViewController *_immersiveBridgeView;
14+
}
1515

1616
RCT_EXPORT_MODULE()
1717

@@ -23,17 +23,42 @@ @implementation RCTSpatialManager
2323
: (RCTPromiseResolveBlock)resolve reject
2424
: (RCTPromiseRejectBlock)reject)
2525
{
26-
dispatch_async(dispatch_get_main_queue(), ^{
27-
[[NSNotificationCenter defaultCenter] postNotificationName:kDismissImmersiveSpace object:self];
26+
RCTExecuteOnMainQueue(^{
27+
[self->_immersiveBridgeView willMoveToParentViewController:nil];
28+
[self->_immersiveBridgeView.view removeFromSuperview];
29+
[self->_immersiveBridgeView removeFromParentViewController];
30+
self->_immersiveBridgeView = nil;
31+
32+
resolve(nil);
2833
});
2934
}
3035

3136
RCT_EXPORT_METHOD(openImmersiveSpace
3237
: (NSString *)sceneId resolve
3338
: (RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject)
3439
{
35-
dispatch_async(dispatch_get_main_queue(), ^{
36-
[[NSNotificationCenter defaultCenter] postNotificationName:kOpenImmersiveSpace object:self userInfo:@{@"sceneId": sceneId}];
40+
RCTExecuteOnMainQueue(^{
41+
UIWindow *keyWindow = RCTKeyWindow();
42+
UIViewController *rootViewController = keyWindow.rootViewController;
43+
44+
if (self->_immersiveBridgeView == nil) {
45+
self->_immersiveBridgeView = [ImmersiveBridgeFactory makeImmersiveBridgeViewWithSpaceId:sceneId
46+
completionHandler:^(enum ImmersiveSpaceResult result){
47+
if (result == ImmersiveSpaceResultError) {
48+
reject(@"ERROR", @"Immersive Space failed to open, the system cannot fulfill the request.", nil);
49+
} else if (result == ImmersiveSpaceResultUserCancelled) {
50+
reject(@"ERROR", @"Immersive Space canceled by user", nil);
51+
} else if (result == ImmersiveSpaceResultOpened) {
52+
resolve(nil);
53+
}
54+
}];
55+
56+
[rootViewController.view addSubview:self->_immersiveBridgeView.view];
57+
[rootViewController addChildViewController:self->_immersiveBridgeView];
58+
[self->_immersiveBridgeView didMoveToParentViewController:rootViewController];
59+
} else {
60+
reject(@"ERROR", @"Immersive Space already opened", nil);
61+
}
3762
});
3863
}
3964

packages/react-native/Libraries/Spatial/React-RCTSpatial.podspec

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ Pod::Spec.new do |s|
3131
s.platforms = min_supported_versions
3232
s.compiler_flags = folly_compiler_flags + ' -Wno-nullability-completeness'
3333
s.source = source
34-
s.source_files = "*.{m,mm}"
34+
s.source_files = "*.{m,mm,swift}"
3535
s.preserve_paths = "package.json", "LICENSE", "LICENSE-docs"
3636
s.header_dir = "RCTSpatial"
3737
s.pod_target_xcconfig = {

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

Lines changed: 0 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,6 @@ import SwiftUI
22
import React
33

44

5-
extension NSNotification.Name {
6-
static let openImmersiveSpace = NSNotification.Name("RCTOpenImmersiveSpaceNotification")
7-
static let dismissImmersiveSpace = NSNotification.Name("RCTDismissImmersiveSpaceNotification")
8-
}
9-
105
/**
116
This SwiftUI struct returns main React Native scene. It should be used only once as it conains setup code.
127

@@ -36,24 +31,6 @@ public struct RCTMainWindow: Scene {
3631
public var body: some Scene {
3732
WindowGroup {
3833
RCTRootViewRepresentable(moduleName: moduleName, initialProps: initialProps)
39-
.modifier(EnvironmentListener())
4034
}
4135
}
4236
}
43-
44-
struct EnvironmentListener: ViewModifier {
45-
@Environment(\.openImmersiveSpace) private var openImmersiveSpace
46-
@Environment(\.dismissImmersiveSpace) private var dismissImmersiveSpace
47-
48-
func body(content: Content) -> some View {
49-
content
50-
.onReceive(NotificationCenter.default.publisher(for: .openImmersiveSpace)) { data in
51-
print("open immersive space")
52-
Task { await openImmersiveSpace(id: "ImmersiveSpace") }
53-
}
54-
.onReceive(NotificationCenter.default.publisher(for: .dismissImmersiveSpace)) { data in
55-
print("dismiss immersive space")
56-
Task { await dismissImmersiveSpace() }
57-
}
58-
}
59-
}

packages/react-native/Libraries/SwiftExtensions/React-RCTSwiftExtensions.podspec

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,4 +24,5 @@ Pod::Spec.new do |s|
2424
s.frameworks = ["UIKit", "SwiftUI"]
2525

2626
s.dependency "React-Core"
27+
s.dependency "React-RCTSpatial"
2728
end

0 commit comments

Comments
 (0)