diff --git a/lib/ios/RNNAnimator.h b/lib/ios/RNNAnimator.h index e2be02a208c..64c991f364c 100644 --- a/lib/ios/RNNAnimator.h +++ b/lib/ios/RNNAnimator.h @@ -3,6 +3,8 @@ #import "RNNElementView.h" @interface RNNAnimator : NSObject + +-(instancetype)initWithAnimationsDictionary:(NSDictionary *)animationsDic; -(void)setupTransition:(NSDictionary*)data; @end diff --git a/lib/ios/RNNAnimator.m b/lib/ios/RNNAnimator.m index 71438f252ee..51a94902a4d 100644 --- a/lib/ios/RNNAnimator.m +++ b/lib/ios/RNNAnimator.m @@ -21,6 +21,17 @@ @interface RNNAnimator() @implementation RNNAnimator +- (instancetype)initWithAnimationsDictionary:(NSDictionary *)animationsDic { + self = [super init]; + if (animationsDic) { + [self setupTransition:animationsDic]; + } else { + return nil; + } + + return self; +} + -(void)setupTransition:(NSDictionary*)data{ if ([data objectForKey:@"animations"]) { self.animations= [data objectForKey:@"animations"]; diff --git a/lib/ios/RNNBridgeModule.m b/lib/ios/RNNBridgeModule.m index 533a0415f94..65cd11e0729 100644 --- a/lib/ios/RNNBridgeModule.m +++ b/lib/ios/RNNBridgeModule.m @@ -26,8 +26,10 @@ -(instancetype)initWithCommandsHandler:(RNNCommandsHandler *)commandsHandler { [_commandsHandler setOptions:containerId options:options]; } -RCT_EXPORT_METHOD(push:(NSString*)containerId layout:(NSDictionary*)layout) { - [_commandsHandler push:containerId layout:layout]; +RCT_EXPORT_METHOD(push:(NSString*)containerId layout:(NSDictionary*)layout resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject) { + [_commandsHandler push:containerId layout:layout completion:^(id result) { + resolve(result); + }]; } RCT_EXPORT_METHOD(pop:(NSString*)containerId options:(NSDictionary*)options) { @@ -42,8 +44,10 @@ -(instancetype)initWithCommandsHandler:(RNNCommandsHandler *)commandsHandler { [_commandsHandler popToRoot:containerId]; } -RCT_EXPORT_METHOD(showModal:(NSDictionary*)layout) { - [_commandsHandler showModal:layout]; +RCT_EXPORT_METHOD(showModal:(NSDictionary*)layout resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject) { + [_commandsHandler showModal:layout completion:^(id containerID) { + resolve(containerID); + }]; } RCT_EXPORT_METHOD(dismissModal:(NSString*)containerId) { diff --git a/lib/ios/RNNCommandsHandler.h b/lib/ios/RNNCommandsHandler.h index 522208a3ae5..25013402bad 100644 --- a/lib/ios/RNNCommandsHandler.h +++ b/lib/ios/RNNCommandsHandler.h @@ -12,7 +12,7 @@ -(void) setOptions:(NSString*)containerId options:(NSDictionary*)options; --(void) push:(NSString*)containerId layout:(NSDictionary*)layout; +-(void) push:(NSString*)containerId layout:(NSDictionary*)layout completion:(RNNTransitionCompletionBlock)completion; -(void) pop:(NSString*)containerId options:(NSDictionary*)options; @@ -20,7 +20,7 @@ -(void) popToRoot:(NSString*)containerId; --(void) showModal:(NSDictionary*)layout; +-(void) showModal:(NSDictionary*)layout completion:(RNNTransitionCompletionBlock)completion; -(void) dismissModal:(NSString*)containerId; diff --git a/lib/ios/RNNCommandsHandler.m b/lib/ios/RNNCommandsHandler.m index e6f62ff8eb4..99d0651c9f5 100644 --- a/lib/ios/RNNCommandsHandler.m +++ b/lib/ios/RNNCommandsHandler.m @@ -3,6 +3,7 @@ #import "RNNNavigationStackManager.h" #import "RNNNavigationOptions.h" #import "RNNRootViewController.h" +#import "React/RCTUIManager.h" @implementation RNNCommandsHandler { RNNControllerFactory *_controllerFactory; @@ -47,20 +48,12 @@ -(void) setOptions:(NSString*)containerId options:(NSDictionary*)options { } } --(void)push:(NSString*)containerId layout:(NSDictionary*)layout { +-(void) push:(NSString*)containerId layout:(NSDictionary*)layout completion:(RNNTransitionCompletionBlock)completion { [self assertReady]; - NSDictionary* customAnimation = layout[@"data"][@"customTransition"]; UIViewController *newVc = [_controllerFactory createLayoutAndSaveToStore:layout]; - RCTBridge* bridge = _bridge; - if (customAnimation) { - if ([customAnimation objectForKey:@"animations"]) { - [_navigationStackManager push:newVc onTop:containerId customAnimationData:(NSDictionary*)customAnimation bridge:bridge]; - } else { - [[NSException exceptionWithName:NSInvalidArgumentException reason:@"unsupported transitionAnimation" userInfo:nil] raise]; - } - } else { - [_navigationStackManager push:newVc onTop:containerId customAnimationData:(NSDictionary*)nil bridge:bridge]; - } + UIViewController *fromVc = [_store findContainerForId:containerId]; + [_bridge.uiManager setAvailableSize:fromVc.view.bounds.size forRootView:newVc.view]; + [_navigationStackManager push:newVc onTop:containerId completion:completion]; } -(void)pop:(NSString*)containerId options:(NSDictionary*)options{ @@ -90,11 +83,11 @@ -(void) popToRoot:(NSString*)containerId { [_navigationStackManager popToRoot:containerId]; } --(void) showModal:(NSDictionary*)layout { +-(void) showModal:(NSDictionary*)layout completion:(RNNTransitionCompletionBlock)completion { [self assertReady]; UIViewController *newVc = [_controllerFactory createLayoutAndSaveToStore:layout]; - [_modalManager showModal:newVc]; + [_modalManager showModal:newVc completion:completion]; } -(void) dismissModal:(NSString*)containerId { diff --git a/lib/ios/RNNControllerFactory.m b/lib/ios/RNNControllerFactory.m index bad4b0156c4..c29e3e061ca 100644 --- a/lib/ios/RNNControllerFactory.m +++ b/lib/ios/RNNControllerFactory.m @@ -78,9 +78,11 @@ - (UIViewController*)fromTree:(NSDictionary*)json { - (RNNRootViewController*)createContainer:(RNNLayoutNode*)node { NSString* name = node.data[@"name"]; + NSDictionary* customTransition = node.data[@"customTransition"]; + RNNAnimator* animator = [[RNNAnimator alloc] initWithAnimationsDictionary:customTransition]; RNNNavigationOptions* options = [[RNNNavigationOptions alloc] initWithDict:node.data[@"navigationOptions"]]; NSString* containerId = node.nodeId; - return [[RNNRootViewController alloc] initWithName:name withOptions:options withContainerId:containerId rootViewCreator:_creator eventEmitter:_eventEmitter]; + return [[RNNRootViewController alloc] initWithName:name withOptions:options withContainerId:containerId rootViewCreator:_creator eventEmitter:_eventEmitter animator:animator]; } - (RNNNavigationController*)createContainerStack:(RNNLayoutNode*)node { diff --git a/lib/ios/RNNModalManager.h b/lib/ios/RNNModalManager.h index b46233ff09c..e9d8635935d 100644 --- a/lib/ios/RNNModalManager.h +++ b/lib/ios/RNNModalManager.h @@ -7,7 +7,7 @@ @property (nonatomic, strong) UIViewController* toVC; -(instancetype)initWithStore:(RNNStore*)store; --(void)showModal:(UIViewController*)viewController; +-(void)showModal:(UIViewController*)viewController completion:(RNNTransitionCompletionBlock)completion; -(void)dismissModal:(NSString*)containerId; -(void)dismissAllModals; diff --git a/lib/ios/RNNModalManager.m b/lib/ios/RNNModalManager.m index 895be604aaf..9e6f3660805 100644 --- a/lib/ios/RNNModalManager.m +++ b/lib/ios/RNNModalManager.m @@ -3,6 +3,7 @@ @implementation RNNModalManager { RNNStore *_store; + RNNTransitionCompletionBlock _completionBlock; } @@ -21,16 +22,16 @@ -(void)waitForContentToAppearAndThen:(SEL)nameOfSelector { -(void)showModalAfterLoad:(NSDictionary*)notif { [[NSNotificationCenter defaultCenter] removeObserver:self name:@"RCTContentDidAppearNotification" object:nil]; UIViewController *topVC = [self topPresentedVC]; - [topVC presentViewController:self.toVC animated:YES completion:nil]; + [topVC presentViewController:self.toVC animated:YES completion:^{ + if (_completionBlock) { + _completionBlock([_store containerKeyForInstance:self.toVC]); + } + }]; } -//-(void)prepareShowModal{ -// -//} - --(void)showModal:(UIViewController *)viewController { +-(void)showModal:(UIViewController *)viewController completion:(RNNTransitionCompletionBlock)completion { self.toVC = viewController; -// [self prepareShowModal] + _completionBlock = completion; [self waitForContentToAppearAndThen:@selector(showModalAfterLoad:)]; } diff --git a/lib/ios/RNNNavigationStackManager.h b/lib/ios/RNNNavigationStackManager.h index aa2d695e9e2..2f19e42f04f 100644 --- a/lib/ios/RNNNavigationStackManager.h +++ b/lib/ios/RNNNavigationStackManager.h @@ -10,7 +10,7 @@ -(instancetype)initWithStore:(RNNStore*)store; --(void)push:(UIViewController*)newTop onTop:(NSString*)containerId customAnimationData:(NSDictionary*)customAnimationData bridge:(RCTBridge*)bridge; +-(void)push:(UIViewController*)newTop onTop:(NSString*)containerId completion:(RNNTransitionCompletionBlock)completion; -(void)pop:(NSString*)containerId withAnimationData:(NSDictionary*)animationData; -(void)popTo:(NSString*)containerId; -(void)popToRoot:(NSString*)containerId; diff --git a/lib/ios/RNNNavigationStackManager.m b/lib/ios/RNNNavigationStackManager.m index b366243ff2a..9cae0549587 100644 --- a/lib/ios/RNNNavigationStackManager.m +++ b/lib/ios/RNNNavigationStackManager.m @@ -1,12 +1,12 @@ #import "RNNNavigationStackManager.h" #import "RNNRootViewController.h" -#import "React/RCTUIManager.h" #import "RNNAnimator.h" dispatch_queue_t RCTGetUIManagerQueue(void); @implementation RNNNavigationStackManager { RNNStore *_store; + RNNTransitionCompletionBlock _completionBlock; } -(instancetype)initWithStore:(RNNStore*)store { @@ -15,30 +15,25 @@ -(instancetype)initWithStore:(RNNStore*)store { return self; } - --(void)push:(UIViewController *)newTop onTop:(NSString *)containerId customAnimationData:(NSDictionary*)customAnimationData bridge:(RCTBridge*)bridge { +-(void)push:(UIViewController *)newTop onTop:(NSString *)containerId completion:(RNNTransitionCompletionBlock)completion { UIViewController *vc = [_store findContainerForId:containerId]; - [self preparePush:newTop onTopVC:vc customAnimationData:customAnimationData bridge:bridge]; + [self preparePush:newTop onTopVC:vc completion:completion]; [self waitForContentToAppearAndThen:@selector(pushAfterLoad:)]; } --(void)preparePush:(UIViewController *)newTop onTopVC:(UIViewController*)vc customAnimationData:(NSDictionary*)customAnimationData bridge:(RCTBridge*)bridge { - if (customAnimationData) { +-(void)preparePush:(UIViewController *)newTop onTopVC:(UIViewController*)vc completion:(RNNTransitionCompletionBlock)completion { + self.toVC = (RNNRootViewController*)newTop; + self.fromVC = vc; + + if (self.toVC.isAnimated) { RNNRootViewController* newTopRootView = (RNNRootViewController*)newTop; - self.fromVC = vc; - self.toVC = newTopRootView; vc.navigationController.delegate = newTopRootView; - [newTopRootView.animator setupTransition:customAnimationData]; - RCTUIManager *uiManager = bridge.uiManager; - CGRect screenBound = [vc.view bounds]; - CGSize screenSize = screenBound.size; - [uiManager setAvailableSize:screenSize forRootView:self.toVC.view]; } else { - self.fromVC = vc; - self.toVC = (RNNRootViewController*)newTop; vc.navigationController.delegate = nil; self.fromVC.navigationController.interactivePopGestureRecognizer.delegate = nil; } + + _completionBlock = completion; } -(void)waitForContentToAppearAndThen:(SEL)nameOfSelector { @@ -50,7 +45,17 @@ -(void)waitForContentToAppearAndThen:(SEL)nameOfSelector { -(void)pushAfterLoad:(NSDictionary*)notif { [[NSNotificationCenter defaultCenter] removeObserver:self name:@"RCTContentDidAppearNotification" object:nil]; + [CATransaction begin]; + [CATransaction setCompletionBlock:^{ + if (_completionBlock) { + _completionBlock(self.toVC.containerId); + _completionBlock = nil; + } + }]; + [[self.fromVC navigationController] pushViewController:self.toVC animated:YES]; + [CATransaction commit]; + self.toVC = nil; self.fromVC.navigationController.interactivePopGestureRecognizer.delegate = nil; self.fromVC = nil; diff --git a/lib/ios/RNNRootViewController.h b/lib/ios/RNNRootViewController.h index 6b8c7876042..2ef3ea4afe3 100644 --- a/lib/ios/RNNRootViewController.h +++ b/lib/ios/RNNRootViewController.h @@ -6,6 +6,7 @@ #import "RNNEventEmitter.h" #import "RNNNavigationOptions.h" #import "RNNAnimator.h" + @interface RNNRootViewController : UIViewController @property (nonatomic, strong) RNNNavigationOptions* navigationOptions; @property (nonatomic, strong) RNNAnimator* animator; @@ -16,9 +17,11 @@ withOptions:(RNNNavigationOptions*)options withContainerId:(NSString*)containerId rootViewCreator:(id)creator - eventEmitter:(RNNEventEmitter*)eventEmitter; + eventEmitter:(RNNEventEmitter*)eventEmitter + animator:(RNNAnimator*)animator; --(void) applyNavigationButtons; +-(void)applyNavigationButtons; +-(BOOL)isAnimated; @end diff --git a/lib/ios/RNNRootViewController.m b/lib/ios/RNNRootViewController.m index 00775d5ae27..f9eb3d948e7 100644 --- a/lib/ios/RNNRootViewController.m +++ b/lib/ios/RNNRootViewController.m @@ -17,19 +17,20 @@ -(instancetype)initWithName:(NSString*)name withOptions:(RNNNavigationOptions*)options withContainerId:(NSString*)containerId rootViewCreator:(id)creator - eventEmitter:(RNNEventEmitter*)eventEmitter { + eventEmitter:(RNNEventEmitter*)eventEmitter + animator:(RNNAnimator *)animator { self = [super init]; self.containerId = containerId; self.containerName = name; self.navigationOptions = options; self.eventEmitter = eventEmitter; + self.animator = animator; self.view = [creator createRootView:self.containerName rootViewId:self.containerId]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onJsReload) name:RCTJavaScriptWillStartLoadingNotification object:nil]; - self.animator = [[RNNAnimator alloc] init]; self.navigationController.modalPresentationStyle = UIModalPresentationCustom; self.navigationController.delegate = self; self.navigationButtons = [[RNNNavigationButtons alloc] initWithViewController:self]; @@ -46,6 +47,10 @@ - (void)viewDidLoad { [super viewDidLoad]; } +-(BOOL)isAnimated { + return self.animator; +} + - (BOOL)prefersStatusBarHidden { if ([self.navigationOptions.statusBarHidden boolValue]) { return YES; diff --git a/lib/ios/RNNStore.h b/lib/ios/RNNStore.h index b9d82a99756..910a8978137 100644 --- a/lib/ios/RNNStore.h +++ b/lib/ios/RNNStore.h @@ -3,6 +3,8 @@ #import #import "RNNRootViewController.h" +typedef void (^RNNTransitionCompletionBlock)(id result); + @interface RNNStore : NSObject -(UIViewController*) findContainerForId:(NSString*)containerId; diff --git a/lib/ios/ReactNativeNavigationTests/RNNCommandsHandlerTest.m b/lib/ios/ReactNativeNavigationTests/RNNCommandsHandlerTest.m index a3114b29d5e..59ae3ac2b0d 100644 --- a/lib/ios/ReactNativeNavigationTests/RNNCommandsHandlerTest.m +++ b/lib/ios/ReactNativeNavigationTests/RNNCommandsHandlerTest.m @@ -30,10 +30,10 @@ - (void)testAssertReadyForEachMethodThrowsExceptoins { [self.store setReadyToReceiveCommands:false]; for (NSString* methodName in methods) { SEL s = NSSelectorFromString(methodName); - NSMethodSignature* signature = [self.uut methodSignatureForSelector:s]; - NSInvocation* invocation = [NSInvocation invocationWithMethodSignature:signature]; - invocation.selector = s; - XCTAssertThrowsSpecificNamed([invocation invokeWithTarget:self.uut], NSException, @"BridgeNotLoadedError"); + IMP imp = [self.uut methodForSelector:s]; + void (*func)(id, SEL, id, id, id) = (void *)imp; + + XCTAssertThrowsSpecificNamed(func(self.uut,s, nil, nil, nil), NSException, @"BridgeNotLoadedError"); } } @@ -70,7 +70,8 @@ -(void)testDynamicStylesMergeWithStaticStyles { withOptions:initialOptions withContainerId:@"containerId" rootViewCreator:[[RNNTestRootViewCreator alloc] init] - eventEmitter:nil]; + eventEmitter:nil + animator:nil]; RNNNavigationController* nav = [[RNNNavigationController alloc] initWithRootViewController:vc]; [vc viewWillAppear:false]; XCTAssertTrue([vc.navigationItem.title isEqual:@"the title"]); diff --git a/lib/ios/ReactNativeNavigationTests/RNNRootViewControllerTest.m b/lib/ios/ReactNativeNavigationTests/RNNRootViewControllerTest.m index 02d0863fccd..eab35a51193 100644 --- a/lib/ios/ReactNativeNavigationTests/RNNRootViewControllerTest.m +++ b/lib/ios/ReactNativeNavigationTests/RNNRootViewControllerTest.m @@ -27,7 +27,7 @@ - (void)setUp { self.containerId = @"cntId"; self.emitter = nil; self.options = [RNNNavigationOptions new]; - self.uut = [[RNNRootViewController alloc] initWithName:self.pageName withOptions:self.options withContainerId:self.containerId rootViewCreator:self.creator eventEmitter:self.emitter]; + self.uut = [[RNNRootViewController alloc] initWithName:self.pageName withOptions:self.options withContainerId:self.containerId rootViewCreator:self.creator eventEmitter:self.emitter animator:nil]; } -(void)testTopBarBackgroundColor_validColor{ diff --git a/lib/src/adapters/NativeCommandsSender.js b/lib/src/adapters/NativeCommandsSender.js index a5b15d8076a..aedc958edd1 100644 --- a/lib/src/adapters/NativeCommandsSender.js +++ b/lib/src/adapters/NativeCommandsSender.js @@ -17,8 +17,9 @@ class NativeCommandsSender { this.nativeCommandsModule.setOptions(containerId, options); } - push(onContainerId, layout) { - return this.nativeCommandsModule.push(onContainerId, layout); + async push(onContainerId, layout) { + const pushedContainerId = await this.nativeCommandsModule.push(onContainerId, layout); + return pushedContainerId; } pop(containerId, options) { @@ -33,8 +34,9 @@ class NativeCommandsSender { return this.nativeCommandsModule.popToRoot(containerId); } - showModal(layout) { - return this.nativeCommandsModule.showModal(layout); + async showModal(layout) { + const completed = await this.nativeCommandsModule.showModal(layout); + return completed; } dismissModal(containerId) {