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 options:EXSplashScreenDefault 43 successCallback:^(BOOL hasEffect){ resolve(@(hasEffect)); } 44 failureCallback:^(NSString *message){ reject(@"ERR_SPLASH_SCREEN_CANNOT_HIDE", message, nil); }]; 45 }); 46} 47 48EX_EXPORT_METHOD_AS(preventAutoHideAsync, 49 preventAutoHideWithResolve:(EXPromiseResolveBlock)resolve 50 reject:(EXPromiseRejectBlock)reject) 51{ 52 EX_WEAKIFY(self); 53 dispatch_async(dispatch_get_main_queue(), ^{ 54 EX_ENSURE_STRONGIFY(self); 55 UIViewController *currentViewController = [self reactViewController]; 56 [[self splashScreenService] preventSplashScreenAutoHideFor:currentViewController 57 options:EXSplashScreenDefault 58 successCallback:^(BOOL hasEffect){ resolve(@(hasEffect)); } 59 failureCallback:^(NSString *message){ reject(@"ERR_SPLASH_SCREEN_CANNOT_PREVENT_AUTOHIDE", message, nil); }]; 60 }); 61} 62 63# pragma mark - EXAppLifecycleListener 64 65- (void)onAppBackgrounded {} 66 67- (void)onAppForegrounded {} 68 69- (void)onAppContentDidAppear 70{ 71 EX_WEAKIFY(self); 72 dispatch_async(dispatch_get_main_queue(), ^{ 73 EX_ENSURE_STRONGIFY(self); 74 UIViewController* currentViewController = [self reactViewController]; 75 [[self splashScreenService] onAppContentDidAppear:currentViewController]; 76 }); 77} 78 79- (void)onAppContentWillReload 80{ 81 EX_WEAKIFY(self); 82 dispatch_async(dispatch_get_main_queue(), ^{ 83 EX_ENSURE_STRONGIFY(self); 84 UIViewController* currentViewController = [self reactViewController]; 85 [[self splashScreenService] onAppContentWillReload:currentViewController]; 86 }); 87} 88 89# pragma mark - internals 90 91/** 92 * Tries to obtain singleton module that is registered as "SplashScreen". 93 * Silent agreement is that registered module conforms to "EXSplashScreenService" protocol. 94 */ 95- (id<EXSplashScreenService>)splashScreenService 96{ 97 return [self.moduleRegistry getSingletonModuleForName:@"SplashScreen"]; 98} 99 100/** 101 * Tries to obtain a reference to the UIViewController for the main RCTRootView 102 * by iterating through all of the application's windows and their viewControllers 103 * until it finds one with a RCTRootView. 104 */ 105- (UIViewController *)reactViewController 106{ 107 dispatch_assert_queue(dispatch_get_main_queue()); 108 109 // first check to see if the host application has a module that provides the reference we want 110 // (this is the case in Expo Go and in the ExpoKit pod used in `expo build` apps) 111 id<EXSplashScreenUtilService> utilService = [_moduleRegistry getSingletonModuleForName:@"Util"]; 112 if (utilService != nil) { 113 return [utilService currentViewController]; 114 } 115 116 UIViewController *controller = [self viewControllerContainingRCTRootView]; 117 if (!controller) { 118 // no RCTRootView was found, so just fall back to the key window's root view controller 119 controller = self.utilities.currentViewController; 120 while ([controller isKindOfClass:[UIAlertController class]] && 121 controller.presentingViewController != nil) { 122 controller = controller.presentingViewController; 123 } 124 return controller; 125 } 126 127 UIViewController *presentedController = controller.presentedViewController; 128 while (presentedController && 129 ![presentedController isBeingDismissed] && 130 ![presentedController isKindOfClass:[UIAlertController class]]) { 131 controller = presentedController; 132 presentedController = controller.presentedViewController; 133 } 134 return controller; 135} 136 137- (nullable UIViewController *)viewControllerContainingRCTRootView 138{ 139 NSArray<UIWindow *> *allWindows; 140 if (@available(iOS 13, *)) { 141 NSSet<UIScene *> *allWindowScenes = UIApplication.sharedApplication.connectedScenes; 142 NSMutableArray<UIWindow *> *allForegroundWindows = [NSMutableArray new]; 143 for (UIScene *scene in allWindowScenes.allObjects) { 144 if ([scene isKindOfClass:[UIWindowScene class]] && scene.activationState == UISceneActivationStateForegroundActive) { 145 [allForegroundWindows addObjectsFromArray:((UIWindowScene *)scene).windows]; 146 } 147 } 148 allWindows = allForegroundWindows; 149 } else { 150 allWindows = UIApplication.sharedApplication.windows; 151 } 152 153 for (UIWindow *window in allWindows) { 154 UIViewController *controller = window.rootViewController; 155 if ([controller.view isKindOfClass:[RCTRootView class]]) { 156 return controller; 157 } 158 UIViewController *presentedController = controller.presentedViewController; 159 while (presentedController && ![presentedController isBeingDismissed]) { 160 if ([presentedController.view isKindOfClass:[RCTRootView class]]) { 161 return presentedController; 162 } 163 controller = presentedController; 164 presentedController = controller.presentedViewController; 165 } 166 } 167 168 // no RCTRootView was found 169 return nil; 170} 171 172@end 173