Skip to content

Commit 13d08f5

Browse files
committed
feat: make SwiftUI React Native entry point (#68)
* feat: add Swift entrypoint [wip] add module maps to some RN modules to allow for swift c++ imports feat: implement RCTReactController and RCTSwiftUIAppDelegate feat: introduce new method to RCTAppDelegate * feat: modify template to use SwiftUI * fix: dimensions, use RCTMainWindow() * fix: fallback to DarkMode on visionOS * fix: use KeyWindow() in RCTPerfMonitor
1 parent 66a60f2 commit 13d08f5

File tree

23 files changed

+388
-272
lines changed

23 files changed

+388
-272
lines changed

packages/react-native/Libraries/AppDelegate/RCTAppDelegate.h

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -136,8 +136,11 @@
136136
- (BOOL)bridgelessEnabled;
137137

138138
/// Return the bundle URL for the main bundle.
139-
- (NSURL *)getBundleURL;
139+
- (NSURL *__nullable)bundleURL;
140140

141-
#endif
141+
/// Don't use this method, it's going to be removed soon.
142+
- (UIView *)viewWithModuleName:(NSString *)moduleName
143+
initialProperties:(NSDictionary *)initialProperties
144+
launchOptions:(NSDictionary *)launchOptions;
142145

143146
@end

packages/react-native/Libraries/AppDelegate/RCTAppDelegate.mm

Lines changed: 101 additions & 91 deletions
Original file line numberDiff line numberDiff line change
@@ -86,62 +86,18 @@ - (instancetype)init
8686

8787
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
8888
{
89-
BOOL enableTM = NO;
90-
BOOL enableBridgeless = NO;
91-
BOOL fabricEnabled = NO;
92-
#if RCT_NEW_ARCH_ENABLED
93-
enableTM = self.turboModuleEnabled;
94-
enableBridgeless = self.bridgelessEnabled;
95-
fabricEnabled = [self fabricEnabled];
96-
#endif
97-
NSDictionary *initProps = updateInitialProps([self prepareInitialProps], fabricEnabled);
98-
99-
RCTAppSetupPrepareApp(application, enableTM);
100-
101-
UIView *rootView;
102-
103-
if (enableBridgeless) {
104-
#if RCT_NEW_ARCH_ENABLED
105-
// Enable native view config interop only if both bridgeless mode and Fabric is enabled.
106-
RCTSetUseNativeViewConfigsInBridgelessMode(fabricEnabled);
89+
RCTSetNewArchEnabled([self newArchEnabled]);
90+
BOOL enableTM = self.turboModuleEnabled;
10791

108-
// Enable TurboModule interop by default in Bridgeless mode
109-
RCTEnableTurboModuleInterop(YES);
110-
RCTEnableTurboModuleInteropBridgeProxy(YES);
111-
112-
[self createReactHost];
113-
[self unstable_registerLegacyComponents];
114-
[RCTComponentViewFactory currentComponentViewFactory].thirdPartyFabricComponentsProvider = self;
115-
RCTFabricSurface *surface = [_reactHost createSurfaceWithModuleName:self.moduleName initialProperties:initProps];
116-
117-
RCTSurfaceHostingProxyRootView *surfaceHostingProxyRootView = [[RCTSurfaceHostingProxyRootView alloc]
118-
initWithSurface:surface
119-
sizeMeasureMode:RCTSurfaceSizeMeasureModeWidthExact | RCTSurfaceSizeMeasureModeHeightExact];
120-
121-
rootView = (RCTRootView *)surfaceHostingProxyRootView;
122-
#endif
123-
} else {
124-
if (!self.bridge) {
125-
self.bridge = [self createBridgeWithDelegate:self launchOptions:launchOptions];
126-
}
127-
#if RCT_NEW_ARCH_ENABLED
128-
self.bridgeAdapter = [[RCTSurfacePresenterBridgeAdapter alloc] initWithBridge:self.bridge
129-
contextContainer:_contextContainer];
130-
self.bridge.surfacePresenter = self.bridgeAdapter.surfacePresenter;
131-
132-
[self unstable_registerLegacyComponents];
133-
[RCTComponentViewFactory currentComponentViewFactory].thirdPartyFabricComponentsProvider = self;
134-
#endif
135-
136-
rootView = [self createRootViewWithBridge:self.bridge moduleName:self.moduleName initProps:initProps];
137-
}
138-
139-
[self customizeRootView:(RCTRootView *)rootView];
92+
RCTAppSetupPrepareApp(application, enableTM, *_reactNativeConfig);
93+
14094
#if TARGET_OS_VISION
141-
self.window = [[UIWindow alloc] initWithFrame:RCTForegroundWindow().bounds];
95+
/// Bail out of UIWindow initializaiton to support multi-window scenarios in SwiftUI lifecycle.
96+
return YES;
14297
#else
98+
UIView* rootView = [self viewWithModuleName:self.moduleName initialProperties:[self prepareInitialProps] launchOptions:launchOptions];
99+
143100
self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
144-
#endif
145101

146102
UIViewController *rootViewController = [self createRootViewController];
147103
[self setRootView:rootView toRootViewController:rootViewController];
@@ -150,6 +106,57 @@ - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(
150106
[self.window makeKeyAndVisible];
151107

152108
return YES;
109+
#endif
110+
}
111+
112+
- (void)applicationDidEnterBackground:(UIApplication *)application
113+
{
114+
// Noop
115+
}
116+
117+
- (UIView *)viewWithModuleName:(NSString *)moduleName initialProperties:(NSDictionary*)initialProperties launchOptions:(NSDictionary*)launchOptions {
118+
BOOL fabricEnabled = self.fabricEnabled;
119+
BOOL enableBridgeless = self.bridgelessEnabled;
120+
121+
NSDictionary *initProps = updateInitialProps(initialProperties, fabricEnabled);
122+
123+
UIView *rootView;
124+
if (enableBridgeless) {
125+
// Enable native view config interop only if both bridgeless mode and Fabric is enabled.
126+
RCTSetUseNativeViewConfigsInBridgelessMode(self.fabricEnabled);
127+
128+
// Enable TurboModule interop by default in Bridgeless mode
129+
RCTEnableTurboModuleInterop(YES);
130+
RCTEnableTurboModuleInteropBridgeProxy(YES);
131+
132+
[self createReactHost];
133+
[RCTComponentViewFactory currentComponentViewFactory].thirdPartyFabricComponentsProvider = self;
134+
RCTFabricSurface *surface = [_reactHost createSurfaceWithModuleName:self.moduleName initialProperties:initProps];
135+
136+
RCTSurfaceHostingProxyRootView *surfaceHostingProxyRootView = [[RCTSurfaceHostingProxyRootView alloc]
137+
initWithSurface:surface
138+
sizeMeasureMode:RCTSurfaceSizeMeasureModeWidthExact | RCTSurfaceSizeMeasureModeHeightExact];
139+
140+
rootView = (RCTRootView *)surfaceHostingProxyRootView;
141+
} else {
142+
if (!self.bridge) {
143+
self.bridge = [self createBridgeWithDelegate:self launchOptions:launchOptions];
144+
}
145+
if ([self newArchEnabled]) {
146+
if (!self.bridgeAdapter) {
147+
self.bridgeAdapter = [[RCTSurfacePresenterBridgeAdapter alloc] initWithBridge:self.bridge
148+
contextContainer:_contextContainer];
149+
self.bridge.surfacePresenter = self.bridgeAdapter.surfacePresenter;
150+
151+
[RCTComponentViewFactory currentComponentViewFactory].thirdPartyFabricComponentsProvider = self;
152+
}
153+
}
154+
rootView = [self createRootViewWithBridge:self.bridge moduleName:moduleName initProps:initProps];
155+
}
156+
157+
[self customizeRootView:(RCTRootView *)rootView];
158+
159+
return rootView;
153160
}
154161

155162
- (NSURL *)sourceURLForBridge:(RCTBridge *)bridge
@@ -212,45 +219,44 @@ - (void)windowScene:(UIWindowScene *)windowScene
212219
- (std::unique_ptr<facebook::react::JSExecutorFactory>)jsExecutorFactoryForBridge:(RCTBridge *)bridge
213220
{
214221
_runtimeScheduler = std::make_shared<facebook::react::RuntimeScheduler>(RCTRuntimeExecutorFromBridge(bridge));
215-
#if RCT_NEW_ARCH_ENABLED
216-
std::shared_ptr<facebook::react::CallInvoker> callInvoker =
217-
std::make_shared<facebook::react::RuntimeSchedulerCallInvoker>(_runtimeScheduler);
218-
RCTTurboModuleManager *turboModuleManager = [[RCTTurboModuleManager alloc] initWithBridge:bridge
219-
delegate:self
220-
jsInvoker:callInvoker];
221-
_contextContainer->erase("RuntimeScheduler");
222-
_contextContainer->insert("RuntimeScheduler", _runtimeScheduler);
223-
return RCTAppSetupDefaultJsExecutorFactory(bridge, turboModuleManager, _runtimeScheduler);
224-
#else
225-
return RCTAppSetupJsExecutorFactoryForOldArch(bridge, _runtimeScheduler);
226-
#endif
222+
if ([self newArchEnabled]) {
223+
std::shared_ptr<facebook::react::CallInvoker> callInvoker =
224+
std::make_shared<facebook::react::RuntimeSchedulerCallInvoker>(_runtimeScheduler);
225+
RCTTurboModuleManager *turboModuleManager = [[RCTTurboModuleManager alloc] initWithBridge:bridge
226+
delegate:self
227+
jsInvoker:callInvoker];
228+
_contextContainer->erase("RuntimeScheduler");
229+
_contextContainer->insert("RuntimeScheduler", _runtimeScheduler);
230+
return RCTAppSetupDefaultJsExecutorFactory(bridge, turboModuleManager, _runtimeScheduler);
231+
} else {
232+
return RCTAppSetupJsExecutorFactoryForOldArch(bridge, _runtimeScheduler);
233+
}
227234
}
228235

229-
#if RCT_NEW_ARCH_ENABLED
230-
231-
#pragma mark - RCTTurboModuleManagerDelegate
236+
#pragma mark - New Arch Enabled settings
232237

233-
- (Class)getModuleClassFromName:(const char *)name
238+
- (BOOL)newArchEnabled
234239
{
235-
return RCTCoreModulesClassProvider(name);
240+
#if RCT_NEW_ARCH_ENABLED
241+
return YES;
242+
#else
243+
return NO;
244+
#endif
236245
}
237246

238-
- (std::shared_ptr<facebook::react::TurboModule>)getTurboModule:(const std::string &)name
239-
jsInvoker:(std::shared_ptr<facebook::react::CallInvoker>)jsInvoker
247+
- (BOOL)turboModuleEnabled
240248
{
241-
return nullptr;
249+
return [self newArchEnabled];
242250
}
243251

244-
- (std::shared_ptr<facebook::react::TurboModule>)getTurboModule:(const std::string &)name
245-
initParams:
246-
(const facebook::react::ObjCTurboModule::InitParams &)params
252+
- (BOOL)fabricEnabled
247253
{
248-
return nullptr;
254+
return [self newArchEnabled];
249255
}
250256

251-
- (id<RCTTurboModule>)getModuleInstanceFromClass:(Class)moduleClass
257+
- (BOOL)bridgelessEnabled
252258
{
253-
return RCTAppSetupDefaultModuleFromClass(moduleClass);
259+
return NO;
254260
}
255261

256262
#pragma mark - RCTComponentViewFactoryComponentProvider
@@ -260,34 +266,38 @@ - (Class)getModuleClassFromName:(const char *)name
260266
return @{};
261267
}
262268

263-
#pragma mark - New Arch Enabled settings
269+
#pragma mark - RCTTurboModuleManagerDelegate
264270

265-
- (BOOL)turboModuleEnabled
271+
- (Class)getModuleClassFromName:(const char *)name
266272
{
267-
return YES;
273+
return RCTCoreModulesClassProvider(name);
268274
}
269275

270-
- (BOOL)fabricEnabled
276+
- (std::shared_ptr<facebook::react::TurboModule>)getTurboModule:(const std::string &)name
277+
jsInvoker:(std::shared_ptr<facebook::react::CallInvoker>)jsInvoker
271278
{
272-
return YES;
279+
return nullptr;
273280
}
274281

275-
- (BOOL)bridgelessEnabled
282+
- (std::shared_ptr<facebook::react::TurboModule>)getTurboModule:(const std::string &)name
283+
initParams:
284+
(const facebook::react::ObjCTurboModule::InitParams &)params
276285
{
277-
return NO;
286+
return nullptr;
278287
}
279288

280-
#pragma mark - New Arch Utilities
281-
282-
- (void)unstable_registerLegacyComponents
289+
- (id<RCTTurboModule>)getModuleInstanceFromClass:(Class)moduleClass
283290
{
284-
for (NSString *legacyComponent in [RCTLegacyInteropComponents legacyInteropComponents]) {
285-
[RCTLegacyViewManagerInteropComponentView supportLegacyViewManagerWithName:legacyComponent];
286-
}
291+
return RCTAppSetupDefaultModuleFromClass(moduleClass);
287292
}
288293

294+
#pragma mark - New Arch Utilities
295+
289296
- (void)createReactHost
290297
{
298+
if (_reactHost != nil) {
299+
return;
300+
}
291301
__weak __typeof(self) weakSelf = self;
292302
_reactHost = [[RCTHost alloc] initWithBundleURL:[self getBundleURL]
293303
hostDelegate:nil
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import SwiftUI
2+
3+
/**
4+
This SwiftUI struct returns main React Native scene. It should be used only once as it conains setup code.
5+
6+
Example:
7+
```swift
8+
@main
9+
struct YourApp: App {
10+
@UIApplicationDelegateAdaptor var delegate: AppDelegate
11+
12+
var body: some Scene {
13+
RCTMainWindow(moduleName: "YourApp")
14+
}
15+
}
16+
```
17+
18+
Note: If you want to create additional windows in your app, create a new `WindowGroup {}` and pass it a `RCTRootViewRepresentable`.
19+
*/
20+
public struct RCTMainWindow: Scene {
21+
var moduleName: String
22+
var initialProps: RCTRootViewRepresentable.InitialPropsType
23+
24+
public init(moduleName: String, initialProps: RCTRootViewRepresentable.InitialPropsType = nil) {
25+
self.moduleName = moduleName
26+
self.initialProps = initialProps
27+
}
28+
29+
public var body: some Scene {
30+
WindowGroup {
31+
RCTRootViewRepresentable(moduleName: moduleName, initialProps: initialProps)
32+
}
33+
}
34+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
#import <UIKit/UIKit.h>
2+
3+
/**
4+
A `UIViewController` responsible for embeding `RCTRootView` inside. Uses Factory pattern to retrive new view instances.
5+
6+
Note: Used to in `RCTRootViewRepresentable` to display React views.
7+
*/
8+
@interface RCTReactViewController : UIViewController
9+
10+
@property (nonatomic, strong, nonnull) NSString *moduleName;
11+
@property (nonatomic, strong, nullable) NSDictionary *initialProps;
12+
13+
- (instancetype _Nonnull)initWithModuleName:(NSString *_Nonnull)moduleName
14+
initProps:(NSDictionary *_Nullable)initProps;
15+
16+
@end
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
#import "RCTReactViewController.h"
2+
#import <React/RCTConstants.h>
3+
4+
@protocol RCTRootViewFactoryProtocol <NSObject>
5+
6+
- (UIView *)viewWithModuleName:(NSString *)moduleName initialProperties:(NSDictionary*)initialProperties launchOptions:(NSDictionary*)launchOptions;
7+
8+
@end
9+
10+
@implementation RCTReactViewController
11+
12+
- (instancetype)initWithModuleName:(NSString *)moduleName initProps:(NSDictionary *)initProps {
13+
if (self = [super init]) {
14+
_moduleName = moduleName;
15+
_initialProps = initProps;
16+
}
17+
return self;
18+
}
19+
20+
- (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id<UIViewControllerTransitionCoordinator>)coordinator {
21+
[[NSNotificationCenter defaultCenter] postNotificationName:RCTWindowFrameDidChangeNotification object:self];
22+
}
23+
24+
// TODO: Temporary solution for creating RCTRootView on demand. This should be done through factory pattern, see here: https://github.com/facebook/react-native/pull/42263
25+
- (void)loadView {
26+
id<UIApplicationDelegate> appDelegate = [UIApplication sharedApplication].delegate;
27+
if ([appDelegate respondsToSelector:@selector(viewWithModuleName:initialProperties:launchOptions:)]) {
28+
id<RCTRootViewFactoryProtocol> delegate = (id<RCTRootViewFactoryProtocol>)appDelegate;
29+
self.view = [delegate viewWithModuleName:_moduleName initialProperties:_initialProps launchOptions:@{}];
30+
} else {
31+
[NSException raise:@"UIApplicationDelegate:viewWithModuleName:initialProperties:launchOptions: not implemented"
32+
format:@"Make sure you subclass RCTAppDelegate"];
33+
}
34+
}
35+
36+
@end
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import SwiftUI
2+
3+
/**
4+
SwiftUI view enclosing `RCTReactViewController`. Its main purpose is to display React Native views inside of SwiftUI lifecycle.
5+
6+
Use it create new windows in your app:
7+
Example:
8+
```swift
9+
WindowGroup {
10+
RCTRootViewRepresentable(moduleName: "YourAppName")
11+
}
12+
```
13+
*/
14+
public struct RCTRootViewRepresentable: UIViewControllerRepresentable {
15+
public typealias InitialPropsType = [AnyHashable: Any]?
16+
17+
var moduleName: String
18+
var initialProps: InitialPropsType
19+
20+
public init(moduleName: String, initialProps: InitialPropsType = nil) {
21+
self.moduleName = moduleName
22+
self.initialProps = initialProps
23+
}
24+
25+
public func makeUIViewController(context: Context) -> UIViewController {
26+
RCTReactViewController(moduleName: moduleName, initProps: initialProps)
27+
}
28+
29+
public func updateUIViewController(_ uiViewController: UIViewController, context: Context) {
30+
// noop
31+
}
32+
}

0 commit comments

Comments
 (0)