Skip to content

Commit 799f942

Browse files
committed
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
1 parent 474980e commit 799f942

File tree

15 files changed

+207
-62
lines changed

15 files changed

+207
-62
lines changed

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

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -159,7 +159,10 @@ NS_ASSUME_NONNULL_BEGIN
159159
- (BOOL)bridgelessEnabled;
160160

161161
/// Return the bundle URL for the main bundle.
162-
- (NSURL *)bundleURL;
162+
- (NSURL *__nullable)bundleURL;
163+
164+
/// Don't use this method, it's going to be removed soon.
165+
- (UIView *)viewWithModuleName:(NSString *)moduleName initialProperties:(NSDictionary*)initialProperties launchOptions:(NSDictionary*)launchOptions;
163166

164167
@end
165168

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

Lines changed: 51 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -83,51 +83,16 @@ - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(
8383
{
8484
RCTSetNewArchEnabled([self newArchEnabled]);
8585
BOOL enableTM = self.turboModuleEnabled;
86-
BOOL fabricEnabled = self.fabricEnabled;
87-
BOOL enableBridgeless = self.bridgelessEnabled;
88-
89-
NSDictionary *initProps = updateInitialProps([self prepareInitialProps], fabricEnabled);
9086

9187
RCTAppSetupPrepareApp(application, enableTM, *_reactNativeConfig);
92-
93-
UIView *rootView;
94-
if (enableBridgeless) {
95-
// Enable native view config interop only if both bridgeless mode and Fabric is enabled.
96-
RCTSetUseNativeViewConfigsInBridgelessMode(fabricEnabled);
97-
98-
// Enable TurboModule interop by default in Bridgeless mode
99-
RCTEnableTurboModuleInterop(YES);
100-
RCTEnableTurboModuleInteropBridgeProxy(YES);
101-
102-
[self createReactHost];
103-
[RCTComponentViewFactory currentComponentViewFactory].thirdPartyFabricComponentsProvider = self;
104-
RCTFabricSurface *surface = [_reactHost createSurfaceWithModuleName:self.moduleName initialProperties:initProps];
105-
106-
RCTSurfaceHostingProxyRootView *surfaceHostingProxyRootView = [[RCTSurfaceHostingProxyRootView alloc]
107-
initWithSurface:surface
108-
sizeMeasureMode:RCTSurfaceSizeMeasureModeWidthExact | RCTSurfaceSizeMeasureModeHeightExact];
109-
110-
rootView = (RCTRootView *)surfaceHostingProxyRootView;
111-
} else {
112-
if (!self.bridge) {
113-
self.bridge = [self createBridgeWithDelegate:self launchOptions:launchOptions];
114-
}
115-
if ([self newArchEnabled]) {
116-
self.bridgeAdapter = [[RCTSurfacePresenterBridgeAdapter alloc] initWithBridge:self.bridge
117-
contextContainer:_contextContainer];
118-
self.bridge.surfacePresenter = self.bridgeAdapter.surfacePresenter;
119-
120-
[RCTComponentViewFactory currentComponentViewFactory].thirdPartyFabricComponentsProvider = self;
121-
}
122-
rootView = [self createRootViewWithBridge:self.bridge moduleName:self.moduleName initProps:initProps];
123-
}
124-
125-
[self customizeRootView:(RCTRootView *)rootView];
88+
12689
#if TARGET_OS_VISION
127-
self.window = [[UIWindow alloc] initWithFrame:RCTForegroundWindow().bounds];
90+
/// Bail out of UIWindow initializaiton to support multi-window scenarios in SwiftUI lifecycle.
91+
return YES;
12892
#else
93+
UIView* rootView = [self viewWithModuleName:self.moduleName initialProperties:[self prepareInitialProps] launchOptions:launchOptions];
94+
12995
self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
130-
#endif
13196

13297
UIViewController *rootViewController = [self createRootViewController];
13398
[self setRootView:rootView toRootViewController:rootViewController];
@@ -136,13 +101,59 @@ - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(
136101
[self.window makeKeyAndVisible];
137102

138103
return YES;
104+
#endif
139105
}
140106

141107
- (void)applicationDidEnterBackground:(UIApplication *)application
142108
{
143109
// Noop
144110
}
145111

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

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
#elif __has_include(<reacthermes/HermesExecutorFactory.h>)
2222
#import <reacthermes/HermesExecutorFactory.h>
2323
#endif
24-
#else // USE_HERMES
24+
#elif __has_include(<React/JSCExecutorFactory.h>) // USE_HERMES
2525
#import <React/JSCExecutorFactory.h>
2626
#endif // USE_HERMES
2727

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
#import <UIKit/UIKit.h>
2+
3+
@interface RCTReactViewController : UIViewController
4+
5+
@property (nonatomic, strong) NSString *_Nonnull moduleName;
6+
@property (nonatomic, strong) NSDictionary *_Nullable initialProps;
7+
8+
- (instancetype _Nonnull)initWithModuleName:(NSString *_Nonnull)moduleName
9+
initProps:(NSDictionary *_Nullable)initProps;
10+
11+
@end
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
#import "RCTReactViewController.h"
2+
3+
@protocol RCTRootViewFactoryProtocol <NSObject>
4+
5+
- (UIView *)viewWithModuleName:(NSString *)moduleName initialProperties:(NSDictionary*)initialProperties launchOptions:(NSDictionary*)launchOptions;
6+
7+
@end
8+
9+
@implementation RCTReactViewController
10+
11+
- (instancetype)initWithModuleName:(NSString *)moduleName initProps:(NSDictionary *)initProps {
12+
if (self = [super init]) {
13+
_moduleName = moduleName;
14+
_initialProps = initProps;
15+
}
16+
return self;
17+
}
18+
19+
// 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
20+
- (void)loadView {
21+
id<UIApplicationDelegate> appDelegate = [UIApplication sharedApplication].delegate;
22+
if ([appDelegate respondsToSelector:@selector(viewWithModuleName:initialProperties:launchOptions:)]) {
23+
id<RCTRootViewFactoryProtocol> delegate = (id<RCTRootViewFactoryProtocol>)appDelegate;
24+
self.view = [delegate viewWithModuleName:_moduleName initialProperties:_initialProps launchOptions:@{}];
25+
} else {
26+
[NSException raise:@"UIApplicationDelegate:viewWithModuleName:initialProperties:launchOptions: not implemented"
27+
format:@"Make sure you subclass RCTAppDelegate"];
28+
}
29+
}
30+
31+
@end
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import SwiftUI
2+
3+
/*
4+
* Use this struct in SwiftUI context to present React Native rootView.
5+
*
6+
* Example:
7+
* ```
8+
* WindowGroup {
9+
* RCTRootViewRepresentable(moduleName: "RNTesterApp", initialProps: [:])
10+
* }
11+
* ```
12+
*/
13+
public struct RCTRootViewRepresentable: UIViewControllerRepresentable {
14+
var moduleName: String
15+
var initialProps: [AnyHashable: Any]?
16+
17+
public init(moduleName: String, initialProps: [AnyHashable : Any]?) {
18+
self.moduleName = moduleName
19+
self.initialProps = initialProps
20+
}
21+
22+
public func makeUIViewController(context: Context) -> UIViewController {
23+
RCTReactViewController(moduleName: moduleName, initProps: initialProps)
24+
}
25+
26+
public func updateUIViewController(_ uiViewController: UIViewController, context: Context) {
27+
// noop
28+
}
29+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
require "json"
2+
3+
package = JSON.parse(File.read(File.join(__dir__, "..", "..", "package.json")))
4+
version = package['version']
5+
6+
source = { :git => 'https://github.com/facebook/react-native.git' }
7+
if version == '1000.0.0'
8+
# This is an unpublished version, use the latest commit hash of the react-native repo, which we’re presumably in.
9+
source[:commit] = `git rev-parse HEAD`.strip if system("git rev-parse --git-dir > /dev/null 2>&1")
10+
else
11+
source[:tag] = "v#{version}"
12+
end
13+
14+
Pod::Spec.new do |s|
15+
s.name = "React-RCTSwiftExtensions"
16+
s.version = version
17+
s.summary = "A library for easier React Native integration with SwiftUI."
18+
s.homepage = "https://reactnative.dev/"
19+
s.license = package["license"]
20+
s.author = "Callstack"
21+
s.platforms = min_supported_versions
22+
s.source = source
23+
s.source_files = "*.{swift,h,m}"
24+
s.frameworks = ["UIKit", "SwiftUI"]
25+
end

packages/react-native/React/Base/RCTBridgeDelegate.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ NS_ASSUME_NONNULL_BEGIN
2020
* When running from a locally bundled JS file, this should be a `file://` url
2121
* pointing to a path inside the app resources, e.g. `file://.../main.jsbundle`.
2222
*/
23-
- (NSURL *)sourceURLForBridge:(RCTBridge *)bridge;
23+
- (NSURL *__nullable)sourceURLForBridge:(RCTBridge *)bridge;
2424

2525
@optional
2626

packages/react-native/React/Base/RCTBundleURLProvider.h

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ RCT_EXTERN const NSUInteger kRCTBundleURLProviderDefaultPort;
2222
RCT_EXTERN void RCTBundleURLProviderAllowPackagerServerAccess(BOOL allowed);
2323
#endif
2424

25+
NS_ASSUME_NONNULL_BEGIN
26+
2527
@interface RCTBundleURLProvider : NSObject
2628

2729
/**
@@ -58,38 +60,38 @@ RCT_EXTERN void RCTBundleURLProviderAllowPackagerServerAccess(BOOL allowed);
5860
* Returns the jsBundleURL for a given bundle entrypoint and
5961
* the fallback offline JS bundle if the packager is not running.
6062
*/
61-
- (NSURL *)jsBundleURLForBundleRoot:(NSString *)bundleRoot fallbackURLProvider:(NSURL * (^)(void))fallbackURLProvider;
63+
- (NSURL *__nullable)jsBundleURLForBundleRoot:(NSString *)bundleRoot fallbackURLProvider:(NSURL * (^)(void))fallbackURLProvider;
6264

6365
/**
6466
* Returns the jsBundleURL for a given split bundle entrypoint in development
6567
*/
66-
- (NSURL *)jsBundleURLForSplitBundleRoot:(NSString *)bundleRoot;
68+
- (NSURL *__nullable)jsBundleURLForSplitBundleRoot:(NSString *)bundleRoot;
6769

6870
/**
6971
* Returns the jsBundleURL for a given bundle entrypoint and
7072
* the fallback offline JS bundle if the packager is not running.
7173
* if extension is nil, "jsbundle" will be used.
7274
*/
73-
- (NSURL *)jsBundleURLForBundleRoot:(NSString *)bundleRoot fallbackExtension:(NSString *)extension;
75+
- (NSURL *__nullable)jsBundleURLForBundleRoot:(NSString *)bundleRoot fallbackExtension:(NSString *)extension;
7476

7577
/**
7678
* Returns the jsBundleURL for a given bundle entrypoint and
7779
* the fallback offline JS bundle if the packager is not running.
7880
*/
79-
- (NSURL *)jsBundleURLForBundleRoot:(NSString *)bundleRoot;
81+
- (NSURL *__nullable)jsBundleURLForBundleRoot:(NSString *)bundleRoot;
8082

8183
/**
8284
* Returns the jsBundleURL for a given bundle entrypoint and
8385
* the fallback offline JS bundle. If extension is nil,
8486
* "jsbundle" will be used.
8587
*/
86-
- (NSURL *)jsBundleURLForFallbackExtension:(NSString *)extension;
88+
- (NSURL *__nullable)jsBundleURLForFallbackExtension:(NSString *)extension;
8789

8890
/**
8991
* Returns the resourceURL for a given bundle entrypoint and
9092
* the fallback offline resource file if the packager is not running.
9193
*/
92-
- (NSURL *)resourceURLForResourceRoot:(NSString *)root
94+
- (NSURL *__nullable)resourceURLForResourceRoot:(NSString *)root
9395
resourceName:(NSString *)name
9496
resourceExtension:(NSString *)extension
9597
offlineBundle:(NSBundle *)offlineBundle;
@@ -124,13 +126,13 @@ RCT_EXTERN void RCTBundleURLProviderAllowPackagerServerAccess(BOOL allowed);
124126
* - runModule: When true, will run the main module after defining all modules. This is used in the main bundle but not
125127
* in split bundles.
126128
*/
127-
+ (NSURL *)jsBundleURLForBundleRoot:(NSString *)bundleRoot
129+
+ (NSURL *__nullable)jsBundleURLForBundleRoot:(NSString *)bundleRoot
128130
packagerHost:(NSString *)packagerHost
129131
enableDev:(BOOL)enableDev
130132
enableMinification:(BOOL)enableMinification
131133
inlineSourceMap:(BOOL)inlineSourceMap;
132134

133-
+ (NSURL *)jsBundleURLForBundleRoot:(NSString *)bundleRoot
135+
+ (NSURL *__nullable)jsBundleURLForBundleRoot:(NSString *)bundleRoot
134136
packagerHost:(NSString *)packagerHost
135137
packagerScheme:(NSString *)scheme
136138
enableDev:(BOOL)enableDev
@@ -160,3 +162,5 @@ RCT_EXTERN void RCTBundleURLProviderAllowPackagerServerAccess(BOOL allowed);
160162
queryItems:(NSArray<NSURLQueryItem *> *)queryItems;
161163

162164
@end
165+
166+
NS_ASSUME_NONNULL_END

packages/react-native/React/CoreModules/RCTAppearance.mm

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,8 @@ @implementation RCTAppearance {
7070
- (instancetype)init
7171
{
7272
if ((self = [super init])) {
73-
UITraitCollection *traitCollection = RCTSharedApplication().delegate.window.traitCollection;
73+
// TODO: Remove this after merging this PR upstream: https://github.com/facebook/react-native/pull/42231
74+
UITraitCollection *traitCollection = RCTKeyWindow().traitCollection;
7475
_currentColorScheme = RCTColorSchemePreference(traitCollection);
7576
[[NSNotificationCenter defaultCenter] addObserver:self
7677
selector:@selector(appearanceChanged:)

0 commit comments

Comments
 (0)