1// Copyright 2018-present 650 Industries. All rights reserved. 2 3#import <EXSplashScreen/EXSplashScreenModule.h> 4#import <EXSplashScreen/EXSplashScreenService.h> 5#import <React/RCTRootView.h> 6#import <ExpoModulesCore/EXAppLifecycleService.h> 7#import <ExpoModulesCore/EXUtilities.h> 8 9@protocol EXSplashScreenUtilService 10 11- (UIViewController *)currentViewController; 12 13@end 14 15@interface EXSplashScreenModule () 16 17@property (nonatomic, weak) EXModuleRegistry *moduleRegistry; 18@property (nonatomic, weak) id<EXUtilitiesInterface> utilities; 19 20@end 21 22@implementation EXSplashScreenModule 23 24EX_EXPORT_MODULE(ExpoSplashScreen); 25 26- (void)setModuleRegistry:(EXModuleRegistry *)moduleRegistry 27{ 28 _moduleRegistry = moduleRegistry; 29 _utilities = [moduleRegistry getModuleImplementingProtocol:@protocol(EXUtilitiesInterface)]; 30 [[moduleRegistry getModuleImplementingProtocol:@protocol(EXAppLifecycleService)] registerAppLifecycleListener:self]; 31} 32 33EX_EXPORT_METHOD_AS(hideAsync, 34 hideWithResolve:(EXPromiseResolveBlock)resolve 35 reject:(EXPromiseRejectBlock)reject) 36{ 37 EX_WEAKIFY(self); 38 dispatch_async(dispatch_get_main_queue(), ^{ 39 EX_ENSURE_STRONGIFY(self); 40 UIViewController *currentViewController = [self reactViewController]; 41 [[self splashScreenService] hideSplashScreenFor:currentViewController 42 successCallback:^(BOOL hasEffect){ resolve(@(hasEffect)); } 43 failureCallback:^(NSString *message){ reject(@"ERR_SPLASH_SCREEN_CANNOT_HIDE", message, nil); }]; 44 }); 45} 46 47EX_EXPORT_METHOD_AS(preventAutoHideAsync, 48 preventAutoHideWithResolve:(EXPromiseResolveBlock)resolve 49 reject:(EXPromiseRejectBlock)reject) 50{ 51 EX_WEAKIFY(self); 52 dispatch_async(dispatch_get_main_queue(), ^{ 53 EX_ENSURE_STRONGIFY(self); 54 UIViewController *currentViewController = [self reactViewController]; 55 [[self splashScreenService] preventSplashScreenAutoHideFor:currentViewController 56 successCallback:^(BOOL hasEffect){ resolve(@(hasEffect)); } 57 failureCallback:^(NSString *message){ reject(@"ERR_SPLASH_SCREEN_CANNOT_PREVENT_AUTOHIDE", message, nil); }]; 58 }); 59} 60 61# pragma mark - EXAppLifecycleListener 62 63- (void)onAppBackgrounded {} 64 65- (void)onAppForegrounded {} 66 67- (void)onAppContentDidAppear 68{ 69 EX_WEAKIFY(self); 70 dispatch_async(dispatch_get_main_queue(), ^{ 71 EX_ENSURE_STRONGIFY(self); 72 UIViewController* currentViewController = [self reactViewController]; 73 [[self splashScreenService] onAppContentDidAppear:currentViewController]; 74 }); 75} 76 77- (void)onAppContentWillReload 78{ 79 EX_WEAKIFY(self); 80 dispatch_async(dispatch_get_main_queue(), ^{ 81 EX_ENSURE_STRONGIFY(self); 82 UIViewController* currentViewController = [self reactViewController]; 83 [[self splashScreenService] onAppContentWillReload:currentViewController]; 84 }); 85} 86 87# pragma mark - internals 88 89/** 90 * Tries to obtain singleton module that is registered as "SplashScreen". 91 * Silent agreement is that registered module conforms to "EXSplashScreenService" protocol. 92 */ 93- (id<EXSplashScreenService>)splashScreenService 94{ 95 return [self.moduleRegistry getSingletonModuleForName:@"SplashScreen"]; 96} 97 98/** 99 * Tries to obtain a reference to the UIViewController for the main RCTRootView 100 * by iterating through all of the application's windows and their viewControllers 101 * until it finds one with a RCTRootView. 102 */ 103- (UIViewController *)reactViewController 104{ 105 dispatch_assert_queue(dispatch_get_main_queue()); 106 107 // first check to see if the host application has a module that provides the reference we want 108 // (this is the case in Expo Go and in the ExpoKit pod used in `expo build` apps) 109 id<EXSplashScreenUtilService> utilService = [_moduleRegistry getSingletonModuleForName:@"Util"]; 110 if (utilService != nil) { 111 return [utilService currentViewController]; 112 } 113 114 UIViewController *controller = [self viewControllerContainingRCTRootView]; 115 if (!controller) { 116 // no RCTRootView was found, so just fall back to the key window's root view controller 117 controller = self.utilities.currentViewController; 118 while ([controller isKindOfClass:[UIAlertController class]] && 119 controller.presentingViewController != nil) { 120 controller = controller.presentingViewController; 121 } 122 return controller; 123 } 124 125 UIViewController *presentedController = controller.presentedViewController; 126 while (presentedController && 127 ![presentedController isBeingDismissed] && 128 ![presentedController isKindOfClass:[UIAlertController class]]) { 129 controller = presentedController; 130 presentedController = controller.presentedViewController; 131 } 132 return controller; 133} 134 135- (nullable UIViewController *)viewControllerContainingRCTRootView 136{ 137 NSArray<UIWindow *> *allWindows; 138 if (@available(iOS 13, *)) { 139 NSSet<UIScene *> *allWindowScenes = UIApplication.sharedApplication.connectedScenes; 140 NSMutableArray<UIWindow *> *allForegroundWindows = [NSMutableArray new]; 141 for (UIScene *scene in allWindowScenes.allObjects) { 142 if ([scene isKindOfClass:[UIWindowScene class]] && scene.activationState == UISceneActivationStateForegroundActive) { 143 [allForegroundWindows addObjectsFromArray:((UIWindowScene *)scene).windows]; 144 } 145 } 146 allWindows = allForegroundWindows; 147 } else { 148 allWindows = UIApplication.sharedApplication.windows; 149 } 150 151 for (UIWindow *window in allWindows) { 152 UIViewController *controller = window.rootViewController; 153 if ([controller.view isKindOfClass:[RCTRootView class]]) { 154 return controller; 155 } 156 UIViewController *presentedController = controller.presentedViewController; 157 while (presentedController && ![presentedController isBeingDismissed]) { 158 if ([presentedController.view isKindOfClass:[RCTRootView class]]) { 159 return presentedController; 160 } 161 controller = presentedController; 162 presentedController = controller.presentedViewController; 163 } 164 } 165 166 // no RCTRootView was found 167 return nil; 168} 169 170@end 171