// Copyright 2018-present 650 Industries. All rights reserved. #import #import #import #import #import @protocol EXSplashScreenUtilService - (UIViewController *)currentViewController; @end @interface EXSplashScreenModule () @property (nonatomic, weak) UMModuleRegistry *moduleRegistry; @property (nonatomic, weak) id utilities; @end @implementation EXSplashScreenModule UM_EXPORT_MODULE(ExpoSplashScreen); - (void)setModuleRegistry:(UMModuleRegistry *)moduleRegistry { _moduleRegistry = moduleRegistry; _utilities = [moduleRegistry getModuleImplementingProtocol:@protocol(UMUtilitiesInterface)]; [[moduleRegistry getModuleImplementingProtocol:@protocol(UMAppLifecycleService)] registerAppLifecycleListener:self]; } UM_EXPORT_METHOD_AS(hideAsync, hideWithResolve:(UMPromiseResolveBlock)resolve reject:(UMPromiseRejectBlock)reject) { UM_WEAKIFY(self); dispatch_async(dispatch_get_main_queue(), ^{ UM_ENSURE_STRONGIFY(self); UIViewController *currentViewController = [self reactViewController]; [[self splashScreenService] hideSplashScreenFor:currentViewController successCallback:^(BOOL hasEffect){ resolve(@(hasEffect)); } failureCallback:^(NSString *message){ reject(@"ERR_SPLASH_SCREEN_CANNOT_HIDE", message, nil); }]; }); } UM_EXPORT_METHOD_AS(preventAutoHideAsync, preventAutoHideWithResolve:(UMPromiseResolveBlock)resolve reject:(UMPromiseRejectBlock)reject) { UM_WEAKIFY(self); dispatch_async(dispatch_get_main_queue(), ^{ UM_ENSURE_STRONGIFY(self); UIViewController *currentViewController = [self reactViewController]; [[self splashScreenService] preventSplashScreenAutoHideFor:currentViewController successCallback:^(BOOL hasEffect){ resolve(@(hasEffect)); } failureCallback:^(NSString *message){ reject(@"ERR_SPLASH_SCREEN_CANNOT_PREVENT_AUTOHIDE", message, nil); }]; }); } # pragma mark - UMAppLifecycleListener - (void)onAppBackgrounded {} - (void)onAppForegrounded {} - (void)onAppContentDidAppear { UM_WEAKIFY(self); dispatch_async(dispatch_get_main_queue(), ^{ UM_ENSURE_STRONGIFY(self); UIViewController* currentViewController = [self reactViewController]; [[self splashScreenService] onAppContentDidAppear:currentViewController]; }); } - (void)onAppContentWillReload { UM_WEAKIFY(self); dispatch_async(dispatch_get_main_queue(), ^{ UM_ENSURE_STRONGIFY(self); UIViewController* currentViewController = [self reactViewController]; [[self splashScreenService] onAppContentWillReload:currentViewController]; }); } # pragma mark - internals /** * Tries to obtain singleton module that is registered as "SplashScreen". * Silent agreement is that registered module conforms to "EXSplashScreenService" protocol. */ - (id)splashScreenService { return [self.moduleRegistry getSingletonModuleForName:@"SplashScreen"]; } /** * Tries to obtain a reference to the UIViewController for the main RCTRootView * by iterating through all of the application's windows and their viewControllers * until it finds one with a RCTRootView. */ - (UIViewController *)reactViewController { dispatch_assert_queue(dispatch_get_main_queue()); // first check to see if the host application has a module that provides the reference we want // (this is the case in Expo Go and in the ExpoKit pod used in `expo build` apps) id utilService = [_moduleRegistry getSingletonModuleForName:@"Util"]; if (utilService != nil) { return [utilService currentViewController]; } UIViewController *controller = [self viewControllerContainingRCTRootView]; if (!controller) { // no RCTRootView was found, so just fall back to the key window's root view controller return self.utilities.currentViewController; } UIViewController *presentedController = controller.presentedViewController; while (presentedController && ![presentedController isBeingDismissed]) { controller = presentedController; presentedController = controller.presentedViewController; } return controller; } - (nullable UIViewController *)viewControllerContainingRCTRootView { NSArray *allWindows; if (@available(iOS 13, *)) { NSSet *allWindowScenes = UIApplication.sharedApplication.connectedScenes; NSMutableArray *allForegroundWindows = [NSMutableArray new]; for (UIScene *scene in allWindowScenes.allObjects) { if ([scene isKindOfClass:[UIWindowScene class]] && scene.activationState == UISceneActivationStateForegroundActive) { [allForegroundWindows addObjectsFromArray:((UIWindowScene *)scene).windows]; } } allWindows = allForegroundWindows; } else { allWindows = UIApplication.sharedApplication.windows; } for (UIWindow *window in allWindows) { UIViewController *controller = window.rootViewController; if ([controller.view isKindOfClass:[RCTRootView class]]) { return controller; } UIViewController *presentedController = controller.presentedViewController; while (presentedController && ![presentedController isBeingDismissed]) { if ([presentedController.view isKindOfClass:[RCTRootView class]]) { return presentedController; } controller = presentedController; presentedController = controller.presentedViewController; } } // no RCTRootView was found return nil; } @end