chore: add mobile app

This commit is contained in:
2026-02-07 22:33:02 +01:00
parent db34640acc
commit e89384ad4e
29327 changed files with 3886944 additions and 12 deletions
@@ -0,0 +1,17 @@
// Copyright 2018-present 650 Industries. All rights reserved.
import ExpoModulesCore
@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)
}
}
@@ -0,0 +1,30 @@
// Copyright 2018-present 650 Industries. All rights reserved.
#import <ExpoModulesCore/Platform.h>
#import <ExpoModulesCore/EXReactDelegateWrapper.h>
#import <Expo/RCTAppDelegateUmbrella.h>
NS_ASSUME_NONNULL_BEGIN
@interface RCTAppDelegate ()
@end
__deprecated_msg("EXAppDelegateWrapper is deprecated. Migrate your AppDelegate to Swift and use ExpoAppDelegate instead. EXAppDelegateWrapper will be removed in SDK 55.")
@interface EXAppDelegateWrapper : NSObject <UIApplicationDelegate, UISceneDelegate, RCTReactNativeFactoryDelegate>
@property (nonatomic, strong, readonly) EXReactDelegateWrapper *reactDelegate;
@property (nonatomic, strong, nullable) NSString *moduleName;
@property (nonatomic, strong, nullable) NSDictionary *initialProps;
/**
Currently (RN 0.76) `customizeRootView` signature in `RCTAppDelegate` is broken as it uses `RCTRootView` type,
but this type is no longer used. It should rather be `RCTSurfaceHostingView`, but for simplicity it could be just `UIView`.
We need a helper function in Objective-C to actually make it to work, otherwise the types will conflict in Swift.
*/
+ (void)customizeRootView:(nonnull UIView *)rootView byAppDelegate:(nonnull RCTAppDelegate *)appDelegate;
@end
NS_ASSUME_NONNULL_END
@@ -0,0 +1,112 @@
// Copyright 2018-present 650 Industries. All rights reserved.
#import <Expo/EXAppDelegateWrapper.h>
#import <Expo/RCTAppDelegateUmbrella.h>
#import <Expo/Swift.h>
#import <React/RCTComponentViewFactory.h> // Allows non-umbrella since it's coming from React-RCTFabric
#import <ReactCommon/RCTHost.h> // Allows non-umbrella because the header is not inside a clang module
@implementation EXAppDelegateWrapper {
EXExpoAppDelegate *_expoAppDelegate;
}
- (instancetype)init
{
if (self = [super init]) {
_expoAppDelegate = [EXExpoAppDelegate new];
}
return self;
}
// This needs to be implemented, otherwise forwarding won't be called.
// When the app starts, `UIApplication` uses it to check beforehand
// which `UIApplicationDelegate` selectors are implemented.
- (BOOL)respondsToSelector:(SEL)selector
{
return [super respondsToSelector:selector] || [_expoAppDelegate respondsToSelector:selector];
}
// Forwards all invocations to `ExpoAppDelegate` object.
- (id)forwardingTargetForSelector:(SEL)selector
{
return _expoAppDelegate;
}
#pragma mark - UIApplicationDelegate
// Make sure to override all necessary methods from `RCTAppDelegate` here, explicitly forwarding everything to `_expoAppDelegate`.
// `forwardingTargetForSelector` works only for methods that are not specified in this and `RCTAppDelegate` classes.
#if !TARGET_OS_OSX
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
return [_expoAppDelegate application:application didFinishLaunchingWithOptions:launchOptions];
}
- (void)applicationDidBecomeActive:(UIApplication *)application
{
return [_expoAppDelegate applicationDidBecomeActive:application];
}
#else
- (void)applicationDidFinishLaunching:(NSNotification *)notification
{
return [_expoAppDelegate applicationDidFinishLaunching:notification];
}
- (void)applicationDidBecomeActive:(NSNotification *)notification
{
return [_expoAppDelegate applicationDidBecomeActive:notification];
}
#endif
- (UIViewController *)createRootViewController
{
return [_expoAppDelegate.factory.delegate createRootViewController];
}
- (void)customizeRootView:(UIView *)rootView
{
[_expoAppDelegate.factory.delegate customizeRootView:rootView];
}
#pragma mark - RCTComponentViewFactoryComponentProvider
- (NSDictionary<NSString *, Class<RCTComponentViewProtocol>> *)thirdPartyFabricComponents
{
return self.dependencyProvider.thirdPartyFabricComponents;
}
#pragma mark - RCTHostDelegate
- (void)hostDidStart:(RCTHost *)host
{
}
- (void)host:(RCTHost *)host
didReceiveJSErrorStack:(NSArray<NSDictionary<NSString *, id> *> *)stack
message:(NSString *)message
exceptionId:(NSUInteger)exceptionId
isFatal:(BOOL)isFatal
{
}
- (id<RCTTurboModule>)getModuleInstanceFromClass:(Class)moduleClass
{
return [_expoAppDelegate.factory.delegate getModuleInstanceFromClass:moduleClass];
}
- (Class)getModuleClassFromName:(const char *)name {
return [_expoAppDelegate.factory.delegate getModuleClassFromName:name];
}
#pragma mark - Helpers
+ (void)customizeRootView:(nonnull UIView *)rootView byAppDelegate:(nonnull RCTAppDelegate *)appDelegate
{
[appDelegate customizeRootView:(RCTRootView *)rootView];
}
@end
@@ -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
@@ -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
@@ -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
@@ -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
@@ -0,0 +1,33 @@
// 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 EXAppDelegateWrapper integration
*/
- (instancetype)initWithReactDelegate:(nullable EXReactDelegate *)reactDelegate
configuration:(RCTRootViewFactoryConfiguration *)configuration
turboModuleManagerDelegate:(nullable id)turboModuleManagerDelegate;
/**
Calls super `viewWithModuleName:initialProperties:launchOptions:` from `RCTRootViewFactory`.
*/
- (UIView *)superViewWithModuleName:(NSString *)moduleName
initialProperties:(nullable NSDictionary *)initialProperties
launchOptions:(nullable NSDictionary *)launchOptions;
@end
NS_ASSUME_NONNULL_END
@@ -0,0 +1,55 @@
// Copyright 2018-present 650 Industries. All rights reserved.
#import <Expo/EXReactRootViewFactory.h>
#import <Expo/RCTAppDelegateUmbrella.h>
#import <Expo/Swift.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;
}
- (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];
}
- (NSURL *)bundleURL
{
return [self.reactDelegate bundleURL] ?: [super bundleURL];
}
@end
+288
View File
@@ -0,0 +1,288 @@
import Dispatch
import Foundation
import ExpoModulesCore
import ReactAppDependencyProvider
/**
Note: you cannot subclass Swift from Objective-C, use EXAppDelegateWrapper with Obj-C app delegates
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, @preconcurrency ReactNativeFactoryProvider, UIApplicationDelegate {
@objc public var factory: RCTReactNativeFactory?
private let defaultModuleName = "main"
private let defaultInitialProps = [AnyHashable: Any]()
public func bindReactNativeFactory(_ factory: RCTReactNativeFactory) {
self.factory = factory
}
public func recreateRootView(
withBundleURL: URL?,
moduleName: String?,
initialProps: [AnyHashable: Any]?,
launchOptions: [AnyHashable: Any]?
) -> UIView {
guard let delegate = self.factory?.delegate,
let rootViewFactory = self.factory?.rootViewFactory else {
fatalError("recreateRootView: Missing factory in ExpoAppDelegate")
}
if delegate.newArchEnabled() {
// chrfalch: rootViewFactory.reactHost is not available here in swift due to the underlying RCTHost type of the property. (todo: check)
assert(rootViewFactory.value(forKey: "reactHost") == nil, "recreateRootViewWithBundleURL: does not support when react instance is created")
} else {
assert(rootViewFactory.bridge == nil, "recreateRootViewWithBundleURL: does not support when react instance is created")
}
let configuration = rootViewFactory.value(forKey: "_configuration") as? RCTRootViewFactoryConfiguration
if let bundleURL = withBundleURL {
configuration?.bundleURLBlock = {
return bundleURL
}
}
let rootView: UIView
if let factory = rootViewFactory as? ExpoReactRootViewFactory {
// 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 ?? [:]
)
} else {
rootView = rootViewFactory.view(
withModuleName: moduleName ?? defaultModuleName,
initialProperties: initialProps,
launchOptions: launchOptions
)
}
return rootView
}
// 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
}
@@ -0,0 +1,138 @@
// Copyright 2015-present 650 Industries. All rights reserved.
import React
public class ExpoReactNativeFactory: RCTReactNativeFactory, ExpoReactNativeFactoryProtocol {
private let defaultModuleName = "main"
private lazy var reactDelegate: ExpoReactDelegate = {
ExpoReactDelegate(
handlers: ExpoAppDelegateSubscriberRepository.reactDelegateHandlers,
reactNativeFactory: self
)
}()
// TODO: Remove check when react-native-macos 0.81 is released
#if !os(macOS)
@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)
}
#endif
@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")
}
if RCTIsNewArchEnabled() {
// chrfalch: rootViewFactory.reactHost is not available here in swift due to the underlying RCTHost type of the property. (todo: check)
assert(self.rootViewFactory.value(forKey: "reactHost") == nil, "recreateRootViewWithBundleURL: does not support when react instance is created")
} else {
assert(self.rootViewFactory.bridge == nil, "recreateRootViewWithBundleURL: does not support when react instance is created")
}
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 {
// 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 ?? [:]
)
} else {
rootView = rootViewFactory.view(
withModuleName: moduleName ?? defaultModuleName,
initialProperties: initialProps,
launchOptions: launchOptions
)
}
return rootView
}
}
@@ -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()
}
}
@@ -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