Browse Source

Decoupled native stack behavior from RNNStore

yogevbd 6 years ago
parent
commit
51ffeee605

+ 52
- 29
lib/ios/RNNCommandsHandler.m View File

@@ -27,9 +27,9 @@ static NSString* const setDefaultOptions	= @"setDefaultOptions";
27 27
 @implementation RNNCommandsHandler {
28 28
 	RNNControllerFactory *_controllerFactory;
29 29
 	RNNStore *_store;
30
-	RNNNavigationStackManager* _navigationStackManager;
31 30
 	RNNModalManager* _modalManager;
32 31
 	RNNOverlayManager* _overlayManager;
32
+	RNNNavigationStackManager* _stackManager;
33 33
 	RNNEventEmitter* _eventEmitter;
34 34
 }
35 35
 
@@ -38,8 +38,8 @@ static NSString* const setDefaultOptions	= @"setDefaultOptions";
38 38
 	_store = store;
39 39
 	_controllerFactory = controllerFactory;
40 40
 	_eventEmitter = eventEmitter;
41
-	_navigationStackManager = [[RNNNavigationStackManager alloc] initWithStore:_store];
42 41
 	_modalManager = [[RNNModalManager alloc] initWithStore:_store];
42
+	_stackManager = [[RNNNavigationStackManager alloc] init];
43 43
 	_overlayManager = [[RNNOverlayManager alloc] initWithStore:_store];
44 44
 	return self;
45 45
 }
@@ -98,8 +98,9 @@ static NSString* const setDefaultOptions	= @"setDefaultOptions";
98 98
 -(void)push:(NSString*)componentId layout:(NSDictionary*)layout completion:(RNNTransitionCompletionBlock)completion rejection:(RCTPromiseRejectBlock)rejection {
99 99
 	[self assertReady];
100 100
 
101
-	UIViewController<RNNRootViewProtocol, UIViewControllerPreviewingDelegate> *newVc = [_controllerFactory createLayoutAndSaveToStore:layout];
102
-
101
+	RNNRootViewController *newVc = (RNNRootViewController *)[_controllerFactory createLayoutAndSaveToStore:layout];
102
+	UIViewController *fromVC = [_store findComponentForId:componentId];
103
+	
103 104
 	if (newVc.options.preview.elementId) {
104 105
 		UIViewController* vc = [_store findComponentForId:componentId];
105 106
 
@@ -127,19 +128,23 @@ static NSString* const setDefaultOptions	= @"setDefaultOptions";
127 128
 			[rootVc registerForPreviewingWithDelegate:(id)rootVc sourceView:elementView];
128 129
 		}
129 130
 	} else {
130
-		[_navigationStackManager push:newVc onTop:componentId completion:^{
131
-			[_eventEmitter sendOnNavigationCommandCompletion:push params:@{@"componentId": componentId}];
132
-			completion();
133
-		} rejection:rejection];
131
+		[newVc onReactViewReady:^{
132
+			id animationDelegate = (newVc.options.animations.push.hasCustomAnimation || newVc.isCustomTransitioned) ? newVc : nil;
133
+			[_stackManager push:newVc onTop:fromVC animated:newVc.options.animations.push.enable animationDelegate:animationDelegate completion:^{
134
+				[_eventEmitter sendOnNavigationCommandCompletion:push params:@{@"componentId": componentId}];
135
+				completion();
136
+			} rejection:rejection];
137
+		}];
134 138
 	}
135 139
 }
136 140
 
137 141
 -(void)setStackRoot:(NSString*)componentId layout:(NSDictionary*)layout completion:(RNNTransitionCompletionBlock)completion rejection:(RCTPromiseRejectBlock)rejection {
138 142
 	[self assertReady];
139 143
 	
140
-	UIViewController<RNNRootViewProtocol> *newVc = [_controllerFactory createLayoutAndSaveToStore:layout];
144
+	UIViewController<RNNRootViewProtocol> *newVC = [_controllerFactory createLayoutAndSaveToStore:layout];
145
+	UIViewController *fromVC = [_store findComponentForId:componentId];
141 146
 	__weak typeof(RNNEventEmitter*) weakEventEmitter = _eventEmitter;
142
-	[_navigationStackManager setStackRoot:newVc fromComponent:componentId completion:^{
147
+	[_stackManager setStackRoot:newVC fromViewController:fromVC animated:newVC.options.animations.push.enable completion:^{
143 148
 		[weakEventEmitter sendOnNavigationCommandCompletion:setStackRoot params:@{@"componentId": componentId}];
144 149
 		completion();
145 150
 	} rejection:rejection];
@@ -148,38 +153,46 @@ static NSString* const setDefaultOptions	= @"setDefaultOptions";
148 153
 -(void)pop:(NSString*)componentId options:(NSDictionary*)options completion:(RNNTransitionCompletionBlock)completion rejection:(RCTPromiseRejectBlock)rejection {
149 154
 	[self assertReady];
150 155
 
151
-	[CATransaction begin];
152
-	[CATransaction setCompletionBlock:^{
156
+	RNNRootViewController *vc = (RNNRootViewController*)[_store findComponentForId:componentId];
157
+	UINavigationController *nvc = vc.navigationController;
158
+
159
+	if ([nvc topViewController] == vc) {
160
+		if (vc.options.animations.pop) {
161
+			nvc.delegate = vc;
162
+		} else {
163
+			nvc.delegate = nil;
164
+		}
165
+	} else {
166
+		NSMutableArray * vcs = nvc.viewControllers.mutableCopy;
167
+		[vcs removeObject:vc];
168
+		[nvc setViewControllers:vcs animated:vc.options.animations.pop.enable];
169
+	}
170
+	
171
+	[_stackManager pop:vc animated:vc.options.animations.pop.enable completion:^{
172
+		[_store removeComponent:componentId];
153 173
 		[_eventEmitter sendOnNavigationCommandCompletion:pop params:@{@"componentId": componentId}];
154 174
 		completion();
175
+	} rejection:^(NSString *code, NSString *message, NSError *error) {
176
+		
155 177
 	}];
156
-	
157
-	NSDictionary* animationData = options[@"customTransition"];
158
-	RNNAnimationOptions* transitionOptions = [[RNNAnimationOptions alloc] initWithDict:animationData];
159
-	
160
-	if (transitionOptions.animations){
161
-		[_navigationStackManager pop:componentId withTransitionOptions:transitionOptions rejection:rejection];
162
-	} else {
163
-		[_navigationStackManager pop:componentId withTransitionOptions:nil rejection:rejection];
164
-	}
165
-	[CATransaction commit];
166 178
 }
167 179
 
168 180
 -(void) popTo:(NSString*)componentId completion:(RNNTransitionCompletionBlock)completion rejection:(RCTPromiseRejectBlock)rejection {
169 181
 	[self assertReady];
170
-	[CATransaction begin];
171
-	[CATransaction setCompletionBlock:^{
182
+	RNNRootViewController *vc = (RNNRootViewController*)[_store findComponentForId:componentId];
183
+
184
+	[_stackManager popTo:vc animated:vc.options.animations.pop.enable completion:^(NSArray *poppedViewControllers) {
172 185
 		[_eventEmitter sendOnNavigationCommandCompletion:popTo params:@{@"componentId": componentId}];
186
+		[self removePopedViewControllers:poppedViewControllers];
173 187
 		completion();
188
+	} rejection:^(NSString *code, NSString *message, NSError *error) {
189
+		
174 190
 	}];
175
-	
176
-	[_navigationStackManager popTo:componentId rejection:rejection];
177
-	
178
-	[CATransaction commit];
179 191
 }
180 192
 
181 193
 -(void) popToRoot:(NSString*)componentId completion:(RNNTransitionCompletionBlock)completion rejection:(RCTPromiseRejectBlock)rejection {
182 194
 	[self assertReady];
195
+	RNNRootViewController *newVc = (RNNRootViewController*)[_store findComponentForId:componentId];
183 196
 
184 197
 	[CATransaction begin];
185 198
 	[CATransaction setCompletionBlock:^{
@@ -187,7 +200,11 @@ static NSString* const setDefaultOptions	= @"setDefaultOptions";
187 200
 		completion();
188 201
 	}];
189 202
 	
190
-	[_navigationStackManager popToRoot:componentId rejection:rejection];
203
+	[_stackManager popToRoot:newVc animated:newVc.options.animations.pop.enable completion:^(NSArray *poppedViewControllers) {
204
+		[self removePopedViewControllers:poppedViewControllers];
205
+	} rejection:^(NSString *code, NSString *message, NSError *error) {
206
+		
207
+	}];
191 208
 	
192 209
 	[CATransaction commit];
193 210
 }
@@ -251,6 +268,12 @@ static NSString* const setDefaultOptions	= @"setDefaultOptions";
251 268
 
252 269
 #pragma mark - private
253 270
 
271
+-(void)removePopedViewControllers:(NSArray*)viewControllers {
272
+	for (UIViewController *popedVC in viewControllers) {
273
+		[_store removeComponentByViewControllerInstance:popedVC];
274
+	}
275
+}
276
+
254 277
 -(void) assertReady {
255 278
 	if (!_store.isReadyToReceiveCommands) {
256 279
 		[[NSException exceptionWithName:@"BridgeNotLoadedError"

+ 2
- 2
lib/ios/RNNNavigationController.m View File

@@ -12,8 +12,8 @@
12 12
 	return ((UIViewController<RNNRootViewProtocol>*)self.topViewController);
13 13
 }
14 14
 
15
-- (BOOL)isCustomTransitioned {
16
-	return NO;
15
+- (UINavigationController *)navigationController {
16
+	return self;
17 17
 }
18 18
 
19 19
 - (BOOL)isCustomViewController {

+ 11
- 8
lib/ios/RNNNavigationStackManager.h View File

@@ -1,17 +1,20 @@
1 1
 #import <Foundation/Foundation.h>
2 2
 #import <UIKit/UIKit.h>
3
-#import "RNNStore.h"
3
+
4
+typedef void (^RNNTransitionCompletionBlock)(void);
5
+typedef void (^RNNPopCompletionBlock)(NSArray* poppedViewControllers);
6
+typedef void (^RNNTransitionRejectionBlock)(NSString *code, NSString *message, NSError *error);
4 7
 
5 8
 @interface RNNNavigationStackManager : NSObject
6 9
 
7
-@property (nonatomic) int loadCount;
8
--(instancetype)initWithStore:(RNNStore*)store;
10
+- (void)push:(UIViewController *)newTop onTop:(UIViewController *)onTopViewController animated:(BOOL)animated animationDelegate:(id)animationDelegate completion:(RNNTransitionCompletionBlock)completion rejection:(RNNTransitionRejectionBlock)rejection;
11
+
12
+- (void)pop:(UIViewController *)viewController animated:(BOOL)animated completion:(RNNTransitionCompletionBlock)completion rejection:(RNNTransitionRejectionBlock)rejection;
13
+
14
+- (void)popTo:(UIViewController *)viewController animated:(BOOL)animated completion:(RNNPopCompletionBlock)completion rejection:(RNNTransitionRejectionBlock)rejection;
9 15
 
16
+- (void)popToRoot:(UIViewController*)viewController animated:(BOOL)animated completion:(RNNPopCompletionBlock)completion rejection:(RNNTransitionRejectionBlock)rejection;
10 17
 
11
--(void)push:(UIViewController<RNNRootViewProtocol>*)newTop onTop:(NSString*)componentId completion:(RNNTransitionCompletionBlock)completion rejection:(RCTPromiseRejectBlock)rejection;
12
--(void)pop:(NSString*)componentId withTransitionOptions:(RNNAnimationOptions*)transitionOptions rejection:(RCTPromiseRejectBlock)rejection;
13
--(void)popTo:(NSString*)componentId rejection:(RCTPromiseRejectBlock)rejection;
14
--(void)popToRoot:(NSString*)componentId rejection:(RCTPromiseRejectBlock)rejection;
15
--(void)setStackRoot:(UIViewController<RNNRootViewProtocol> *)newRoot fromComponent:(NSString *)componentId completion:(RNNTransitionCompletionBlock)completion rejection:(RCTPromiseRejectBlock)rejection;
18
+- (void)setStackRoot:(UIViewController *)newRoot fromViewController:(UIViewController *)fromViewController animated:(BOOL)animated completion:(RNNTransitionCompletionBlock)completion rejection:(RNNTransitionRejectionBlock)rejection;
16 19
 
17 20
 @end

+ 36
- 115
lib/ios/RNNNavigationStackManager.m View File

@@ -1,122 +1,64 @@
1 1
 #import "RNNNavigationStackManager.h"
2
-#import "RNNRootViewController.h"
3
-#import "RNNAnimator.h"
4 2
 #import "RNNErrorHandler.h"
5
-#import "RNNNavigationController.h"
6 3
 
7
-dispatch_queue_t RCTGetUIManagerQueue(void);
8
-@interface RNNNavigationStackManager()
9
-@property (nonatomic, strong) RNNNavigationController* nvc;
10
-@property (nonatomic, strong) RNNRootViewController* toVC;
11
-@end
4
+typedef void (^RNNAnimationBlock)(void);
12 5
 
13
-@implementation RNNNavigationStackManager {
14
-	RNNStore *_store;
15
-	RNNTransitionCompletionBlock _completionBlock;
16
-}
6
+@implementation RNNNavigationStackManager
17 7
 
18
--(instancetype)initWithStore:(RNNStore*)store {
19
-	self = [super init];
20
-	_store = store;
21
-	return self;
22
-}
8
+- (void)push:(UIViewController *)newTop onTop:(UIViewController *)onTopViewController animated:(BOOL)animated animationDelegate:(id)animationDelegate completion:(RNNTransitionCompletionBlock)completion rejection:(RCTPromiseRejectBlock)rejection {
9
+	UINavigationController *nvc = onTopViewController.navigationController;
23 10
 
24
--(void)push:(UIViewController<RNNRootViewProtocol> *)newTop onTop:(NSString *)componentId completion:(RNNTransitionCompletionBlock)completion rejection:(RCTPromiseRejectBlock)rejection {
25
-	RNNNavigationController *nvc = [self getComponentStack:componentId];
26
-	[self assertNavigationControllerExist:nvc reject:rejection];
27
-	[self preparePush:newTop onTopVC:nvc completion:completion];
28
-	if ([newTop isCustomViewController]) {
29
-		[self pushAfterLoad:nil];
30
-	} else {
31
-		[self waitForContentToAppearAndThen:@selector(pushAfterLoad:)];
32
-	}
33
-}
34
-
35
--(void)preparePush:(UIViewController<RNNRootViewProtocol> *)newTop onTopVC:(RNNNavigationController*)nvc completion:(RNNTransitionCompletionBlock)completion {
36
-	self.toVC = (RNNRootViewController*)newTop;
37
-	self.nvc = nvc;
38
-	
39
-	
40
-	if (self.toVC.options.animations.push.hasCustomAnimation || self.toVC.isCustomTransitioned) {
41
-		nvc.delegate = newTop;
11
+	if (animationDelegate) {
12
+		nvc.delegate = animationDelegate;
42 13
 	} else {
43 14
 		nvc.delegate = nil;
44 15
 		nvc.interactivePopGestureRecognizer.delegate = nil;
45 16
 	}
46 17
 	
47
-	_completionBlock = completion;
18
+	[self performAnimationBlock:^{
19
+		[nvc pushViewController:newTop animated:animated];
20
+	} completion:completion];
48 21
 }
49 22
 
50
--(void)waitForContentToAppearAndThen:(SEL)nameOfSelector {
51
-	[[NSNotificationCenter defaultCenter] addObserver:self
52
-											 selector:nameOfSelector
53
-												 name: @"RCTContentDidAppearNotification"
54
-											   object:nil];
23
+- (void)pop:(UIViewController *)viewController animated:(BOOL)animated completion:(RNNTransitionCompletionBlock)completion rejection:(RNNTransitionRejectionBlock)rejection {
24
+	[self performAnimationBlock:^{
25
+		[viewController.navigationController popViewControllerAnimated:animated];
26
+	} completion:completion];
55 27
 }
56 28
 
57
--(void)pushAfterLoad:(NSDictionary*)notif {
58
-	[[NSNotificationCenter defaultCenter] removeObserver:self name:@"RCTContentDidAppearNotification" object:nil];
59
-	[CATransaction begin];
60
-	[CATransaction setCompletionBlock:^{
61
-		if (_completionBlock) {
62
-			_completionBlock();
63
-			_completionBlock = nil;
29
+- (void)popTo:(UIViewController *)viewController animated:(BOOL)animated completion:(RNNPopCompletionBlock)completion rejection:(RNNTransitionRejectionBlock)rejection; {
30
+	__block NSArray* poppedVCs;
31
+	
32
+	[self performAnimationBlock:^{
33
+		poppedVCs = [viewController.navigationController popToViewController:viewController animated:animated];
34
+	} completion:^{
35
+		if (completion) {
36
+			completion(poppedVCs);
64 37
 		}
65 38
 	}];
66
-	
67
-	[self.nvc pushViewController:self.toVC animated:self.toVC.options.animations.push.enable];
68
-	[CATransaction commit];
69
-	
70
-	self.toVC = nil;
71
-	self.nvc.interactivePopGestureRecognizer.delegate = nil;
72
-	self.nvc = nil;
73 39
 }
74 40
 
75
--(void)pop:(NSString *)componentId withTransitionOptions:(RNNAnimationOptions *)transitionOptions rejection:(RCTPromiseRejectBlock)rejection {
76
-	RNNRootViewController* vc = (RNNRootViewController*)[_store findComponentForId:componentId];
77
-	UINavigationController* nvc = [self getComponentStack:componentId];
78
-	[self assertNavigationControllerExist:nvc reject:rejection];
41
+- (void)popToRoot:(UIViewController*)viewController animated:(BOOL)animated completion:(RNNPopCompletionBlock)completion rejection:(RNNTransitionRejectionBlock)rejection {
42
+	__block NSArray* poppedVCs;
79 43
 	
80
-	if ([nvc topViewController] == vc) {
81
-		if (vc.options.animations.pop) {
82
-			nvc.delegate = vc;
83
-			[nvc popViewControllerAnimated:vc.options.animations.pop.enable];
84
-		} else {
85
-			nvc.delegate = nil;
86
-			[nvc popViewControllerAnimated:vc.options.animations.pop.enable];
87
-		}
88
-	} else {
89
-		NSMutableArray * vcs = nvc.viewControllers.mutableCopy;
90
-		[vcs removeObject:vc];
91
-		[nvc setViewControllers:vcs animated:vc.options.animations.pop.enable];
92
-	}
93
-	[_store removeComponent:componentId];
44
+	[self performAnimationBlock:^{
45
+		poppedVCs = [viewController.navigationController popToRootViewControllerAnimated:animated];
46
+	} completion:^{
47
+		completion(poppedVCs);
48
+	}];
94 49
 }
95 50
 
96
--(void)popTo:(NSString*)componentId rejection:(RCTPromiseRejectBlock)rejection {
97
-	RNNRootViewController *vc = (RNNRootViewController*)[_store findComponentForId:componentId];
98
-	RNNNavigationController *nvc = [self getComponentStack:componentId];
99
-	[self assertNavigationControllerExist:nvc reject:rejection];
51
+- (void)setStackRoot:(UIViewController *)newRoot fromViewController:(UIViewController *)fromViewController animated:(BOOL)animated completion:(RNNTransitionCompletionBlock)completion rejection:(RNNTransitionRejectionBlock)rejection {
52
+	UINavigationController* nvc = fromViewController.navigationController;
100 53
 	
101
-	if (vc && nvc) {
102
-		NSArray *poppedVCs = [nvc popToViewController:vc animated:vc.options.animations.pop.enable];
103
-		[self removePopedViewControllers:poppedVCs];
104
-	}
54
+	[self performAnimationBlock:^{
55
+		[nvc setViewControllers:@[newRoot] animated:animated];
56
+	} completion:completion];
105 57
 }
106 58
 
107
--(void)popToRoot:(NSString*)componentId rejection:(RCTPromiseRejectBlock)rejection {
108
-	RNNRootViewController *vc = (RNNRootViewController*)[_store findComponentForId:componentId];
109
-	RNNNavigationController *nvc = [self getComponentStack:componentId];
110
-	[self assertNavigationControllerExist:nvc reject:rejection];
111
-	NSArray* poppedVCs = [nvc popToRootViewControllerAnimated:vc.options.animations.pop.enable];
112
-	[self removePopedViewControllers:poppedVCs];
113
-}
114
-
115
--(void)setStackRoot:(UIViewController<RNNRootViewProtocol> *)newRoot fromComponent:(NSString *)componentId completion:(RNNTransitionCompletionBlock)completion rejection:(RCTPromiseRejectBlock)rejection {
116
-	RNNNavigationController* nvc = [self getComponentStack:componentId];
59
+# pragma mark Private
117 60
 
118
-	[self assertNavigationControllerExist:nvc reject:rejection];
119
-	
61
+- (void)performAnimationBlock:(RNNAnimationBlock)animationBlock completion:(RNNTransitionCompletionBlock)completion {
120 62
 	[CATransaction begin];
121 63
 	[CATransaction setCompletionBlock:^{
122 64
 		if (completion) {
@@ -124,31 +66,10 @@ dispatch_queue_t RCTGetUIManagerQueue(void);
124 66
 		}
125 67
 	}];
126 68
 	
127
-	[nvc setViewControllers:@[newRoot] animated:newRoot.options.animations.push.enable];
69
+	animationBlock();
128 70
 	
129 71
 	[CATransaction commit];
130 72
 }
131 73
 
132
-- (void)assertNavigationControllerExist:(UINavigationController *)viewController reject:(RCTPromiseRejectBlock)reject {
133
-	if (![viewController isKindOfClass:[UINavigationController class]]) {
134
-		_completionBlock = nil;
135
-		[RNNErrorHandler reject:reject withErrorCode:RNNCommandErrorCodeNoStack errorDescription:[NSString stringWithFormat:@"%@ should be called from a stack child component", [RNNErrorHandler getCallerFunctionName]]];
136
-	}
137
-}
138
-
139
-- (RNNNavigationController*)getComponentStack:(NSString *)componentId {
140
-	UIViewController* component = [_store findComponentForId:componentId];
141
-	if ([component isKindOfClass:[UINavigationController class]]) {
142
-		return (RNNNavigationController*)component;
143
-	} else {
144
-		return (RNNNavigationController*)component.navigationController;
145
-	}
146
-}
147
-
148
--(void)removePopedViewControllers:(NSArray*)viewControllers {
149
-	for (UIViewController *popedVC in viewControllers) {
150
-		[_store removeComponentByViewControllerInstance:popedVC];
151
-	}
152
-}
153 74
 
154 75
 @end

+ 5
- 2
lib/ios/RNNRootViewController.h View File

@@ -9,6 +9,10 @@
9 9
 #import "RNNTopTabsViewController.h"
10 10
 #import "RNNRootViewProtocol.h"
11 11
 
12
+@class RNNRootViewController;
13
+
14
+typedef void (^RNNReactViewReadyCompletionBlock)(void);
15
+
12 16
 @interface RNNRootViewController : UIViewController	<RNNRootViewProtocol, UIViewControllerPreviewingDelegate, UISearchResultsUpdating>
13 17
 
14 18
 @property (nonatomic, strong) RNNNavigationOptions* options;
@@ -27,10 +31,9 @@
27 31
 			   eventEmitter:(RNNEventEmitter*)eventEmitter
28 32
 		  isExternalComponent:(BOOL)isExternalComponent;
29 33
 
34
+- (void)onReactViewReady:(RNNReactViewReadyCompletionBlock)readyBlock;
30 35
 
31 36
 -(void)applyTabBarItem;
32 37
 -(void)applyTopTabsOptions;
33 38
 
34
--(BOOL)isCustomTransitioned;
35
-
36 39
 @end

+ 24
- 0
lib/ios/RNNRootViewController.m View File

@@ -10,11 +10,14 @@
10 10
 	UIView* _customTopBar;
11 11
 	UIView* _customTopBarBackground;
12 12
 }
13
+
13 14
 @property (nonatomic, strong) NSString* componentName;
14 15
 @property (nonatomic) BOOL _statusBarHidden;
15 16
 @property (nonatomic) BOOL isExternalComponent;
16 17
 @property (nonatomic) BOOL _optionsApplied;
17 18
 @property (nonatomic, copy) void (^rotationBlock)(void);
19
+@property (nonatomic, copy) RNNReactViewReadyCompletionBlock reactViewReadyBlock;
20
+
18 21
 @end
19 22
 
20 23
 @implementation RNNRootViewController
@@ -36,6 +39,10 @@
36 39
 	
37 40
 	if (!self.isExternalComponent) {
38 41
 		self.view = [creator createRootView:self.componentName rootViewId:self.componentId];
42
+		[[NSNotificationCenter defaultCenter] addObserver:self
43
+												 selector:@selector(reactViewReady)
44
+													 name: @"RCTContentDidAppearNotification"
45
+												   object:nil];
39 46
 	}
40 47
 	
41 48
 	[[NSNotificationCenter defaultCenter] addObserver:self
@@ -75,6 +82,23 @@
75 82
 	[self.eventEmitter sendComponentDidDisappear:self.componentId componentName:self.componentName];
76 83
 }
77 84
 
85
+- (void)reactViewReady {
86
+	if (_reactViewReadyBlock) {
87
+		_reactViewReadyBlock();
88
+		_reactViewReadyBlock = nil;
89
+	}
90
+	
91
+	[[NSNotificationCenter defaultCenter] removeObserver:self name:@"RCTContentDidAppearNotification" object:nil];
92
+}
93
+
94
+- (void)onReactViewReady:(RNNReactViewReadyCompletionBlock)readyBlock {
95
+	if (self.isCustomViewController) {
96
+		readyBlock();
97
+	} else {
98
+		self.reactViewReadyBlock = readyBlock;
99
+	}
100
+}
101
+
78 102
 -(void)updateSearchResultsForSearchController:(UISearchController *)searchController {
79 103
 	[self.eventEmitter sendOnSearchBarUpdated:self.componentId
80 104
 										 text:searchController.searchBar.text

+ 2
- 2
lib/ios/RNNRootViewProtocol.h View File

@@ -8,10 +8,10 @@
8 8
 - (void)performOnRotation:(void (^)(void))block;
9 9
 - (void)optionsUpdated;
10 10
 - (void)applyModalOptions;
11
-
12
-@required
13 11
 - (BOOL)isCustomTransitioned;
14 12
 - (RNNNavigationOptions*)options;
13
+
14
+@required
15 15
 - (NSString *)componentId;
16 16
 
17 17
 @end

+ 0
- 8
lib/ios/RNNSideMenuChildVC.m View File

@@ -35,14 +35,6 @@
35 35
 	return self.child.preferredStatusBarStyle;
36 36
 }
37 37
 
38
-- (BOOL)isCustomTransitioned {
39
-	return NO;
40
-}
41
-
42
-- (RNNOptions *)options {
43
-	return nil;
44
-}
45
-
46 38
 - (NSString *)componentId {
47 39
 	return _child.componentId;
48 40
 }

+ 0
- 8
lib/ios/RNNSideMenuController.m View File

@@ -89,14 +89,6 @@
89 89
 	}
90 90
 }
91 91
 
92
-- (BOOL)isCustomTransitioned {
93
-	return NO;
94
-}
95
-
96
-- (RNNOptions *)options {
97
-	return nil;
98
-}
99
-
100 92
 - (NSString *)componentId {
101 93
 	return _center.componentId;
102 94
 }

+ 0
- 2
lib/ios/RNNSplitViewController.h View File

@@ -22,6 +22,4 @@
22 22
 			rootViewCreator:(id<RNNRootViewCreator>)creator
23 23
 				  eventEmitter:(RNNEventEmitter*)eventEmitter;
24 24
 
25
--(BOOL)isCustomTransitioned;
26
-
27 25
 @end

+ 0
- 4
lib/ios/RNNSplitViewController.m View File

@@ -37,8 +37,4 @@
37 37
 	[self.options mergeIfEmptyWith:options];
38 38
 }
39 39
 
40
--(BOOL)isCustomTransitioned {
41
-	return false;
42
-}
43
-
44 40
 @end

+ 0
- 1
lib/ios/RNNStore.h View File

@@ -5,7 +5,6 @@
5 5
 #import "ReactNativeNavigation.h"
6 6
 
7 7
 typedef void (^RNNTransitionCompletionBlock)(void);
8
-//typedef void (^RNNTransitionRejectionBlock)(NSString *code, NSString *message, NSError *error);
9 8
 
10 9
 @interface RNNStore : NSObject
11 10
 

+ 0
- 8
lib/ios/RNNTabBarController.m View File

@@ -41,14 +41,6 @@
41 41
 	}
42 42
 }
43 43
 
44
-- (BOOL)isCustomTransitioned {
45
-	return NO;
46
-}
47
-
48
-- (RNNOptions *)options {
49
-	return nil;
50
-}
51
-
52 44
 - (void)mergeOptions:(NSDictionary *)options {
53 45
 	[((UIViewController<RNNRootViewProtocol>*)self.selectedViewController) mergeOptions:options];
54 46
 }

+ 0
- 8
lib/ios/RNNTopTabsViewController.m View File

@@ -72,14 +72,6 @@
72 72
     [super viewDidLoad];
73 73
 }
74 74
 
75
-- (BOOL)isCustomTransitioned {
76
-	return NO;
77
-}
78
-
79
-- (RNNOptions *)options {
80
-	return nil;
81
-}
82
-
83 75
 - (NSString *)componentId {
84 76
 	return _currentViewController.componentId;
85 77
 }

+ 75
- 2
lib/ios/ReactNativeNavigationTests/RNNCommandsHandlerTest.m View File

@@ -4,12 +4,32 @@
4 4
 #import "RNNNavigationOptions.h"
5 5
 #import "RNNTestRootViewCreator.h"
6 6
 #import "RNNRootViewController.h"
7
-#import "RNNNavigationController.h"
7
+#import "RNNNavigationStackManager.h"
8
+
9
+@interface MockUINavigationController : UINavigationController
10
+@property (nonatomic, strong) NSArray* willReturnVCs;
11
+@end
12
+
13
+@implementation MockUINavigationController
14
+
15
+-(NSArray<UIViewController *> *)popToViewController:(UIViewController *)viewController animated:(BOOL)animated {
16
+	return self.willReturnVCs;
17
+}
18
+
19
+-(NSArray<UIViewController *> *)popToRootViewControllerAnimated:(BOOL)animated {
20
+	return self.willReturnVCs;
21
+}
22
+
23
+@end
8 24
 
9 25
 @interface RNNCommandsHandlerTest : XCTestCase
10 26
 
11 27
 @property (nonatomic, strong) RNNStore* store;
12 28
 @property (nonatomic, strong) RNNCommandsHandler* uut;
29
+@property (nonatomic, strong) RNNRootViewController* vc1;
30
+@property (nonatomic, strong) RNNRootViewController* vc2;
31
+@property (nonatomic, strong) RNNRootViewController* vc3;
32
+@property (nonatomic, strong) MockUINavigationController* nvc;
13 33
 
14 34
 @end
15 35
 
@@ -20,6 +40,14 @@
20 40
 //	[self.store setReadyToReceiveCommands:true];
21 41
 	self.store = [[RNNStore alloc] init];
22 42
 	self.uut = [[RNNCommandsHandler alloc] initWithStore:self.store controllerFactory:nil eventEmitter:nil];
43
+	self.vc1 = [RNNRootViewController new];
44
+	self.vc2 = [RNNRootViewController new];
45
+	self.vc3 = [RNNRootViewController new];
46
+	_nvc = [[MockUINavigationController alloc] init];
47
+	[_nvc setViewControllers:@[self.vc1, self.vc2, self.vc3]];
48
+	[self.store setComponent:self.vc1 componentId:@"vc1"];
49
+	[self.store setComponent:self.vc2 componentId:@"vc2"];
50
+	[self.store setComponent:self.vc3 componentId:@"vc3"];
23 51
 }
24 52
 
25 53
 
@@ -40,6 +68,7 @@
40 68
 
41 69
 	[skipMethods addObject:@"initWithStore:controllerFactory:eventEmitter:"];
42 70
 	[skipMethods addObject:@"assertReady"];
71
+	[skipMethods addObject:@"removePopedViewControllers:"];
43 72
 	[skipMethods addObject:@".cxx_destruct"];
44 73
 
45 74
 	NSMutableArray* result = [NSMutableArray new];
@@ -70,7 +99,7 @@
70 99
 															rootViewCreator:[[RNNTestRootViewCreator alloc] init]
71 100
 															   eventEmitter:nil
72 101
 														  isExternalComponent:NO];
73
-	RNNNavigationController* nav = [[RNNNavigationController alloc] initWithRootViewController:vc];
102
+	UINavigationController* nav = [[UINavigationController alloc] initWithRootViewController:vc];
74 103
 	[vc viewWillAppear:false];
75 104
 	XCTAssertTrue([vc.navigationItem.title isEqual:@"the title"]);
76 105
 
@@ -86,4 +115,48 @@
86 115
 	}];
87 116
 }
88 117
 
118
+- (void)testPop_removeTopVCFromStore {
119
+	[self.store setReadyToReceiveCommands:true];
120
+	XCTestExpectation *expectation = [self expectationWithDescription:@"Testing Async Method"];
121
+
122
+	[self.uut pop:@"vc3" options:nil completion:^{
123
+		XCTAssertNil([self.store findComponentForId:@"vc3"]);
124
+		XCTAssertNotNil([self.store findComponentForId:@"vc2"]);
125
+		XCTAssertNotNil([self.store findComponentForId:@"vc1"]);
126
+		[expectation fulfill];
127
+	} rejection:^(NSString *code, NSString *message, NSError *error) {
128
+		
129
+	}];
130
+	
131
+	[self waitForExpectationsWithTimeout:1 handler:nil];
132
+}
133
+
134
+- (void)testPopToSpecificVC_removeAllPopedVCFromStore {
135
+	[self.store setReadyToReceiveCommands:true];
136
+	XCTestExpectation *expectation = [self expectationWithDescription:@"Testing Async Method"];
137
+	_nvc.willReturnVCs = @[self.vc2, self.vc3];
138
+	[self.uut popTo:@"vc1" completion:^{
139
+		XCTAssertNil([self.store findComponentForId:@"vc2"]);
140
+		XCTAssertNil([self.store findComponentForId:@"vc3"]);
141
+		XCTAssertNotNil([self.store findComponentForId:@"vc1"]);
142
+		[expectation fulfill];
143
+	} rejection:nil];
144
+	
145
+	[self waitForExpectationsWithTimeout:1 handler:nil];
146
+}
147
+
148
+- (void)testPopToRoot_removeAllTopVCsFromStore {
149
+	[self.store setReadyToReceiveCommands:true];
150
+	_nvc.willReturnVCs = @[self.vc2, self.vc3];
151
+	XCTestExpectation *expectation = [self expectationWithDescription:@"Testing Async Method"];
152
+	[self.uut popToRoot:@"vc3" completion:^{
153
+		XCTAssertNil([self.store findComponentForId:@"vc2"]);
154
+		XCTAssertNil([self.store findComponentForId:@"vc3"]);
155
+		XCTAssertNotNil([self.store findComponentForId:@"vc1"]);
156
+		[expectation fulfill];
157
+	} rejection:nil];
158
+	
159
+	[self waitForExpectationsWithTimeout:1 handler:nil];
160
+}
161
+
89 162
 @end

+ 38
- 51
lib/ios/ReactNativeNavigationTests/RNNNavigationStackManagerTest.m View File

@@ -2,31 +2,15 @@
2 2
 #import "RNNStore.h"
3 3
 #import "RNNNavigationStackManager.h"
4 4
 
5
-@interface MockUINavigationController : UINavigationController
6
-@property (nonatomic, strong) NSArray* willReturnVCs;
7
-@end
8
-
9
-@implementation MockUINavigationController
10
-
11
--(NSArray<UIViewController *> *)popToViewController:(UIViewController *)viewController animated:(BOOL)animated {
12
-	return self.willReturnVCs;
13
-}
14
-
15
--(NSArray<UIViewController *> *)popToRootViewControllerAnimated:(BOOL)animated {
16
-	return self.willReturnVCs;
17
-}
18
-
19
-@end
20
-
21 5
 
22 6
 @interface RNNNavigationStackManagerTest : XCTestCase
23 7
 
24 8
 @property (nonatomic, strong) RNNStore *store;
25
-@property (nonatomic, strong) RNNNavigationStackManager *uut;
26
-@property (nonatomic, strong) MockUINavigationController *nvc;
9
+@property (nonatomic, strong) UINavigationController *nvc;
27 10
 @property (nonatomic, strong) UIViewController *vc1;
28 11
 @property (nonatomic, strong) UIViewController *vc2;
29 12
 @property (nonatomic, strong) UIViewController *vc3;
13
+@property (nonatomic, strong) RNNNavigationStackManager *stackManager;
30 14
 
31 15
 @end
32 16
 
@@ -35,57 +19,60 @@
35 19
 - (void)setUp {
36 20
     [super setUp];
37 21
 	self.store = [RNNStore new];
38
-	self.uut = [[RNNNavigationStackManager alloc] initWithStore:self.store];
39 22
 	
40
-	self.nvc = [[MockUINavigationController alloc] init];
23
+	self.nvc = [[UINavigationController alloc] init];
41 24
 	self.vc1 = [RNNRootViewController new];
42 25
 	self.vc2 = [RNNRootViewController new];
43 26
 	self.vc3 = [RNNRootViewController new];
27
+	self.stackManager = [RNNNavigationStackManager new];
28
+	
44 29
 	NSArray *vcArray = @[self.vc1, self.vc2, self.vc3];
45 30
 	[self.nvc setViewControllers:vcArray];
46
-	
47
-	[self.store setComponent:self.vc1 componentId:@"vc1"];
48
-	[self.store setComponent:self.vc2 componentId:@"vc2"];
49
-	[self.store setComponent:self.vc3 componentId:@"vc3"];
50
-	
51
-	
52 31
 }
53 32
 
54 33
 
55
-- (void)testPop_removeTopVCFromStore {
56
-	[self.uut pop:@"vc3" withTransitionOptions:nil rejection:nil];
57
-	XCTAssertNil([self.store findComponentForId:@"vc3"]);
58
-	XCTAssertNotNil([self.store findComponentForId:@"vc2"]);
59
-	XCTAssertNotNil([self.store findComponentForId:@"vc1"]);
34
+- (void)testPop_removeTopVCFromStack {
35
+	XCTestExpectation *expectation = [self expectationWithDescription:@"Testing Async Method"];
36
+	XCTAssertTrue([self.nvc.topViewController isEqual:self.vc3]);
37
+	[_stackManager popTo:self.vc2 animated:YES completion:^(NSArray *poppedViewControllers) {
38
+		XCTAssertTrue([self.nvc.topViewController isEqual:self.vc2]);
39
+		[expectation fulfill];
40
+	} rejection:nil];
41
+	
42
+	[self waitForExpectationsWithTimeout:1 handler:nil];
60 43
 }
61 44
 
62
-- (void)testPopToSpecificVC_removeAllPopedVCFromStore {
63
-	self.nvc.willReturnVCs = @[self.vc2, self.vc3];
64
-	[self.uut popTo:@"vc1" rejection:nil];
65
-	
66
-	XCTAssertNil([self.store findComponentForId:@"vc2"]);
67
-	XCTAssertNil([self.store findComponentForId:@"vc3"]);
68
-	XCTAssertNotNil([self.store findComponentForId:@"vc1"]);
45
+- (void)testPopToSpecificVC_removeAllPopedVCFromStack {
46
+	XCTestExpectation *expectation = [self expectationWithDescription:@"Testing Async Method"];
47
+	XCTAssertFalse([self.nvc.topViewController isEqual:self.vc1]);
48
+	[_stackManager popTo:self.vc1 animated:NO completion:^(NSArray *poppedViewControllers) {
49
+		XCTAssertTrue([self.nvc.topViewController isEqual:self.vc1]);
50
+		[expectation fulfill];
51
+	} rejection:nil];
69 52
 	
53
+	[self waitForExpectationsWithTimeout:1 handler:nil];
70 54
 }
71 55
 
72
-- (void)testPopToRoot_removeAllTopVCsFromStore {
73
-	self.nvc.willReturnVCs = @[self.vc2, self.vc3];
74
-	[self.uut popToRoot:@"vc3" rejection:nil];
56
+- (void)testPopToRoot_removeAllTopVCsFromStack {
57
+	XCTestExpectation *expectation = [self expectationWithDescription:@"Testing Async Method"];
58
+	[_stackManager popToRoot:self.vc3 animated:NO completion:^(NSArray *poppedViewControllers) {
59
+		XCTAssertTrue(self.nvc.childViewControllers.count == 1);
60
+		XCTAssertTrue([self.nvc.topViewController isEqual:self.vc1]);
61
+		[expectation fulfill];
62
+	} rejection:nil];
75 63
 	
76
-	XCTAssertNil([self.store findComponentForId:@"vc2"]);
77
-	XCTAssertNil([self.store findComponentForId:@"vc3"]);
78
-	XCTAssertNotNil([self.store findComponentForId:@"vc1"]);
79
-
64
+	[self waitForExpectationsWithTimeout:1 handler:nil];
80 65
 }
81 66
 
82 67
 - (void)testStackRoot_shouldUpdateNavigationControllerChildrenViewControllers {
83
-	self.nvc.willReturnVCs = @[self.vc1, self.vc2, self.vc3];
84
-	
85
-	[self.uut setStackRoot:self.vc2 fromComponent:@"vc1" completion:nil rejection:nil];
86
-	
87
-	XCTAssertEqual(self.nvc.viewControllers.lastObject, self.vc2);
88
-	XCTAssertEqual(self.nvc.viewControllers.count, 1);
68
+	XCTestExpectation *expectation = [self expectationWithDescription:@"Testing Async Method"];
69
+	[_stackManager setStackRoot:self.vc2 fromViewController:self.vc1 animated:NO completion:^{
70
+		XCTAssertTrue(self.nvc.childViewControllers.count == 1);
71
+		XCTAssertTrue([self.nvc.topViewController isEqual:self.vc2]);
72
+		[expectation fulfill];
73
+	} rejection:nil];
74
+
75
+	[self waitForExpectationsWithTimeout:1 handler:nil];
89 76
 }
90 77
 
91 78
 @end