first commit

This commit is contained in:
2026-03-10 16:18:05 +00:00
commit 11f9c069b5
31635 changed files with 3187747 additions and 0 deletions

View File

@@ -0,0 +1,19 @@
// Copyright 2018-present 650 Industries. All rights reserved.
import ExpoModulesCore
@MainActor
@preconcurrency
@objc
public class AppDelegatesLoaderDelegate: NSObject {
/**
Gets and registers AppDelegate subscribers.
*/
@objc
public static func registerAppDelegateSubscribers(_ legacySubscriber: ExpoAppDelegateSubscriberProtocol) {
let modulesProvider = AppContext.modulesProvider(withName: "ExpoModulesProvider")
ExpoAppDelegateSubscriberRepository.registerSubscriber(legacySubscriber)
ExpoAppDelegateSubscriberRepository.registerSubscribersFrom(modulesProvider: modulesProvider)
ExpoAppDelegateSubscriberRepository.registerReactDelegateHandlersFrom(modulesProvider: modulesProvider)
}
}

View File

@@ -0,0 +1,15 @@
// Copyright 2018-present 650 Industries. All rights reserved.
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
/**
Loads `ExpoAppDelegate` subscribers based on
the list from the generated `ExpoModulesProvider`.
*/
@interface EXAppDelegatesLoader : NSObject
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,22 @@
// Copyright 2018-present 650 Industries. All rights reserved.
#import <Expo/EXLegacyAppDelegateWrapper.h>
#import <Expo/EXAppDelegatesLoader.h>
#import <Expo/Swift.h>
// Make the legacy wrapper conform to the protocol for subscribers.
@interface EXLegacyAppDelegateWrapper () <EXAppDelegateSubscriberProtocol>
@end
@implementation EXAppDelegatesLoader
// App delegate providers must be registered before any `AppDelegate` life-cycle event is called.
// Unfortunately it's not possible in Swift to run code right after the binary is loaded
// and before any code is executed, so we switch back to Objective-C just to do this one thing.
+ (void)load
{
[AppDelegatesLoaderDelegate registerAppDelegateSubscribers:[[EXLegacyAppDelegateWrapper alloc] init]];
}
@end

View File

@@ -0,0 +1,19 @@
// Copyright © 2018 650 Industries. All rights reserved.
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
/**
The legacy wrapper is still used to forward app delegate calls to singleton modules.
See `EXAppDelegatesLoader.m` which registers this class as a subscriber of `ExpoAppDelegate`.
*/
#if TARGET_OS_OSX
@interface EXLegacyAppDelegateWrapper : NSResponder <NSApplicationDelegate>
#else
@interface EXLegacyAppDelegateWrapper : UIResponder <UIApplicationDelegate>
#endif
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,272 @@
// Copyright © 2018 650 Industries. All rights reserved.
#import <Expo/EXLegacyAppDelegateWrapper.h>
#import <ExpoModulesCore/EXSingletonModule.h>
#import <ExpoModulesCore/Platform.h>
#import <ExpoModulesCore/EXModuleRegistryProvider.h>
#import <Foundation/FoundationErrors.h>
#if !TARGET_OS_OSX
static NSMutableArray<id<UIApplicationDelegate>> *subcontractors;
static NSMutableDictionary<NSString *,NSArray<id<UIApplicationDelegate>> *> *subcontractorsForSelector;
static dispatch_once_t onceToken;
#endif
@implementation EXLegacyAppDelegateWrapper
// The legacy app delegate wrapper is not supported on macOS, but we keep it no-op for convenience.
#if !TARGET_OS_OSX
@synthesize window = _window;
- (void)forwardInvocation:(NSInvocation *)invocation {
#if DEBUG
SEL selector = [invocation selector];
NSArray<id<UIApplicationDelegate>> *delegatesToBeCalled = [self getSubcontractorsImplementingSelector:selector];
NSString *selectorName = NSStringFromSelector(selector);
if ([delegatesToBeCalled count] > 0) {
[NSException raise:@"Method not implemented in UIApplicationDelegate" format:@"Some modules: %@ have registered for `%@` UIApplicationDelegate's callback, however, neither your AppDelegate nor %@ can handle this method. You'll need to either implement this method in your AppDelegate or submit a pull request to handle it in %@.", delegatesToBeCalled, selectorName, NSStringFromClass([self class]), NSStringFromClass([self class])];
}
#endif
[super forwardInvocation:invocation];
}
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(nullable NSDictionary *)launchOptions
{
BOOL answer = NO;
SEL selector = @selector(application:didFinishLaunchingWithOptions:);
NSArray<id<UIApplicationDelegate>> *subcontractorsArray = [self getSubcontractorsImplementingSelector:selector];
for (id<UIApplicationDelegate> subcontractor in subcontractorsArray) {
BOOL subcontractorAnswer = NO;
subcontractorAnswer = [subcontractor application:application didFinishLaunchingWithOptions:launchOptions];
answer |= subcontractorAnswer;
}
return answer;
}
- (void)applicationWillEnterForeground:(UIApplication *)application
{
SEL selector = @selector(applicationWillEnterForeground:);
NSArray<id<UIApplicationDelegate>> *subcontractorsArray = [self getSubcontractorsImplementingSelector:selector];
for (id<UIApplicationDelegate> subcontractor in subcontractorsArray) {
[subcontractor applicationWillEnterForeground:application];
}
}
- (BOOL)application:(UIApplication *)app openURL:(NSURL *)url options:(NSDictionary<UIApplicationOpenURLOptionsKey,id> *)options
{
SEL selector = @selector(application:openURL:options:);
NSArray<id<UIApplicationDelegate>> *subcontractorsArray = [self getSubcontractorsImplementingSelector:selector];
for (id<UIApplicationDelegate> subcontractor in subcontractorsArray) {
if ([subcontractor application:app openURL:url options:options]) {
return YES;
}
}
return NO;
}
- (void)application:(UIApplication *)application performFetchWithCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler
{
SEL selector = @selector(application:performFetchWithCompletionHandler:);
NSArray<id<UIApplicationDelegate>> *subcontractorsArray = [self getSubcontractorsImplementingSelector:selector];
__block NSUInteger subcontractorsLeft = [subcontractorsArray count];
__block UIBackgroundFetchResult fetchResult = UIBackgroundFetchResultNoData;
__block NSObject *lock = [NSObject new];
void (^handler)(UIBackgroundFetchResult) = ^(UIBackgroundFetchResult result) {
@synchronized (lock) {
if (result == UIBackgroundFetchResultFailed) {
fetchResult = UIBackgroundFetchResultFailed;
} else if (fetchResult != UIBackgroundFetchResultFailed && result == UIBackgroundFetchResultNewData) {
fetchResult = UIBackgroundFetchResultNewData;
}
subcontractorsLeft--;
if (subcontractorsLeft == 0) {
completionHandler(fetchResult);
}
}
};
if (subcontractorsLeft == 0) {
completionHandler(fetchResult);
} else {
for (id<UIApplicationDelegate> subcontractor in subcontractorsArray) {
[subcontractor application:application performFetchWithCompletionHandler:handler];
}
}
}
- (BOOL)application:(UIApplication *)application continueUserActivity:(NSUserActivity *)userActivity restorationHandler:(void (^)(NSArray<id<UIUserActivityRestoring>> * _Nullable))restorationHandler
{
SEL selector = @selector(application:continueUserActivity:restorationHandler:);
NSArray<id<UIApplicationDelegate>> *subcontractorsArray = [self getSubcontractorsImplementingSelector:selector];
__block NSMutableArray<id<UIUserActivityRestoring>> * _Nullable mergedParams = [NSMutableArray new];
__block NSUInteger subcontractorsLeft = [subcontractorsArray count];
__block NSObject *lock = [NSObject new];
void (^handler)(NSArray<id<UIUserActivityRestoring>> * _Nullable) = ^(NSArray<id<UIUserActivityRestoring>> * _Nullable param) {
@synchronized (lock) {
[mergedParams addObjectsFromArray:param];
subcontractorsLeft--;
if (subcontractorsLeft == 0) {
restorationHandler(mergedParams);
}
}
};
BOOL result = NO;
for (id<UIApplicationDelegate> subcontractor in subcontractorsArray) {
result = result || [subcontractor application:application continueUserActivity:userActivity restorationHandler:handler];
}
return result;
}
#pragma mark - BackgroundSession
- (void)application:(UIApplication *)application handleEventsForBackgroundURLSession:(NSString *)identifier completionHandler:(void (^)(void))completionHandler
{
SEL selector = @selector(application:handleEventsForBackgroundURLSession:completionHandler:);
NSArray<id<UIApplicationDelegate>> *subcontractorsArray = [self getSubcontractorsImplementingSelector:selector];
__block BOOL delegatingCompleted = NO;
__block int delegatesCompleted = 0;
__block unsigned long allDelegates = subcontractorsArray.count;
__block void (^completionHandlerCaller)(void) = ^ {
if (delegatesCompleted && delegatingCompleted == allDelegates) {
completionHandler();
}
};
for (id<UIApplicationDelegate> subcontractor in subcontractorsArray) {
[subcontractor application:application handleEventsForBackgroundURLSession:identifier completionHandler:^(){
@synchronized (self) {
delegatesCompleted += 1;
completionHandlerCaller();
}
}];
}
@synchronized (self) {
delegatingCompleted = YES;
completionHandlerCaller();
}
}
#pragma mark - Notifications
- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)token
{
SEL selector = @selector(application:didRegisterForRemoteNotificationsWithDeviceToken:);
NSArray<id<UIApplicationDelegate>> *subcontractorsArray = [self getSubcontractorsImplementingSelector:selector];
for (id<UIApplicationDelegate> subcontractor in subcontractorsArray) {
[subcontractor application:application didRegisterForRemoteNotificationsWithDeviceToken:token];
}
}
- (void)application:(UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)err
{
SEL selector = @selector(application:didFailToRegisterForRemoteNotificationsWithError:);
NSArray<id<UIApplicationDelegate>> *subcontractorsArray = [self getSubcontractorsImplementingSelector:selector];
for(id<UIApplicationDelegate> subcontractor in subcontractorsArray) {
[subcontractor application:application didFailToRegisterForRemoteNotificationsWithError:err];
}
}
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult result))completionHandler
{
SEL selector = @selector(application:didReceiveRemoteNotification:fetchCompletionHandler:);
NSArray<id<UIApplicationDelegate>> *subcontractorsArray = [self getSubcontractorsImplementingSelector:selector];
__block NSUInteger subcontractorsLeft = [subcontractorsArray count];
__block UIBackgroundFetchResult fetchResult = UIBackgroundFetchResultNoData;
__block NSObject *lock = [NSObject new];
void (^handler)(UIBackgroundFetchResult) = ^(UIBackgroundFetchResult result) {
@synchronized (lock) {
if (result == UIBackgroundFetchResultFailed) {
fetchResult = UIBackgroundFetchResultFailed;
} else if (fetchResult != UIBackgroundFetchResultFailed && result == UIBackgroundFetchResultNewData) {
fetchResult = UIBackgroundFetchResultNewData;
}
subcontractorsLeft--;
if (subcontractorsLeft == 0) {
completionHandler(fetchResult);
}
}
};
if (subcontractorsLeft == 0) {
completionHandler(fetchResult);
} else {
for (id<UIApplicationDelegate> subcontractor in subcontractorsArray) {
[subcontractor application:application didReceiveRemoteNotification:userInfo fetchCompletionHandler:handler];
}
}
}
#pragma mark - Subcontractors
- (void)ensureSubcontractorsAreInitializedAndSorted {
dispatch_once(&onceToken, ^{
subcontractors = [[NSMutableArray alloc] init];
subcontractorsForSelector = [NSMutableDictionary new];
NSArray<EXSingletonModule*> * singletonModules = [[EXModuleRegistryProvider singletonModules] allObjects];
for (EXSingletonModule *singletonModule in singletonModules) {
if ([singletonModule conformsToProtocol:@protocol(UIApplicationDelegate)]) {
[subcontractors addObject:(id<UIApplicationDelegate>)singletonModule];
}
}
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"priority"
ascending:NO];
[subcontractors sortUsingDescriptors:[NSArray arrayWithObject:sortDescriptor]];
});
}
- (NSArray<id<UIApplicationDelegate>> *)getSubcontractorsImplementingSelector:(SEL)selector {
[self ensureSubcontractorsAreInitializedAndSorted];
NSString *selectorKey = NSStringFromSelector(selector);
if (subcontractorsForSelector[selectorKey]) {
return subcontractorsForSelector[selectorKey];
}
NSMutableArray<id<UIApplicationDelegate>> *result = [NSMutableArray new];
for (id<UIApplicationDelegate> subcontractor in subcontractors) {
if ([subcontractor respondsToSelector:selector]) {
[result addObject:subcontractor];
}
}
subcontractorsForSelector[selectorKey] = result;
return result;
}
#endif // !TARGET_OS_OSX
@end

View File

@@ -0,0 +1,40 @@
// Copyright 2018-present 650 Industries. All rights reserved.
#pragma once
#import <ExpoModulesCore/Platform.h>
#import <Expo/RCTAppDelegateUmbrella.h>
NS_ASSUME_NONNULL_BEGIN
@class EXReactDelegate;
NS_SWIFT_NAME(ExpoReactRootViewFactory)
@interface EXReactRootViewFactory : RCTRootViewFactory
@property (nonatomic, weak, nullable) EXReactDelegate *reactDelegate;
/**
Initializer for ExpoReactNativeFactory integration
*/
- (instancetype)initWithReactDelegate:(nullable EXReactDelegate *)reactDelegate
configuration:(RCTRootViewFactoryConfiguration *)configuration
turboModuleManagerDelegate:(nullable id)turboModuleManagerDelegate;
/**
Calls super `viewWithModuleName:initialProperties:launchOptions:` from `RCTRootViewFactory`.
*/
#if TARGET_OS_IOS || TARGET_OS_TV
- (UIView *)superViewWithModuleName:(NSString *)moduleName
initialProperties:(nullable NSDictionary *)initialProperties
launchOptions:(nullable NSDictionary *)launchOptions
devMenuConfiguration:(nullable RCTDevMenuConfiguration *)devMenuConfiguration;
#else
- (UIView *)superViewWithModuleName:(NSString *)moduleName
initialProperties:(nullable NSDictionary *)initialProperties
launchOptions:(nullable NSDictionary *)launchOptions;
#endif
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,81 @@
// Copyright 2018-present 650 Industries. All rights reserved.
#import <Expo/EXReactRootViewFactory.h>
#import <Expo/RCTAppDelegateUmbrella.h>
#import <Expo/Swift.h>
#import <React/RCTDevMenu.h>
// When `use_frameworks!` is used, the generated Swift header is inside ExpoModulesCore module.
// Otherwise, it's available only locally with double-quoted imports.
#if __has_include(<ExpoModulesCore/ExpoModulesCore-Swift.h>)
#import <ExpoModulesCore/ExpoModulesCore-Swift.h>
#else
#import "ExpoModulesCore-Swift.h"
#endif
@interface RCTRootViewFactory ()
- (NSURL *)bundleURL;
@end
@implementation EXReactRootViewFactory
- (instancetype)initWithReactDelegate:(nullable EXReactDelegate *)reactDelegate
configuration:(RCTRootViewFactoryConfiguration *)configuration
turboModuleManagerDelegate:(nullable id<RCTTurboModuleManagerDelegate>)turboModuleManagerDelegate
{
if (self = [super initWithConfiguration:configuration andTurboModuleManagerDelegate:turboModuleManagerDelegate]) {
self.reactDelegate = reactDelegate;
}
return self;
}
#if TARGET_OS_IOS || TARGET_OS_TV
- (UIView *)viewWithModuleName:(NSString *)moduleName
initialProperties:(nullable NSDictionary *)initialProperties
launchOptions:(nullable NSDictionary *)launchOptions
devMenuConfiguration:(RCTDevMenuConfiguration *)devMenuConfiguration
{
if (self.reactDelegate != nil) {
return [self.reactDelegate createReactRootViewWithModuleName:moduleName initialProperties:initialProperties launchOptions:launchOptions];
}
return [super viewWithModuleName:moduleName initialProperties:initialProperties launchOptions:launchOptions devMenuConfiguration:devMenuConfiguration];
}
- (UIView *)superViewWithModuleName:(NSString *)moduleName
initialProperties:(nullable NSDictionary *)initialProperties
launchOptions:(nullable NSDictionary *)launchOptions
devMenuConfiguration:(nullable RCTDevMenuConfiguration *)devMenuConfiguration
{
if (devMenuConfiguration == nil) {
devMenuConfiguration = [RCTDevMenuConfiguration defaultConfiguration];
}
return [super viewWithModuleName:moduleName initialProperties:initialProperties launchOptions:launchOptions devMenuConfiguration:devMenuConfiguration];
}
#else
- (UIView *)viewWithModuleName:(NSString *)moduleName
initialProperties:(nullable NSDictionary *)initialProperties
launchOptions:(nullable NSDictionary *)launchOptions
{
if (self.reactDelegate != nil) {
return [self.reactDelegate createReactRootViewWithModuleName:moduleName initialProperties:initialProperties launchOptions:launchOptions];
}
return [super viewWithModuleName:moduleName initialProperties:initialProperties launchOptions:launchOptions];
}
- (UIView *)superViewWithModuleName:(NSString *)moduleName
initialProperties:(nullable NSDictionary *)initialProperties
launchOptions:(nullable NSDictionary *)launchOptions
{
return [super viewWithModuleName:moduleName initialProperties:initialProperties launchOptions:launchOptions];
}
#endif
- (NSURL *)bundleURL
{
return [self.reactDelegate bundleURL] ?: [super bundleURL];
}
@end

View File

@@ -0,0 +1,247 @@
import Foundation
import ExpoModulesCore
/**
Allows classes extending `ExpoAppDelegateSubscriber` to hook into project's app delegate
by forwarding `UIApplicationDelegate` events to the subscribers.
Keep functions and markers in sync with https://developer.apple.com/documentation/uikit/uiapplicationdelegate
*/
@objc(EXExpoAppDelegate)
open class ExpoAppDelegate: UIResponder, UIApplicationDelegate {
override public init() {
// The subscribers are initializing and registering before the main code starts executing.
// Here we're letting them know when the `AppDelegate` is being created,
// which happens at the beginning of the main code execution and before launching the app.
ExpoAppDelegateSubscriberRepository.subscribers.forEach {
$0.appDelegateWillBeginInitialization?()
}
super.init()
}
#if os(macOS)
required public init?(coder: NSCoder) {
super.init(coder: coder)
}
#endif
// MARK: - Initializing the App
#if os(iOS) || os(tvOS)
open func application(
_ application: UIApplication,
willFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil
) -> Bool {
return ExpoAppDelegateSubscriberManager.application(application, willFinishLaunchingWithOptions: launchOptions)
}
open func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil
) -> Bool {
return ExpoAppDelegateSubscriberManager.application(application, didFinishLaunchingWithOptions: launchOptions)
}
#elseif os(macOS)
open func applicationWillFinishLaunching(_ notification: Notification) {
ExpoAppDelegateSubscriberManager.applicationWillFinishLaunching(notification)
}
open func applicationDidFinishLaunching(_ notification: Notification) {
ExpoAppDelegateSubscriberManager.applicationDidFinishLaunching(notification)
}
// TODO: - Configuring and Discarding Scenes
#endif
// MARK: - Responding to App Life-Cycle Events
#if os(iOS) || os(tvOS)
@objc
open func applicationDidBecomeActive(_ application: UIApplication) {
ExpoAppDelegateSubscriberManager.applicationDidBecomeActive(application)
}
@objc
open func applicationWillResignActive(_ application: UIApplication) {
ExpoAppDelegateSubscriberManager.applicationWillResignActive(application)
}
@objc
open func applicationDidEnterBackground(_ application: UIApplication) {
ExpoAppDelegateSubscriberManager.applicationDidEnterBackground(application)
}
open func applicationWillEnterForeground(_ application: UIApplication) {
ExpoAppDelegateSubscriberManager.applicationWillEnterForeground(application)
}
open func applicationWillTerminate(_ application: UIApplication) {
ExpoAppDelegateSubscriberManager.applicationWillTerminate(application)
}
#elseif os(macOS)
@objc
open func applicationDidBecomeActive(_ notification: Notification) {
ExpoAppDelegateSubscriberManager.applicationDidBecomeActive(notification)
}
@objc
open func applicationWillResignActive(_ notification: Notification) {
ExpoAppDelegateSubscriberManager.applicationWillResignActive(notification)
}
@objc
open func applicationDidHide(_ notification: Notification) {
ExpoAppDelegateSubscriberManager.applicationDidHide(notification)
}
open func applicationWillUnhide(_ notification: Notification) {
ExpoAppDelegateSubscriberManager.applicationWillUnhide(notification)
}
open func applicationWillTerminate(_ notification: Notification) {
ExpoAppDelegateSubscriberManager.applicationWillTerminate(notification)
}
#endif
// MARK: - Responding to Environment Changes
#if os(iOS) || os(tvOS)
open func applicationDidReceiveMemoryWarning(_ application: UIApplication) {
ExpoAppDelegateSubscriberManager.applicationDidReceiveMemoryWarning(application)
}
#endif
// TODO: - Managing App State Restoration
// MARK: - Downloading Data in the Background
#if os(iOS) || os(tvOS)
open func application(
_ application: UIApplication,
handleEventsForBackgroundURLSession identifier: String,
completionHandler: @escaping () -> Void
) {
ExpoAppDelegateSubscriberManager.application(application, handleEventsForBackgroundURLSession: identifier, completionHandler: completionHandler)
}
#endif
// MARK: - Handling Remote Notification Registration
open func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
ExpoAppDelegateSubscriberManager.application(application, didRegisterForRemoteNotificationsWithDeviceToken: deviceToken)
}
open func application(_ application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: Error) {
ExpoAppDelegateSubscriberManager.application(application, didFailToRegisterForRemoteNotificationsWithError: error)
}
#if os(iOS) || os(tvOS)
open func application(
_ application: UIApplication,
didReceiveRemoteNotification userInfo: [AnyHashable: Any],
fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void
) {
ExpoAppDelegateSubscriberManager.application(application, didReceiveRemoteNotification: userInfo, fetchCompletionHandler: completionHandler)
}
#elseif os(macOS)
open func application(
_ application: NSApplication,
didReceiveRemoteNotification userInfo: [String: Any]
) {
ExpoAppDelegateSubscriberManager.application(application, didReceiveRemoteNotification: userInfo)
}
#endif
// MARK: - Continuing User Activity and Handling Quick Actions
open func application(_ application: UIApplication, willContinueUserActivityWithType userActivityType: String) -> Bool {
return ExpoAppDelegateSubscriberManager.application(application, willContinueUserActivityWithType: userActivityType)
}
#if os(iOS) || os(tvOS)
open func application(
_ application: UIApplication,
continue userActivity: NSUserActivity,
restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void
) -> Bool {
return ExpoAppDelegateSubscriberManager.application(application, continue: userActivity, restorationHandler: restorationHandler)
}
#elseif os(macOS)
open func application(
_ application: NSApplication,
continue userActivity: NSUserActivity,
restorationHandler: @escaping ([any NSUserActivityRestoring]) -> Void
) -> Bool {
return ExpoAppDelegateSubscriberManager.application(application, continue: userActivity, restorationHandler: restorationHandler)
}
#endif
open func application(_ application: UIApplication, didUpdate userActivity: NSUserActivity) {
return ExpoAppDelegateSubscriberManager.application(application, didUpdate: userActivity)
}
open func application(_ application: UIApplication, didFailToContinueUserActivityWithType userActivityType: String, error: Error) {
return ExpoAppDelegateSubscriberManager.application(application, didFailToContinueUserActivityWithType: userActivityType, error: error)
}
#if os(iOS)
open func application(
_ application: UIApplication,
performActionFor shortcutItem: UIApplicationShortcutItem,
completionHandler: @escaping (Bool) -> Void
) {
ExpoAppDelegateSubscriberManager.application(application, performActionFor: shortcutItem, completionHandler: completionHandler)
}
#endif
// MARK: - Background Fetch
#if os(iOS) || os(tvOS)
open func application(
_ application: UIApplication,
performFetchWithCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void
) {
ExpoAppDelegateSubscriberManager.application(application, performFetchWithCompletionHandler: completionHandler)
}
// TODO: - Interacting With WatchKit
// TODO: - Interacting With HealthKit
#endif
// MARK: - Opening a URL-Specified Resource
#if os(iOS) || os(tvOS)
open func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey: Any] = [:]) -> Bool {
return ExpoAppDelegateSubscriberManager.application(app, open: url, options: options)
}
#elseif os(macOS)
open func application(_ app: NSApplication, open urls: [URL]) {
ExpoAppDelegateSubscriberManager.application(app, open: urls)
}
#endif
// TODO: - Disallowing Specified App Extension Types
// TODO: - Handling SiriKit Intents
// TODO: - Handling CloudKit Invitations
// MARK: - Managing Interface Geometry
#if os(iOS)
/**
* Sets allowed orientations for the application. It will use the values from `Info.plist`as the orientation mask unless a subscriber requested
* a different orientation.
*/
open func application(_ application: UIApplication, supportedInterfaceOrientationsFor window: UIWindow?) -> UIInterfaceOrientationMask {
return ExpoAppDelegateSubscriberManager.application(application, supportedInterfaceOrientationsFor: window)
}
#endif
}

View File

@@ -0,0 +1,12 @@
// Copyright 2025-present 650 Industries. All rights reserved.
#import <ExpoModulesCore/ExpoModulesCore.h>
#import <Expo/RCTAppDelegateUmbrella.h>
@protocol RCTHostDelegate;
@protocol RCTHostRuntimeDelegate;
NS_SWIFT_NAME(ExpoReactNativeFactoryObjC)
@interface EXReactNativeFactory : RCTReactNativeFactory <RCTHostDelegate, RCTHostRuntimeDelegate>
@end

View File

@@ -0,0 +1,45 @@
// Copyright 2025-present 650 Industries. All rights reserved.
#import <Expo/ExpoReactNativeFactory.h>
#import <ExpoModulesCore/ExpoModulesCore.h>
#import <ExpoModulesCore/EXRuntime.h>
#if __has_include(<ExpoModulesCore/ExpoModulesCore-Swift.h>)
#import <ExpoModulesCore/ExpoModulesCore-Swift.h>
#else
#import "ExpoModulesCore-Swift.h"
#endif
#import <ReactCommon/RCTHost.h>
@implementation EXReactNativeFactory {
EXAppContext *_appContext;
}
#pragma mark - RCTHostDelegate
// [main thread]
- (void)hostDidStart:(nonnull RCTHost *)host
{
// Setting the runtime delegate here doesn't feel right, but there is no other way
// to capture the `host:didInitializeRuntime:` method call.
// With the current API design we also depend that the runtime is initialized after the host started,
// which isn't obvious, especially they are invoked on different threads.
// Ideally if the current `RCTHostRuntimeDelegate` is part of `RCTHostDelegate`.
host.runtimeDelegate = self;
}
#pragma mark - RCTHostRuntimeDelegate
// [JS thread]
- (void)host:(nonnull RCTHost *)host didInitializeRuntime:(jsi::Runtime &)runtime
{
_appContext = [[EXAppContext alloc] init];
// Inject and decorate the `global.expo` object
_appContext._runtime = [[EXRuntime alloc] initWithRuntime:runtime];
[_appContext setHostWrapper:[[EXHostWrapper alloc] initWithHost:host]];
[_appContext registerNativeModules];
}
@end

View File

@@ -0,0 +1,149 @@
// Copyright 2015-present 650 Industries. All rights reserved.
import React
public class ExpoReactNativeFactory: ExpoReactNativeFactoryObjC, ExpoReactNativeFactoryProtocol {
private let defaultModuleName = "main"
@MainActor
private lazy var reactDelegate: ExpoReactDelegate = {
ExpoReactDelegate(
handlers: ExpoAppDelegateSubscriberRepository.reactDelegateHandlers,
reactNativeFactory: self
)
}()
@objc public override init(delegate: any RCTReactNativeFactoryDelegate) {
let releaseLevel = (Bundle.main.object(forInfoDictionaryKey: "ReactNativeReleaseLevel") as? String)
.flatMap { [
"canary": RCTReleaseLevel.Canary,
"experimental": RCTReleaseLevel.Experimental,
"stable": RCTReleaseLevel.Stable
][$0.lowercased()]
}
?? RCTReleaseLevel.Stable
super.init(delegate: delegate, releaseLevel: releaseLevel)
}
@MainActor
@objc func createRCTRootViewFactory() -> RCTRootViewFactory {
// Alan: This is temporary. We need to cast to ExpoReactNativeFactoryDelegate here because currently, if you extend RCTReactNativeFactory
// from Swift, customizeRootView will not work on the new arch because the cast to RCTRootView will never
// succeed which breaks expo-splash-screen and react-native-bootsplash.
guard let weakDelegate = self.delegate as? ExpoReactNativeFactoryDelegate else {
fatalError("ExpoReactNativeFactory: delegate is nil.")
}
let bundleUrlBlock: RCTBundleURLBlock = { [weak weakDelegate] in
return weakDelegate?.bundleURL()
}
let configuration = RCTRootViewFactoryConfiguration(
bundleURLBlock: bundleUrlBlock,
newArchEnabled: weakDelegate.newArchEnabled()
)
configuration.createRootViewWithBridge = { bridge, moduleName, initProps in
return weakDelegate.createRootView(with: bridge, moduleName: moduleName, initProps: initProps)
}
configuration.jsRuntimeConfiguratorDelegate = delegate
configuration.createBridgeWithDelegate = { delegate, launchOptions in
weakDelegate.createBridge(with: delegate, launchOptions: launchOptions)
}
configuration.customizeRootView = { rootView in
weakDelegate.customize(rootView)
}
// NOTE(kudo): `sourceURLForBridge` is not referenced intentionally because it does not support New Architecture.
configuration.sourceURLForBridge = nil
configuration.loadSourceForBridgeWithProgress = { bridge, onProgress, onComplete in
weakDelegate.loadSource(for: bridge, onProgress: onProgress, onComplete: onComplete)
}
if weakDelegate.responds(to: #selector(RCTReactNativeFactoryDelegate.extraModules(for:))) {
configuration.extraModulesForBridge = { bridge in
weakDelegate.extraModules(for: bridge)
}
}
if weakDelegate.responds(to: #selector(RCTReactNativeFactoryDelegate.extraLazyModuleClasses(for:))) {
configuration.extraLazyModuleClassesForBridge = { bridge in
weakDelegate.extraLazyModuleClasses(for: bridge)
}
}
if weakDelegate.responds(to: #selector(RCTReactNativeFactoryDelegate.bridge(_:didNotFindModule:))) {
configuration.bridgeDidNotFindModule = { bridge, moduleName in
weakDelegate.bridge(bridge, didNotFindModule: moduleName)
}
}
return ExpoReactRootViewFactory(
reactDelegate: reactDelegate,
configuration: configuration,
turboModuleManagerDelegate: self
)
}
public func recreateRootView(
withBundleURL: URL?,
moduleName: String?,
initialProps: [AnyHashable: Any]?,
launchOptions: [AnyHashable: Any]?
) -> UIView {
guard let delegate = self.delegate else {
fatalError("recreateRootView: Missing RCTReactNativeFactoryDelegate")
}
let configuration = self.rootViewFactory.value(forKey: "_configuration") as? RCTRootViewFactoryConfiguration
if let bundleURL = withBundleURL {
configuration?.bundleURLBlock = {
return bundleURL
}
}
let rootView: UIView
if let factory = self.rootViewFactory as? ExpoReactRootViewFactory {
// RCTDevMenuConfiguration is only available in react-native 0.83+
#if os(iOS) || os(tvOS)
// When calling `recreateRootViewWithBundleURL:` from `EXReactRootViewFactory`,
// we don't want to loop the ReactDelegate again. Otherwise, it will be an infinite loop.
rootView = factory.superView(
withModuleName: moduleName ?? defaultModuleName,
initialProperties: initialProps,
launchOptions: launchOptions ?? [:],
devMenuConfiguration: self.devMenuConfiguration
)
#else
rootView = factory.superView(
withModuleName: moduleName ?? defaultModuleName,
initialProperties: initialProps,
launchOptions: launchOptions ?? [:]
)
#endif
} else {
#if os(iOS) || os(tvOS)
rootView = rootViewFactory.view(
withModuleName: moduleName ?? defaultModuleName,
initialProperties: initialProps,
launchOptions: launchOptions,
devMenuConfiguration: self.devMenuConfiguration
)
#else
rootView = rootViewFactory.view(
withModuleName: moduleName ?? defaultModuleName,
initialProperties: initialProps,
launchOptions: launchOptions
)
#endif
}
return rootView
}
}

View File

@@ -0,0 +1,13 @@
import React
open class ExpoReactNativeFactoryDelegate: RCTDefaultReactNativeFactoryDelegate {
open override func customize(_ rootView: UIView) {
ExpoAppDelegateSubscriberRepository.subscribers.forEach { $0.customizeRootView?(rootView) }
}
open override func createRootViewController() -> UIViewController {
return ExpoAppDelegateSubscriberRepository.reactDelegateHandlers.lazy
.compactMap { $0.createRootViewController() }
.first(where: { _ in true }) ?? UIViewController()
}
}

View File

@@ -0,0 +1,29 @@
// Copyright 2015-present 650 Industries. All rights reserved.
/**
`React-RCTAppDelegate-umbrella.h` requires `USE_HERMES`.
This umbrella wrapper lets us import `React-RCTAppDelegate-umbrella.h` in `Expo.h` without requiring `USE_HERMES` from app project settings.
*/
#if __has_include(<reacthermes/HermesExecutorFactory.h>)
#ifndef USE_HERMES
#define USE_HERMES 1
#define INLINE_USE_HERMES 1
#endif // USE_HERMES
#endif // __has_include(<reacthermes/HermesExecutorFactory.h>)
#if __has_include(<React_RCTAppDelegate/React-RCTAppDelegate-umbrella.h>)
#import <React_RCTAppDelegate/React-RCTAppDelegate-umbrella.h>
#else
#import <React_RCTAppDelegate/React_RCTAppDelegate-umbrella.h>
#endif
#if INLINE_USE_HERMES
#undef USE_HERMES
#undef INLINE_USE_HERMES
#endif // INLINE_USE_HERMES