Browse Source

Bottom tabs attach mode support on iOS (#5733)

Add iOS support for `bottomTabs.attachMode`.
This property controls when BottomTabs children are attached to hierarchy and when their React views are created.

By default, RNN attaches all children at the same time which might impact loading time as a few screens are instantiated at once.
Using `afterInitialTab` or `onSwitchToTab` generally leads to improved start up time.
Yogev Ben David 4 years ago
parent
commit
60c4dfcd72
43 changed files with 621 additions and 198 deletions
  1. 5
    0
      lib/ios/BottomTabsAfterInitialTabAttacher.h
  2. 17
    0
      lib/ios/BottomTabsAfterInitialTabAttacher.m
  3. 15
    0
      lib/ios/BottomTabsAttachMode.h
  4. 21
    0
      lib/ios/BottomTabsAttachMode.m
  5. 13
    0
      lib/ios/BottomTabsAttachModeFactory.h
  6. 29
    0
      lib/ios/BottomTabsAttachModeFactory.m
  7. 8
    0
      lib/ios/BottomTabsBaseAttacher.h
  8. 9
    0
      lib/ios/BottomTabsBaseAttacher.m
  9. 5
    0
      lib/ios/BottomTabsOnSwitchToTabAttacher.h
  10. 13
    0
      lib/ios/BottomTabsOnSwitchToTabAttacher.m
  11. 5
    0
      lib/ios/BottomTabsTogetherAttacher.h
  12. 13
    0
      lib/ios/BottomTabsTogetherAttacher.m
  13. 10
    0
      lib/ios/RNNBottomTabsController.h
  14. 19
    0
      lib/ios/RNNBottomTabsController.m
  15. 2
    0
      lib/ios/RNNBottomTabsOptions.h
  16. 1
    1
      lib/ios/RNNBottomTabsOptions.m
  17. 1
    1
      lib/ios/RNNBridgeManager.m
  18. 67
    54
      lib/ios/RNNCommandsHandler.m
  19. 1
    11
      lib/ios/RNNComponentViewController.h
  20. 22
    43
      lib/ios/RNNComponentViewController.m
  21. 1
    1
      lib/ios/RNNComponentViewCreator.h
  22. 6
    4
      lib/ios/RNNControllerFactory.h
  23. 32
    19
      lib/ios/RNNControllerFactory.m
  24. 7
    0
      lib/ios/RNNExternalViewController.h
  25. 25
    0
      lib/ios/RNNExternalViewController.m
  26. 3
    1
      lib/ios/RNNLayoutProtocol.h
  27. 3
    4
      lib/ios/RNNReactRootViewCreator.m
  28. 1
    1
      lib/ios/RNNReactView.h
  29. 1
    2
      lib/ios/RNNReactView.m
  30. 2
    2
      lib/ios/RNNSideMenuChildVC.m
  31. 86
    6
      lib/ios/ReactNativeNavigation.xcodeproj/project.pbxproj
  32. 9
    1
      lib/ios/UIViewController+LayoutProtocol.h
  33. 44
    23
      lib/ios/UIViewController+LayoutProtocol.m
  34. 5
    1
      lib/ios/Utils/UITabBarController+RNNUtils.h
  35. 8
    1
      lib/ios/Utils/UITabBarController+RNNUtils.m
  36. 74
    13
      playground/ios/NavigationTests/RNNCommandsHandlerTest.m
  37. 4
    2
      playground/ios/NavigationTests/RNNControllerFactoryTest.m
  38. 10
    3
      playground/ios/NavigationTests/RNNSideMenuControllerTest.m
  39. 1
    1
      playground/ios/NavigationTests/RNNTestRootViewCreator.h
  40. 2
    2
      playground/ios/Podfile.lock
  41. 6
    0
      playground/ios/playground.xcodeproj/project.pbxproj
  42. 14
    0
      playground/ios/playground.xcodeproj/xcshareddata/xcschemes/playground.xcscheme
  43. 1
    1
      playground/src/commons/Options.js

+ 5
- 0
lib/ios/BottomTabsAfterInitialTabAttacher.h View File

@@ -0,0 +1,5 @@
1
+#import "BottomTabsBaseAttacher.h"
2
+
3
+@interface BottomTabsAfterInitialTabAttacher : BottomTabsBaseAttacher
4
+
5
+@end

+ 17
- 0
lib/ios/BottomTabsAfterInitialTabAttacher.m View File

@@ -0,0 +1,17 @@
1
+#import "BottomTabsAfterInitialTabAttacher.h"
2
+#import "UITabBarController+RNNUtils.h"
3
+
4
+@implementation BottomTabsAfterInitialTabAttacher
5
+
6
+- (void)attach:(UITabBarController *)bottomTabsController {
7
+    [bottomTabsController.selectedViewController setReactViewReadyCallback:^{
8
+        [bottomTabsController readyForPresentation];
9
+        for (UIViewController* viewController in bottomTabsController.deselectedViewControllers) {
10
+            [viewController render];
11
+        }
12
+    }];
13
+    
14
+    [bottomTabsController.selectedViewController render];
15
+}
16
+
17
+@end

+ 15
- 0
lib/ios/BottomTabsAttachMode.h View File

@@ -0,0 +1,15 @@
1
+#import "Text.h"
2
+
3
+typedef NS_ENUM(NSInteger, AttachMode) {
4
+    BottomTabsAttachModeTogether = 0,
5
+    BottomTabsAttachModeAfterInitialTab,
6
+    BottomTabsAttachModeOnSwitchToTab
7
+};
8
+
9
+@interface BottomTabsAttachMode : Text
10
+
11
+- (AttachMode)get;
12
+
13
+- (AttachMode)getWithDefaultValue:(id)defaultValue;
14
+
15
+@end

+ 21
- 0
lib/ios/BottomTabsAttachMode.m View File

@@ -0,0 +1,21 @@
1
+#import "BottomTabsAttachMode.h"
2
+#import <React/RCTConvert.h>
3
+
4
+@implementation BottomTabsAttachMode
5
+
6
+- (AttachMode)get {
7
+    return [self.class AttachMode:[super get]];
8
+}
9
+
10
+- (AttachMode)getWithDefaultValue:(id)defaultValue {
11
+    return [self.class AttachMode:[super getWithDefaultValue:defaultValue]];
12
+}
13
+
14
+RCT_ENUM_CONVERTER(AttachMode,
15
+(@{@"together": @(BottomTabsAttachModeTogether),
16
+   @"afterInitialTab": @(BottomTabsAttachModeAfterInitialTab),
17
+   @"onSwitchToTab": @(BottomTabsAttachModeOnSwitchToTab)
18
+}), BottomTabsAttachModeTogether, integerValue)
19
+
20
+
21
+@end

+ 13
- 0
lib/ios/BottomTabsAttachModeFactory.h View File

@@ -0,0 +1,13 @@
1
+#import <Foundation/Foundation.h>
2
+#import "RNNNavigationOptions.h"
3
+#import "BottomTabsBaseAttacher.h"
4
+
5
+@interface BottomTabsAttachModeFactory : NSObject
6
+
7
+- (instancetype)initWithDefaultOptions:(RNNNavigationOptions *)defaultOptions;
8
+
9
+- (BottomTabsBaseAttacher *)fromOptions:(RNNNavigationOptions *)options;
10
+
11
+@property (nonatomic, retain) RNNNavigationOptions* defaultOptions;
12
+
13
+@end

+ 29
- 0
lib/ios/BottomTabsAttachModeFactory.m View File

@@ -0,0 +1,29 @@
1
+#import "BottomTabsAttachModeFactory.h"
2
+#import "BottomTabsTogetherAttacher.h"
3
+#import "BottomTabsOnSwitchToTabAttacher.h"
4
+#import "BottomTabsAfterInitialTabAttacher.h"
5
+
6
+@implementation BottomTabsAttachModeFactory
7
+
8
+- (instancetype)initWithDefaultOptions:(RNNNavigationOptions *)defaultOptions {
9
+	self = [super init];
10
+	_defaultOptions = defaultOptions;
11
+	return self;
12
+}
13
+
14
+- (BottomTabsBaseAttacher *)fromOptions:(RNNNavigationOptions *)options {
15
+    AttachMode attachMode = [[options withDefault:_defaultOptions].bottomTabs.tabsAttachMode getWithDefaultValue:@"together"];
16
+	switch (attachMode) {
17
+        case BottomTabsAttachModeAfterInitialTab: {
18
+            return [BottomTabsAfterInitialTabAttacher new];
19
+        }
20
+        case BottomTabsAttachModeOnSwitchToTab: {
21
+            return [BottomTabsOnSwitchToTabAttacher new];
22
+        }
23
+        default:
24
+            return [BottomTabsTogetherAttacher new];
25
+            break;
26
+    }
27
+}
28
+
29
+@end

+ 8
- 0
lib/ios/BottomTabsBaseAttacher.h View File

@@ -0,0 +1,8 @@
1
+#import <Foundation/Foundation.h>
2
+#import "UIViewController+LayoutProtocol.h"
3
+
4
+@interface BottomTabsBaseAttacher : NSObject
5
+
6
+- (void)attach:(UITabBarController *)bottomTabsController;
7
+
8
+@end

+ 9
- 0
lib/ios/BottomTabsBaseAttacher.m View File

@@ -0,0 +1,9 @@
1
+#import "BottomTabsBaseAttacher.h"
2
+
3
+@implementation BottomTabsBaseAttacher
4
+
5
+- (void)attach:(UITabBarController *)bottomTabsController {
6
+    
7
+}
8
+
9
+@end

+ 5
- 0
lib/ios/BottomTabsOnSwitchToTabAttacher.h View File

@@ -0,0 +1,5 @@
1
+#import "BottomTabsBaseAttacher.h"
2
+
3
+@interface BottomTabsOnSwitchToTabAttacher : BottomTabsBaseAttacher
4
+
5
+@end

+ 13
- 0
lib/ios/BottomTabsOnSwitchToTabAttacher.m View File

@@ -0,0 +1,13 @@
1
+#import "BottomTabsOnSwitchToTabAttacher.h"
2
+
3
+@implementation BottomTabsOnSwitchToTabAttacher
4
+
5
+- (void)attach:(UITabBarController *)bottomTabsController {
6
+    [bottomTabsController.selectedViewController setReactViewReadyCallback:^{
7
+        [bottomTabsController readyForPresentation];
8
+    }];
9
+    
10
+    [bottomTabsController.selectedViewController render];
11
+}
12
+
13
+@end

+ 5
- 0
lib/ios/BottomTabsTogetherAttacher.h View File

@@ -0,0 +1,5 @@
1
+#import "BottomTabsBaseAttacher.h"
2
+
3
+@interface BottomTabsTogetherAttacher : BottomTabsBaseAttacher
4
+
5
+@end

+ 13
- 0
lib/ios/BottomTabsTogetherAttacher.m View File

@@ -0,0 +1,13 @@
1
+#import "BottomTabsTogetherAttacher.h"
2
+
3
+@implementation BottomTabsTogetherAttacher
4
+
5
+- (void)attach:(UITabBarController *)bottomTabsController {
6
+    for (UIViewController* childViewController in bottomTabsController.childViewControllers) {
7
+        [childViewController render];
8
+    }
9
+    
10
+    [bottomTabsController readyForPresentation];
11
+}
12
+
13
+@end

+ 10
- 0
lib/ios/RNNBottomTabsController.h View File

@@ -2,9 +2,19 @@
2 2
 #import "RNNEventEmitter.h"
3 3
 #import "RNNBottomTabsPresenter.h"
4 4
 #import "UIViewController+LayoutProtocol.h"
5
+#import "BottomTabsBaseAttacher.h"
5 6
 
6 7
 @interface RNNBottomTabsController : UITabBarController <RNNLayoutProtocol, UITabBarControllerDelegate>
7 8
 
9
+- (instancetype)initWithLayoutInfo:(RNNLayoutInfo *)layoutInfo
10
+                           creator:(id<RNNComponentViewCreator>)creator
11
+                           options:(RNNNavigationOptions *)options
12
+                    defaultOptions:(RNNNavigationOptions *)defaultOptions
13
+                         presenter:(RNNBasePresenter *)presenter
14
+                      eventEmitter:(RNNEventEmitter *)eventEmitter
15
+              childViewControllers:(NSArray *)childViewControllers
16
+                bottomTabsAttacher:(BottomTabsBaseAttacher *)bottomTabsAttacher;
17
+
8 18
 - (void)setSelectedIndexByComponentID:(NSString *)componentID;
9 19
 
10 20
 @end

+ 19
- 0
lib/ios/RNNBottomTabsController.m View File

@@ -1,13 +1,32 @@
1 1
 #import "RNNBottomTabsController.h"
2
+#import "UITabBarController+RNNUtils.h"
2 3
 
3 4
 @implementation RNNBottomTabsController {
4 5
 	NSUInteger _currentTabIndex;
6
+    BottomTabsBaseAttacher* _bottomTabsAttacher;
7
+}
8
+
9
+- (instancetype)initWithLayoutInfo:(RNNLayoutInfo *)layoutInfo
10
+                           creator:(id<RNNComponentViewCreator>)creator
11
+                           options:(RNNNavigationOptions *)options
12
+                    defaultOptions:(RNNNavigationOptions *)defaultOptions
13
+                         presenter:(RNNBasePresenter *)presenter
14
+                      eventEmitter:(RNNEventEmitter *)eventEmitter
15
+              childViewControllers:(NSArray *)childViewControllers
16
+                bottomTabsAttacher:(BottomTabsBaseAttacher *)bottomTabsAttacher {
17
+    self = [super initWithLayoutInfo:layoutInfo creator:creator options:options defaultOptions:defaultOptions presenter:presenter eventEmitter:eventEmitter childViewControllers:childViewControllers];
18
+    _bottomTabsAttacher = bottomTabsAttacher;
19
+    return self;
5 20
 }
6 21
 
7 22
 - (id<UITabBarControllerDelegate>)delegate {
8 23
 	return self;
9 24
 }
10 25
 
26
+- (void)render {
27
+    [_bottomTabsAttacher attach:self];
28
+}
29
+
11 30
 - (void)viewDidLayoutSubviews {
12 31
 	[self.presenter viewDidLayoutSubviews];
13 32
 }

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

@@ -1,4 +1,5 @@
1 1
 #import "RNNOptions.h"
2
+#import "BottomTabsAttachMode.h"
2 3
 
3 4
 @interface RNNBottomTabsOptions : RNNOptions
4 5
 
@@ -18,5 +19,6 @@
18 19
 @property (nonatomic, strong) Text* barStyle;
19 20
 @property (nonatomic, strong) Text* fontFamily;
20 21
 @property (nonatomic, strong) Text* titleDisplayMode;
22
+@property (nonatomic, strong) BottomTabsAttachMode* tabsAttachMode;
21 23
 
22 24
 @end

+ 1
- 1
lib/ios/RNNBottomTabsOptions.m View File

@@ -15,12 +15,12 @@
15 15
 	self.hideShadow = [BoolParser parse:dict key:@"hideShadow"];
16 16
 	self.backgroundColor = [ColorParser parse:dict key:@"backgroundColor"];
17 17
 	self.fontSize = [NumberParser parse:dict key:@"fontSize"];
18
-	
19 18
 	self.testID = [TextParser parse:dict key:@"testID"];
20 19
 	self.currentTabId = [TextParser parse:dict key:@"currentTabId"];
21 20
 	self.barStyle = [TextParser parse:dict key:@"barStyle"];
22 21
 	self.fontFamily = [TextParser parse:dict key:@"fontFamily"];
23 22
 	self.titleDisplayMode = [TextParser parse:dict key:@"titleDisplayMode"];
23
+    self.tabsAttachMode = [TextParser parse:dict key:@"tabsAttachMode"];
24 24
 	
25 25
 	return self;
26 26
 }

+ 1
- 1
lib/ios/RNNBridgeManager.m View File

@@ -89,7 +89,7 @@
89 89
 
90 90
 	id<RNNComponentViewCreator> rootViewCreator = [[RNNReactRootViewCreator alloc] initWithBridge:bridge];
91 91
 	_componentRegistry = [[RNNReactComponentRegistry alloc] initWithCreator:rootViewCreator];
92
-	RNNControllerFactory *controllerFactory = [[RNNControllerFactory alloc] initWithRootViewCreator:rootViewCreator eventEmitter:eventEmitter store:_store componentRegistry:_componentRegistry andBridge:bridge];
92
+	RNNControllerFactory *controllerFactory = [[RNNControllerFactory alloc] initWithRootViewCreator:rootViewCreator eventEmitter:eventEmitter store:_store componentRegistry:_componentRegistry andBridge:bridge bottomTabsAttachModeFactory:[BottomTabsAttachModeFactory new]];
93 93
 
94 94
 	_commandsHandler = [[RNNCommandsHandler alloc] initWithControllerFactory:controllerFactory eventEmitter:eventEmitter stackManager:[RNNNavigationStackManager new] modalManager:_modalManager overlayManager:_overlayManager mainWindow:_mainWindow];
95 95
 	RNNBridgeModule *bridgeModule = [[RNNBridgeModule alloc] initWithCommandsHandler:_commandsHandler];

+ 67
- 54
lib/ios/RNNCommandsHandler.m View File

@@ -72,12 +72,15 @@ static NSString* const setDefaultOptions	= @"setDefaultOptions";
72 72
 	[_modalManager dismissAllModalsAnimated:NO completion:nil];
73 73
 	
74 74
 	UIViewController *vc = [_controllerFactory createLayout:layout[@"root"]];
75
-	
76
-	[vc renderTreeAndWait:[vc.resolveOptions.animations.setRoot.waitForRender getWithDefaultValue:NO] perform:^{
77
-		_mainWindow.rootViewController = vc;
78
-		[_eventEmitter sendOnNavigationCommandCompletion:setRoot commandId:commandId params:@{@"layout": layout}];
79
-		completion() ;
80
-	}];
75
+    vc.waitForRender = [vc.resolveOptionsWithDefault.animations.setRoot.waitForRender getWithDefaultValue:NO];
76
+    
77
+    [vc setReactViewReadyCallback:^{
78
+        _mainWindow.rootViewController = vc;
79
+        [_eventEmitter sendOnNavigationCommandCompletion:setRoot commandId:commandId params:@{@"layout": layout}];
80
+        completion();
81
+    }];
82
+    
83
+	[vc render];
81 84
 }
82 85
 
83 86
 - (void)mergeOptions:(NSString*)componentId options:(NSDictionary*)mergeOptions completion:(RNNTransitionCompletionBlock)completion {
@@ -112,18 +115,18 @@ static NSString* const setDefaultOptions	= @"setDefaultOptions";
112 115
 	UIViewController *newVc = [_controllerFactory createLayout:layout];
113 116
 	UIViewController *fromVC = [RNNLayoutManager findComponentForId:componentId];
114 117
 	
115
-	if ([[newVc.resolveOptions.preview.reactTag getWithDefaultValue:@(0)] floatValue] > 0) {
118
+	if ([[newVc.resolveOptionsWithDefault.preview.reactTag getWithDefaultValue:@(0)] floatValue] > 0) {
116 119
 		UIViewController* vc = [RNNLayoutManager findComponentForId:componentId];
117 120
 		
118 121
 		if([vc isKindOfClass:[RNNComponentViewController class]]) {
119 122
 			RNNComponentViewController* rootVc = (RNNComponentViewController*)vc;
120 123
 			rootVc.previewController = newVc;
121
-			[newVc renderTreeAndWait:NO perform:nil];
124
+			[newVc render];
122 125
 			
123 126
 			rootVc.previewCallback = ^(UIViewController *vcc) {
124 127
 				RNNComponentViewController* rvc  = (RNNComponentViewController*)vcc;
125 128
 				[self->_eventEmitter sendOnPreviewCompleted:componentId previewComponentId:newVc.layoutInfo.componentId];
126
-				if ([newVc.resolveOptions.preview.commit getWithDefaultValue:NO]) {
129
+				if ([newVc.resolveOptionsWithDefault.preview.commit getWithDefaultValue:NO]) {
127 130
 					[CATransaction begin];
128 131
 					[CATransaction setCompletionBlock:^{
129 132
 						[self->_eventEmitter sendOnNavigationCommandCompletion:push commandId:commandId params:@{@"componentId": componentId}];
@@ -136,31 +139,34 @@ static NSString* const setDefaultOptions	= @"setDefaultOptions";
136 139
 			
137 140
 			CGSize size = CGSizeMake(rootVc.view.frame.size.width, rootVc.view.frame.size.height);
138 141
 			
139
-			if (newVc.resolveOptions.preview.width.hasValue) {
140
-				size.width = [newVc.resolveOptions.preview.width.get floatValue];
142
+			if (newVc.resolveOptionsWithDefault.preview.width.hasValue) {
143
+				size.width = [newVc.resolveOptionsWithDefault.preview.width.get floatValue];
141 144
 			}
142 145
 			
143
-			if (newVc.resolveOptions.preview.height.hasValue) {
144
-				size.height = [newVc.resolveOptions.preview.height.get floatValue];
146
+			if (newVc.resolveOptionsWithDefault.preview.height.hasValue) {
147
+				size.height = [newVc.resolveOptionsWithDefault.preview.height.get floatValue];
145 148
 			}
146 149
 			
147
-			if (newVc.resolveOptions.preview.width.hasValue || newVc.resolveOptions.preview.height.hasValue) {
150
+			if (newVc.resolveOptionsWithDefault.preview.width.hasValue || newVc.resolveOptionsWithDefault.preview.height.hasValue) {
148 151
 				newVc.preferredContentSize = size;
149 152
 			}
150 153
 			
151 154
 			RCTExecuteOnMainQueue(^{
152
-				UIView *view = [[ReactNativeNavigation getBridge].uiManager viewForReactTag:newVc.resolveOptions.preview.reactTag.get];
155
+				UIView *view = [[ReactNativeNavigation getBridge].uiManager viewForReactTag:newVc.resolveOptionsWithDefault.preview.reactTag.get];
153 156
 				[rootVc registerForPreviewingWithDelegate:(id)rootVc sourceView:view];
154 157
 			});
155 158
 		}
156 159
 	} else {
157
-		id animationDelegate = (newVc.resolveOptions.animations.push.hasCustomAnimation || newVc.resolveOptions.customTransition.animations) ? newVc : nil;
158
-		[newVc renderTreeAndWait:([newVc.resolveOptions.animations.push.waitForRender getWithDefaultValue:NO] || animationDelegate) perform:^{
159
-			[_stackManager push:newVc onTop:fromVC animated:[newVc.resolveOptions.animations.push.enable getWithDefaultValue:YES] animationDelegate:animationDelegate completion:^{
160
-				[_eventEmitter sendOnNavigationCommandCompletion:push commandId:commandId params:@{@"componentId": componentId}];
161
-				completion();
162
-			} rejection:rejection];
163
-		}];
160
+		id animationDelegate = (newVc.resolveOptionsWithDefault.animations.push.hasCustomAnimation || newVc.resolveOptionsWithDefault.customTransition.animations) ? newVc : nil;
161
+        newVc.waitForRender = ([newVc.resolveOptionsWithDefault.animations.push.waitForRender getWithDefaultValue:NO] || animationDelegate);
162
+        [newVc setReactViewReadyCallback:^{
163
+            [_stackManager push:newVc onTop:fromVC animated:[newVc.resolveOptionsWithDefault.animations.push.enable getWithDefaultValue:YES] animationDelegate:animationDelegate completion:^{
164
+                [_eventEmitter sendOnNavigationCommandCompletion:push commandId:commandId params:@{@"componentId": componentId}];
165
+                completion();
166
+            } rejection:rejection];
167
+        }];
168
+        
169
+        [newVc render];
164 170
 	}
165 171
 }
166 172
 
@@ -170,20 +176,23 @@ static NSString* const setDefaultOptions	= @"setDefaultOptions";
170 176
 	NSArray<UIViewController *> *childViewControllers = [_controllerFactory createChildrenLayout:children];
171 177
 	for (UIViewController<RNNLayoutProtocol>* viewController in childViewControllers) {
172 178
 		if (![viewController isEqual:childViewControllers.lastObject]) {
173
-			[viewController renderTreeAndWait:NO perform:nil];
179
+			[viewController render];
174 180
 		}
175 181
 	}
176 182
 	UIViewController *newVC = childViewControllers.lastObject;
177 183
 	UIViewController *fromVC = [RNNLayoutManager findComponentForId:componentId];
178
-	RNNNavigationOptions* options = newVC.resolveOptions;
184
+	RNNNavigationOptions* options = newVC.resolveOptionsWithDefault;
179 185
 	__weak typeof(RNNEventEmitter*) weakEventEmitter = _eventEmitter;
180 186
 
181
-	[newVC renderTreeAndWait:([options.animations.setStackRoot.waitForRender getWithDefaultValue:NO]) perform:^{
182
-		[_stackManager setStackChildren:childViewControllers fromViewController:fromVC animated:[options.animations.setStackRoot.enable getWithDefaultValue:YES] completion:^{
183
-			[weakEventEmitter sendOnNavigationCommandCompletion:setStackRoot commandId:commandId params:@{@"componentId": componentId}];
184
-			completion();
185
-		} rejection:rejection];
186
-	}]; 
187
+    newVC.waitForRender = ([options.animations.setStackRoot.waitForRender getWithDefaultValue:NO]);
188
+    [newVC setReactViewReadyCallback:^{
189
+        [self->_stackManager setStackChildren:childViewControllers fromViewController:fromVC animated:[options.animations.setStackRoot.enable getWithDefaultValue:YES] completion:^{
190
+            [weakEventEmitter sendOnNavigationCommandCompletion:setStackRoot commandId:commandId params:@{@"componentId": componentId}];
191
+            completion();
192
+        } rejection:rejection];
193
+    }];
194
+
195
+    [newVC render];
187 196
 }
188 197
 
189 198
 - (void)pop:(NSString*)componentId commandId:(NSString*)commandId mergeOptions:(NSDictionary*)mergeOptions completion:(RNNTransitionCompletionBlock)completion rejection:(RCTPromiseRejectBlock)rejection {
@@ -196,7 +205,7 @@ static NSString* const setDefaultOptions	= @"setDefaultOptions";
196 205
 	UINavigationController *nvc = vc.navigationController;
197 206
 	
198 207
 	if ([nvc topViewController] == vc) {
199
-		if (vc.resolveOptions.animations.pop) {
208
+		if (vc.resolveOptionsWithDefault.animations.pop) {
200 209
 			nvc.delegate = vc;
201 210
 		} else {
202 211
 			nvc.delegate = nil;
@@ -204,10 +213,10 @@ static NSString* const setDefaultOptions	= @"setDefaultOptions";
204 213
 	} else {
205 214
 		NSMutableArray * vcs = nvc.viewControllers.mutableCopy;
206 215
 		[vcs removeObject:vc];
207
-		[nvc setViewControllers:vcs animated:[vc.resolveOptions.animations.pop.enable getWithDefaultValue:YES]];
216
+		[nvc setViewControllers:vcs animated:[vc.resolveOptionsWithDefault.animations.pop.enable getWithDefaultValue:YES]];
208 217
 	}
209 218
 	
210
-	[_stackManager pop:vc animated:[vc.resolveOptions.animations.pop.enable getWithDefaultValue:YES] completion:^{
219
+	[_stackManager pop:vc animated:[vc.resolveOptionsWithDefault.animations.pop.enable getWithDefaultValue:YES] completion:^{
211 220
 		[_eventEmitter sendOnNavigationCommandCompletion:pop commandId:commandId params:@{@"componentId": componentId}];
212 221
 		completion();
213 222
 	} rejection:rejection];
@@ -219,7 +228,7 @@ static NSString* const setDefaultOptions	= @"setDefaultOptions";
219 228
 	RNNNavigationOptions *options = [[RNNNavigationOptions alloc] initWithDict:mergeOptions];
220 229
 	[vc overrideOptions:options];
221 230
 	
222
-	[_stackManager popTo:vc animated:[vc.resolveOptions.animations.pop.enable getWithDefaultValue:YES] completion:^(NSArray *poppedViewControllers) {
231
+	[_stackManager popTo:vc animated:[vc.resolveOptionsWithDefault.animations.pop.enable getWithDefaultValue:YES] completion:^(NSArray *poppedViewControllers) {
223 232
 		[_eventEmitter sendOnNavigationCommandCompletion:popTo commandId:commandId params:@{@"componentId": componentId}];
224 233
 		completion();
225 234
 	} rejection:rejection];
@@ -237,7 +246,7 @@ static NSString* const setDefaultOptions	= @"setDefaultOptions";
237 246
 		completion();
238 247
 	}];
239 248
 	
240
-	[_stackManager popToRoot:vc animated:[vc.resolveOptions.animations.pop.enable getWithDefaultValue:YES] completion:^(NSArray *poppedViewControllers) {
249
+	[_stackManager popToRoot:vc animated:[vc.resolveOptionsWithDefault.animations.pop.enable getWithDefaultValue:YES] completion:^(NSArray *poppedViewControllers) {
241 250
 		
242 251
 	} rejection:^(NSString *code, NSString *message, NSError *error) {
243 252
 		
@@ -251,12 +260,14 @@ static NSString* const setDefaultOptions	= @"setDefaultOptions";
251 260
 	
252 261
 	UIViewController *newVc = [_controllerFactory createLayout:layout];
253 262
 	
254
-	[newVc renderTreeAndWait:[newVc.resolveOptions.animations.showModal.waitForRender getWithDefaultValue:NO] perform:^{
255
-		[_modalManager showModal:newVc animated:[newVc.resolveOptions.animations.showModal.enable getWithDefaultValue:YES] hasCustomAnimation:newVc.resolveOptions.animations.showModal.hasCustomAnimation completion:^(NSString *componentId) {
256
-			[_eventEmitter sendOnNavigationCommandCompletion:showModal commandId:commandId params:@{@"layout": layout}];
257
-			completion(newVc.layoutInfo.componentId);
258
-		}];
259
-	}];
263
+    newVc.waitForRender = [newVc.resolveOptionsWithDefault.animations.showModal.waitForRender getWithDefaultValue:NO];
264
+    [newVc setReactViewReadyCallback:^{
265
+        [_modalManager showModal:newVc animated:[newVc.resolveOptionsWithDefault.animations.showModal.enable getWithDefaultValue:YES] hasCustomAnimation:newVc.resolveOptionsWithDefault.animations.showModal.hasCustomAnimation completion:^(NSString *componentId) {
266
+            [self->_eventEmitter sendOnNavigationCommandCompletion:showModal commandId:commandId params:@{@"layout": layout}];
267
+            completion(newVc.layoutInfo.componentId);
268
+        }];
269
+    }];
270
+	[newVc render];
260 271
 }
261 272
 
262 273
 - (void)dismissModal:(NSString*)componentId commandId:(NSString*)commandId mergeOptions:(NSDictionary *)mergeOptions completion:(RNNTransitionCompletionBlock)completion rejection:(RNNTransitionRejectionBlock)reject {
@@ -274,7 +285,7 @@ static NSString* const setDefaultOptions	= @"setDefaultOptions";
274 285
 	
275 286
 	[CATransaction begin];
276 287
 	[CATransaction setCompletionBlock:^{
277
-		[_eventEmitter sendOnNavigationCommandCompletion:dismissModal commandId:commandId params:@{@"componentId": componentId}];
288
+        [self->_eventEmitter sendOnNavigationCommandCompletion:dismissModal commandId:commandId params:@{@"componentId": componentId}];
278 289
 	}];
279 290
 	
280 291
 	[_modalManager dismissModal:modalToDismiss completion:completion];
@@ -300,18 +311,20 @@ static NSString* const setDefaultOptions	= @"setDefaultOptions";
300 311
 	[self assertReady];
301 312
 	
302 313
 	UIViewController* overlayVC = [_controllerFactory createLayout:layout];
303
-	[overlayVC renderTreeAndWait:NO perform:^{
304
-		UIWindow* overlayWindow = [[RNNOverlayWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
305
-		overlayWindow.rootViewController = overlayVC;
306
-		if ([overlayVC.resolveOptions.overlay.handleKeyboardEvents getWithDefaultValue:NO]) {
307
-			[_overlayManager showOverlayWindowAsKeyWindow:overlayWindow];
308
-		} else {
309
-			[_overlayManager showOverlayWindow:overlayWindow];
310
-		}
311
-		
312
-		[_eventEmitter sendOnNavigationCommandCompletion:showOverlay commandId:commandId params:@{@"layout": layout}];
313
-		completion();
314
-	}];
314
+    [overlayVC setReactViewReadyCallback:^{UIWindow* overlayWindow = [[RNNOverlayWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
315
+        overlayWindow.rootViewController = overlayVC;
316
+        if ([overlayVC.resolveOptionsWithDefault.overlay.handleKeyboardEvents getWithDefaultValue:NO]) {
317
+            [self->_overlayManager showOverlayWindowAsKeyWindow:overlayWindow];
318
+        } else {
319
+            [self->_overlayManager showOverlayWindow:overlayWindow];
320
+        }
321
+        
322
+        [self->_eventEmitter sendOnNavigationCommandCompletion:showOverlay commandId:commandId params:@{@"layout": layout}];
323
+        completion();
324
+        
325
+    }];
326
+    
327
+    [overlayVC render];
315 328
 }
316 329
 
317 330
 - (void)dismissOverlay:(NSString*)componentId commandId:(NSString*)commandId completion:(RNNTransitionCompletionBlock)completion rejection:(RNNTransitionRejectionBlock)reject {

+ 1
- 11
lib/ios/RNNComponentViewController.h View File

@@ -10,7 +10,7 @@
10 10
 
11 11
 typedef void (^PreviewCallback)(UIViewController *vc);
12 12
 
13
-@interface RNNComponentViewController : UIViewController	<RNNLayoutProtocol, UIViewControllerPreviewingDelegate, UISearchResultsUpdating, UISearchBarDelegate, UINavigationControllerDelegate, UISplitViewControllerDelegate>
13
+@interface RNNComponentViewController : UIViewController <RNNLayoutProtocol, UIViewControllerPreviewingDelegate, UISearchResultsUpdating, UISearchBarDelegate, UINavigationControllerDelegate, UISplitViewControllerDelegate>
14 14
 
15 15
 @property (nonatomic, strong) RNNEventEmitter *eventEmitter;
16 16
 @property (nonatomic, retain) RNNLayoutInfo* layoutInfo;
@@ -29,16 +29,6 @@ typedef void (^PreviewCallback)(UIViewController *vc);
29 29
 						   options:(RNNNavigationOptions *)options
30 30
 					defaultOptions:(RNNNavigationOptions *)defaultOptions;
31 31
 
32
-- (instancetype)initExternalComponentWithLayoutInfo:(RNNLayoutInfo *)layoutInfo
33
-									   eventEmitter:(RNNEventEmitter*)eventEmitter
34
-										  presenter:(RNNComponentPresenter *)presenter
35
-											options:(RNNNavigationOptions *)options
36
-									 defaultOptions:(RNNNavigationOptions *)defaultOptions;
37
-
38
-- (BOOL)isExternalViewController;
39
-
40 32
 - (void)onButtonPress:(RNNUIBarButtonItem *)barButtonItem;
41 33
 
42
-- (void)bindViewController:(UIViewController *)viewController;
43
-
44 34
 @end

+ 22
- 43
lib/ios/RNNComponentViewController.m View File

@@ -16,17 +16,6 @@
16 16
 	return self;
17 17
 }
18 18
 
19
-- (instancetype)initExternalComponentWithLayoutInfo:(RNNLayoutInfo *)layoutInfo eventEmitter:(RNNEventEmitter *)eventEmitter presenter:(RNNComponentPresenter *)presenter options:(RNNNavigationOptions *)options defaultOptions:(RNNNavigationOptions *)defaultOptions {
20
-	self = [self initWithLayoutInfo:layoutInfo rootViewCreator:nil eventEmitter:eventEmitter presenter:presenter options:options defaultOptions:defaultOptions];
21
-	return self;
22
-}
23
-
24
-- (void)bindViewController:(UIViewController *)viewController {
25
-	[self addChildViewController:viewController];
26
-	[self.view addSubview:viewController.view];
27
-	[viewController didMoveToParentViewController:self];
28
-}
29
-
30 19
 - (void)setDefaultOptions:(RNNNavigationOptions *)defaultOptions {
31 20
     _defaultOptions = defaultOptions;
32 21
 	[_presenter setDefaultOptions:defaultOptions];
@@ -36,12 +25,9 @@
36 25
 	[self.options overrideOptions:options];
37 26
 }
38 27
 
39
-- (void)viewWillAppear:(BOOL)animated{
28
+- (void)viewWillAppear:(BOOL)animated {
40 29
 	[super viewWillAppear:animated];
41
-	
42 30
 	[_presenter applyOptions:self.resolveOptions];
43
-	[_presenter renderComponents:self.resolveOptions perform:nil];
44
-	
45 31
 	[self.parentViewController onChildWillAppear];
46 32
 }
47 33
 
@@ -59,30 +45,27 @@
59 45
 	[self.eventEmitter sendComponentDidDisappear:self.layoutInfo.componentId componentName:self.layoutInfo.name];
60 46
 }
61 47
 
62
-- (void)renderTreeAndWait:(BOOL)wait perform:(RNNReactViewReadyCompletionBlock)readyBlock {
63
-	if (self.isExternalViewController) {
64
-		if (readyBlock) {
65
-			readyBlock();
66
-		}
67
-		return;
68
-	}
69
-	
70
-	__block RNNReactViewReadyCompletionBlock readyBlockCopy = readyBlock;
71
-	UIView* reactView = [self.creator createRootView:self.layoutInfo.name rootViewId:self.layoutInfo.componentId availableSize:[UIScreen mainScreen].bounds.size reactViewReadyBlock:^{
72
-		[_presenter renderComponents:self.resolveOptions perform:^{
73
-			if (readyBlockCopy) {
74
-				readyBlockCopy();
75
-				readyBlockCopy = nil;
76
-			}
77
-		}];
78
-	}];
79
-	
80
-	self.view = reactView;
81
-	
82
-	if (!wait && readyBlock) {
83
-		readyBlockCopy();
84
-		readyBlockCopy = nil;
85
-	}
48
+- (void)loadView {
49
+	[self renderReactViewIfNeeded];
50
+}
51
+
52
+- (void)render {
53
+    if (!self.waitForRender)
54
+        [self readyForPresentation];
55
+    else
56
+        [self renderReactViewIfNeeded];
57
+}
58
+
59
+- (void)renderReactViewIfNeeded {
60
+    if (!self.isViewLoaded) {
61
+        self.view = [self.creator createRootView:self.layoutInfo.name rootViewId:self.layoutInfo.componentId reactViewReadyBlock:^{
62
+            [self->_presenter renderComponents:self.resolveOptions perform:^{
63
+                [self readyForPresentation];
64
+            }];
65
+        }];
66
+    } else {
67
+        [self readyForPresentation];
68
+    }
86 69
 }
87 70
 
88 71
 - (UIViewController *)getCurrentChild {
@@ -103,10 +86,6 @@
103 86
 	return self.resolveOptions.customTransition.animations != nil;
104 87
 }
105 88
 
106
-- (BOOL)isExternalViewController {
107
-	return !self.creator;
108
-}
109
-
110 89
 - (BOOL)prefersStatusBarHidden {
111 90
 	return [_presenter isStatusBarVisibility:self.navigationController resolvedOptions:self.resolveOptions];
112 91
 }

+ 1
- 1
lib/ios/RNNComponentViewCreator.h View File

@@ -5,7 +5,7 @@
5 5
 
6 6
 @protocol RNNComponentViewCreator
7 7
 
8
-- (RNNReactView*)createRootView:(NSString*)name rootViewId:(NSString*)rootViewId availableSize:(CGSize)availableSize reactViewReadyBlock:(RNNReactViewReadyCompletionBlock)reactViewReadyBlock;
8
+- (RNNReactView*)createRootView:(NSString*)name rootViewId:(NSString*)rootViewId reactViewReadyBlock:(RNNReactViewReadyCompletionBlock)reactViewReadyBlock;
9 9
 
10 10
 - (UIView*)createRootViewFromComponentOptions:(RNNComponentOptions*)componentOptions;
11 11
 

+ 6
- 4
lib/ios/RNNControllerFactory.h View File

@@ -6,14 +6,16 @@
6 6
 #import "RNNEventEmitter.h"
7 7
 #import "RNNReactComponentRegistry.h"
8 8
 #import "RNNNavigationOptions.h"
9
+#import "BottomTabsAttachModeFactory.h"
9 10
 
10 11
 @interface RNNControllerFactory : NSObject
11 12
 
12 13
 -(instancetype)initWithRootViewCreator:(id <RNNComponentViewCreator>)creator
13
-						  eventEmitter:(RNNEventEmitter*)eventEmitter
14
-								 store:(RNNExternalComponentStore *)store
15
-					  componentRegistry:(RNNReactComponentRegistry *)componentRegistry
16
-							 andBridge:(RCTBridge*)bridge;
14
+                          eventEmitter:(RNNEventEmitter*)eventEmitter
15
+                                 store:(RNNExternalComponentStore *)store
16
+                     componentRegistry:(RNNReactComponentRegistry *)componentRegistry
17
+                             andBridge:(RCTBridge*)bridge
18
+           bottomTabsAttachModeFactory:(BottomTabsAttachModeFactory *)bottomTabsAttachModeFactory;
17 19
 
18 20
 - (UIViewController *)createLayout:(NSDictionary*)layout;
19 21
 

+ 32
- 19
lib/ios/RNNControllerFactory.m View File

@@ -5,22 +5,27 @@
5 5
 #import "RNNBottomTabsController.h"
6 6
 #import "RNNTopTabsViewController.h"
7 7
 #import "RNNComponentViewController.h"
8
+#import "RNNExternalViewController.h"
9
+#import "BottomTabsBaseAttacher.h"
10
+#import "BottomTabsAttachModeFactory.h"
8 11
 
9 12
 @implementation RNNControllerFactory {
10 13
 	id<RNNComponentViewCreator> _creator;
11 14
 	RNNExternalComponentStore *_store;
12 15
 	RCTBridge *_bridge;
13 16
 	RNNReactComponentRegistry* _componentRegistry;
17
+    BottomTabsAttachModeFactory* _bottomTabsAttachModeFactory;
14 18
 }
15 19
 
16 20
 # pragma mark public
17 21
 
18 22
 
19 23
 - (instancetype)initWithRootViewCreator:(id <RNNComponentViewCreator>)creator
20
-						   eventEmitter:(RNNEventEmitter*)eventEmitter
21
-								  store:(RNNExternalComponentStore *)store
22
-					   componentRegistry:(RNNReactComponentRegistry *)componentRegistry
23
-							  andBridge:(RCTBridge *)bridge {
24
+                           eventEmitter:(RNNEventEmitter*)eventEmitter
25
+                                  store:(RNNExternalComponentStore *)store
26
+                      componentRegistry:(RNNReactComponentRegistry *)componentRegistry
27
+                              andBridge:(RCTBridge *)bridge
28
+            bottomTabsAttachModeFactory:(BottomTabsAttachModeFactory *)bottomTabsAttachModeFactory {
24 29
 	
25 30
 	self = [super init];
26 31
 	
@@ -29,10 +34,16 @@
29 34
 	_bridge = bridge;
30 35
 	_store = store;
31 36
 	_componentRegistry = componentRegistry;
37
+    _bottomTabsAttachModeFactory = bottomTabsAttachModeFactory;
32 38
 
33 39
 	return self;
34 40
 }
35 41
 
42
+- (void)setDefaultOptions:(RNNNavigationOptions *)defaultOptions {
43
+    _defaultOptions = defaultOptions;
44
+    _bottomTabsAttachModeFactory.defaultOptions = defaultOptions;
45
+}
46
+
36 47
 - (UIViewController *)createLayout:(NSDictionary*)layout {
37 48
 	UIViewController* layoutViewController = [self fromTree:layout];
38 49
 	return layoutViewController;
@@ -117,8 +128,7 @@
117 128
 	
118 129
 	UIViewController* externalVC = [_store getExternalComponent:layoutInfo bridge:_bridge];
119 130
 	
120
-	RNNComponentViewController* component = [[RNNComponentViewController alloc] initExternalComponentWithLayoutInfo:layoutInfo eventEmitter:_eventEmitter presenter:presenter options:options defaultOptions:_defaultOptions];
121
-	[component bindViewController:externalVC];
131
+    RNNExternalViewController* component = [[RNNExternalViewController alloc] initWithLayoutInfo:layoutInfo eventEmitter:_eventEmitter presenter:presenter options:options defaultOptions:_defaultOptions viewController:externalVC];
122 132
 	
123 133
 	return component;
124 134
 }
@@ -135,19 +145,22 @@
135 145
 	return stack;
136 146
 }
137 147
 
138
--(UIViewController *)createBottomTabs:(RNNLayoutNode*)node {
139
-	RNNLayoutInfo* layoutInfo = [[RNNLayoutInfo alloc] initWithNode:node];
140
-	RNNNavigationOptions* options = [[RNNNavigationOptions alloc] initWithDict:node.data[@"options"]];
141
-	RNNBottomTabsPresenter* presenter = [[RNNBottomTabsPresenter alloc] initWithDefaultOptions:_defaultOptions];
142
-	NSArray *childViewControllers = [self extractChildrenViewControllersFromNode:node];
143
-	return [[RNNBottomTabsController alloc] initWithLayoutInfo:layoutInfo
144
-																					  creator:_creator
145
-																					  options:options
146
-																			   defaultOptions:_defaultOptions
147
-																					presenter:presenter
148
-																				 eventEmitter:_eventEmitter
149
-																		 childViewControllers:childViewControllers
150
-	];
148
+- (UIViewController *)createBottomTabs:(RNNLayoutNode*)node {
149
+    RNNLayoutInfo* layoutInfo = [[RNNLayoutInfo alloc] initWithNode:node];
150
+    RNNNavigationOptions* options = [[RNNNavigationOptions alloc] initWithDict:node.data[@"options"]];
151
+    RNNBottomTabsPresenter* presenter = [[RNNBottomTabsPresenter alloc] initWithDefaultOptions:_defaultOptions];
152
+	BottomTabsBaseAttacher* bottomTabsAttacher = [_bottomTabsAttachModeFactory fromOptions:options];
153
+    
154
+    NSArray *childViewControllers = [self extractChildrenViewControllersFromNode:node];
155
+    return [[RNNBottomTabsController alloc] initWithLayoutInfo:layoutInfo
156
+                                                       creator:_creator
157
+                                                       options:options
158
+                                                defaultOptions:_defaultOptions
159
+                                                     presenter:presenter
160
+                                                  eventEmitter:_eventEmitter
161
+                                          childViewControllers:childViewControllers
162
+                                            bottomTabsAttacher:bottomTabsAttacher
163
+            ];
151 164
 }
152 165
 
153 166
 - (UIViewController *)createTopTabs:(RNNLayoutNode*)node {

+ 7
- 0
lib/ios/RNNExternalViewController.h View File

@@ -0,0 +1,7 @@
1
+#import "RNNComponentViewController.h"
2
+
3
+@interface RNNExternalViewController : RNNComponentViewController
4
+
5
+- (instancetype)initWithLayoutInfo:(RNNLayoutInfo *)layoutInfo eventEmitter:(RNNEventEmitter *)eventEmitter presenter:(RNNComponentPresenter *)presenter options:(RNNNavigationOptions *)options defaultOptions:(RNNNavigationOptions *)defaultOptions viewController:(UIViewController *)viewController;
6
+
7
+@end

+ 25
- 0
lib/ios/RNNExternalViewController.m View File

@@ -0,0 +1,25 @@
1
+#import "RNNExternalViewController.h"
2
+
3
+@implementation RNNExternalViewController
4
+
5
+- (instancetype)initWithLayoutInfo:(RNNLayoutInfo *)layoutInfo eventEmitter:(RNNEventEmitter *)eventEmitter presenter:(RNNComponentPresenter *)presenter options:(RNNNavigationOptions *)options defaultOptions:(RNNNavigationOptions *)defaultOptions viewController:(UIViewController *)viewController {
6
+	self = [super initWithLayoutInfo:layoutInfo rootViewCreator:nil eventEmitter:eventEmitter presenter:presenter options:options defaultOptions:defaultOptions];
7
+    [self bindViewController:viewController];
8
+	return self;
9
+}
10
+
11
+- (void)bindViewController:(UIViewController *)viewController {
12
+    [self addChildViewController:viewController];
13
+    [self.view addSubview:viewController.view];
14
+    [viewController didMoveToParentViewController:self];
15
+}
16
+
17
+- (void)loadView {
18
+	self.view = [UIView new];
19
+}
20
+
21
+- (void)render {
22
+	[self readyForPresentation];
23
+}
24
+
25
+@end

+ 3
- 1
lib/ios/RNNLayoutProtocol.h View File

@@ -17,7 +17,7 @@ typedef void (^RNNReactViewReadyCompletionBlock)(void);
17 17
 					  eventEmitter:(RNNEventEmitter *)eventEmitter
18 18
 			  childViewControllers:(NSArray *)childViewControllers;
19 19
 
20
-- (void)renderTreeAndWait:(BOOL)wait perform:(RNNReactViewReadyCompletionBlock)readyBlock;
20
+- (void)render;
21 21
 
22 22
 - (UIViewController<RNNLayoutProtocol> *)getCurrentChild;
23 23
 
@@ -35,4 +35,6 @@ typedef void (^RNNReactViewReadyCompletionBlock)(void);
35 35
 
36 36
 - (void)onChildWillAppear;
37 37
 
38
+- (void)readyForPresentation;
39
+
38 40
 @end

+ 3
- 4
lib/ios/RNNReactRootViewCreator.m View File

@@ -14,7 +14,7 @@
14 14
 	return self;
15 15
 }
16 16
 
17
-- (RNNReactView*)createRootView:(NSString*)name rootViewId:(NSString*)rootViewId availableSize:(CGSize)availableSize reactViewReadyBlock:(RNNReactViewReadyCompletionBlock)reactViewReadyBlock {
17
+- (RNNReactView*)createRootView:(NSString*)name rootViewId:(NSString*)rootViewId reactViewReadyBlock:(RNNReactViewReadyCompletionBlock)reactViewReadyBlock {
18 18
 	if (!rootViewId) {
19 19
 		@throw [NSException exceptionWithName:@"MissingViewId" reason:@"Missing view id" userInfo:nil];
20 20
 	}
@@ -22,17 +22,16 @@
22 22
 	RNNReactView *view = [[RNNReactView alloc] initWithBridge:_bridge
23 23
 												   moduleName:name
24 24
 											initialProperties:@{@"componentId": rootViewId}
25
-												availableSize:availableSize
26 25
 										  reactViewReadyBlock:reactViewReadyBlock];
27 26
 	return view;
28 27
 }
29 28
 
30 29
 - (UIView*)createRootViewFromComponentOptions:(RNNComponentOptions*)componentOptions {
31
-	return [self createRootView:componentOptions.name.get rootViewId:componentOptions.componentId.get availableSize:CGSizeZero reactViewReadyBlock:nil];
30
+	return [self createRootView:componentOptions.name.get rootViewId:componentOptions.componentId.get reactViewReadyBlock:nil];
32 31
 }
33 32
 
34 33
 - (UIView*)createRootViewFromComponentOptions:(RNNComponentOptions*)componentOptions reactViewReadyBlock:(RNNReactViewReadyCompletionBlock)reactViewReadyBlock {
35
-	return [self createRootView:componentOptions.name.get rootViewId:componentOptions.componentId.get availableSize:CGSizeZero reactViewReadyBlock:reactViewReadyBlock];
34
+	return [self createRootView:componentOptions.name.get rootViewId:componentOptions.componentId.get reactViewReadyBlock:reactViewReadyBlock];
36 35
 }
37 36
 
38 37
 @end

+ 1
- 1
lib/ios/RNNReactView.h View File

@@ -5,7 +5,7 @@ typedef void (^RNNReactViewReadyCompletionBlock)(void);
5 5
 
6 6
 @interface RNNReactView : RCTRootView <RCTRootViewDelegate>
7 7
 
8
-- (instancetype)initWithBridge:(RCTBridge *)bridge moduleName:(NSString *)moduleName initialProperties:(NSDictionary *)initialProperties availableSize:(CGSize)availableSize reactViewReadyBlock:(RNNReactViewReadyCompletionBlock)reactViewReadyBlock;
8
+- (instancetype)initWithBridge:(RCTBridge *)bridge moduleName:(NSString *)moduleName initialProperties:(NSDictionary *)initialProperties reactViewReadyBlock:(RNNReactViewReadyCompletionBlock)reactViewReadyBlock;
9 9
 
10 10
 @property (nonatomic, copy) void (^rootViewDidChangeIntrinsicSize)(CGSize intrinsicSize);
11 11
 @property (nonatomic, copy) RNNReactViewReadyCompletionBlock reactViewReadyBlock;

+ 1
- 2
lib/ios/RNNReactView.m View File

@@ -6,11 +6,10 @@
6 6
 	BOOL _fillParent;
7 7
 }
8 8
 
9
-- (instancetype)initWithBridge:(RCTBridge *)bridge moduleName:(NSString *)moduleName initialProperties:(NSDictionary *)initialProperties availableSize:(CGSize)availableSize reactViewReadyBlock:(RNNReactViewReadyCompletionBlock)reactViewReadyBlock {
9
+- (instancetype)initWithBridge:(RCTBridge *)bridge moduleName:(NSString *)moduleName initialProperties:(NSDictionary *)initialProperties reactViewReadyBlock:(RNNReactViewReadyCompletionBlock)reactViewReadyBlock {
10 10
 	self = [super initWithBridge:bridge moduleName:moduleName initialProperties:initialProperties];
11 11
 	[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(contentDidAppear:) name:RCTContentDidAppearNotification object:nil];
12 12
 	 _reactViewReadyBlock = reactViewReadyBlock;
13
-	[bridge.uiManager setAvailableSize:availableSize forRootView:self];
14 13
 	
15 14
 	return self;
16 15
 }

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

@@ -17,8 +17,8 @@
17 17
 	return self;
18 18
 }
19 19
 
20
-- (void)renderTreeAndWait:(BOOL)wait perform:(RNNReactViewReadyCompletionBlock)readyBlock {
21
-	[self.getCurrentChild renderTreeAndWait:wait perform:readyBlock];
20
+- (void)render {
21
+	[self.getCurrentChild render];
22 22
 }
23 23
 
24 24
 - (void)setChild:(UIViewController<RNNLayoutProtocol> *)child {

+ 86
- 6
lib/ios/ReactNativeNavigation.xcodeproj/project.pbxproj View File

@@ -76,6 +76,16 @@
76 76
 		5016E8F020209690009D4F7C /* RNNCustomTitleView.m in Sources */ = {isa = PBXBuildFile; fileRef = 5016E8EE2020968F009D4F7C /* RNNCustomTitleView.m */; };
77 77
 		50175CD1207A2AA1004FE91B /* RNNComponentOptions.h in Headers */ = {isa = PBXBuildFile; fileRef = 50175CCF207A2AA1004FE91B /* RNNComponentOptions.h */; };
78 78
 		50175CD2207A2AA1004FE91B /* RNNComponentOptions.m in Sources */ = {isa = PBXBuildFile; fileRef = 50175CD0207A2AA1004FE91B /* RNNComponentOptions.m */; };
79
+		5017D9E1239D2C6C00B74047 /* BottomTabsAttachModeFactory.h in Headers */ = {isa = PBXBuildFile; fileRef = 5017D9DF239D2C6C00B74047 /* BottomTabsAttachModeFactory.h */; };
80
+		5017D9E2239D2C6C00B74047 /* BottomTabsAttachModeFactory.m in Sources */ = {isa = PBXBuildFile; fileRef = 5017D9E0239D2C6C00B74047 /* BottomTabsAttachModeFactory.m */; };
81
+		5017D9E6239D2D9E00B74047 /* BottomTabsBaseAttacher.h in Headers */ = {isa = PBXBuildFile; fileRef = 5017D9E4239D2D9E00B74047 /* BottomTabsBaseAttacher.h */; };
82
+		5017D9E7239D2D9E00B74047 /* BottomTabsBaseAttacher.m in Sources */ = {isa = PBXBuildFile; fileRef = 5017D9E5239D2D9E00B74047 /* BottomTabsBaseAttacher.m */; };
83
+		5017D9EA239D2F9D00B74047 /* BottomTabsTogetherAttacher.h in Headers */ = {isa = PBXBuildFile; fileRef = 5017D9E8239D2F9D00B74047 /* BottomTabsTogetherAttacher.h */; };
84
+		5017D9EB239D2F9D00B74047 /* BottomTabsTogetherAttacher.m in Sources */ = {isa = PBXBuildFile; fileRef = 5017D9E9239D2F9D00B74047 /* BottomTabsTogetherAttacher.m */; };
85
+		5017D9EE239D2FAF00B74047 /* BottomTabsAfterInitialTabAttacher.h in Headers */ = {isa = PBXBuildFile; fileRef = 5017D9EC239D2FAF00B74047 /* BottomTabsAfterInitialTabAttacher.h */; };
86
+		5017D9EF239D2FAF00B74047 /* BottomTabsAfterInitialTabAttacher.m in Sources */ = {isa = PBXBuildFile; fileRef = 5017D9ED239D2FAF00B74047 /* BottomTabsAfterInitialTabAttacher.m */; };
87
+		5017D9F2239D2FCB00B74047 /* BottomTabsOnSwitchToTabAttacher.h in Headers */ = {isa = PBXBuildFile; fileRef = 5017D9F0239D2FCB00B74047 /* BottomTabsOnSwitchToTabAttacher.h */; };
88
+		5017D9F3239D2FCB00B74047 /* BottomTabsOnSwitchToTabAttacher.m in Sources */ = {isa = PBXBuildFile; fileRef = 5017D9F1239D2FCB00B74047 /* BottomTabsOnSwitchToTabAttacher.m */; };
79 89
 		501CD31F214A5B6900A6E225 /* RNNLayoutInfo.h in Headers */ = {isa = PBXBuildFile; fileRef = 501CD31D214A5B6900A6E225 /* RNNLayoutInfo.h */; };
80 90
 		501CD320214A5B6900A6E225 /* RNNLayoutInfo.m in Sources */ = {isa = PBXBuildFile; fileRef = 501CD31E214A5B6900A6E225 /* RNNLayoutInfo.m */; };
81 91
 		501E0217213E7EA3003365C5 /* RNNReactView.h in Headers */ = {isa = PBXBuildFile; fileRef = 501E0215213E7EA3003365C5 /* RNNReactView.h */; };
@@ -86,6 +96,8 @@
86 96
 		502F0E142178CF8200367CC3 /* UIViewController+RNNOptionsTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 502F0E132178CF8200367CC3 /* UIViewController+RNNOptionsTest.m */; };
87 97
 		502F0E162178D09600367CC3 /* RNNBasePresenterTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 502F0E152178D09600367CC3 /* RNNBasePresenterTest.m */; };
88 98
 		502F0E182179C39900367CC3 /* RNNTabBarPresenterTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 502F0E172179C39900367CC3 /* RNNTabBarPresenterTest.m */; };
99
+		50344D2823A03DB4004B6A7C /* BottomTabsAttachMode.h in Headers */ = {isa = PBXBuildFile; fileRef = 50344D2623A03DB4004B6A7C /* BottomTabsAttachMode.h */; };
100
+		50344D2923A03DB4004B6A7C /* BottomTabsAttachMode.m in Sources */ = {isa = PBXBuildFile; fileRef = 50344D2723A03DB4004B6A7C /* BottomTabsAttachMode.m */; };
89 101
 		5038A374216CDDB6009280BC /* UIViewController+SideMenuController.h in Headers */ = {isa = PBXBuildFile; fileRef = 5038A372216CDDB6009280BC /* UIViewController+SideMenuController.h */; };
90 102
 		5038A375216CDDB6009280BC /* UIViewController+SideMenuController.m in Sources */ = {isa = PBXBuildFile; fileRef = 5038A373216CDDB6009280BC /* UIViewController+SideMenuController.m */; };
91 103
 		5038A377216CF252009280BC /* UITabBarController+RNNOptionsTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 5038A376216CF252009280BC /* UITabBarController+RNNOptionsTest.m */; };
@@ -215,6 +227,8 @@
215 227
 		50AB0B1C2255F8640039DAED /* UIViewController+LayoutProtocol.h in Headers */ = {isa = PBXBuildFile; fileRef = 50AB0B1A2255F8640039DAED /* UIViewController+LayoutProtocol.h */; };
216 228
 		50AB0B1D2255F8640039DAED /* UIViewController+LayoutProtocol.m in Sources */ = {isa = PBXBuildFile; fileRef = 50AB0B1B2255F8640039DAED /* UIViewController+LayoutProtocol.m */; };
217 229
 		50AB0B1F22562FA10039DAED /* UIViewController+LayoutProtocolTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 50AB0B1E22562FA10039DAED /* UIViewController+LayoutProtocolTest.m */; };
230
+		50BAFE4B2399405800798674 /* RNNExternalViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = 50BAFE492399405800798674 /* RNNExternalViewController.h */; };
231
+		50BAFE4C2399405800798674 /* RNNExternalViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 50BAFE4A2399405800798674 /* RNNExternalViewController.m */; };
218 232
 		50BE951220B5A787004F5DF5 /* RNNStatusBarOptions.m in Sources */ = {isa = PBXBuildFile; fileRef = 50BE951020B5A787004F5DF5 /* RNNStatusBarOptions.m */; };
219 233
 		50BE951320B5A787004F5DF5 /* RNNStatusBarOptions.h in Headers */ = {isa = PBXBuildFile; fileRef = 50BE951120B5A787004F5DF5 /* RNNStatusBarOptions.h */; };
220 234
 		50C4A496206BDDBB00DB292E /* RNNSubtitleOptions.h in Headers */ = {isa = PBXBuildFile; fileRef = 50C4A494206BDDBB00DB292E /* RNNSubtitleOptions.h */; };
@@ -433,6 +447,16 @@
433 447
 		5016E8EE2020968F009D4F7C /* RNNCustomTitleView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RNNCustomTitleView.m; sourceTree = "<group>"; };
434 448
 		50175CCF207A2AA1004FE91B /* RNNComponentOptions.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RNNComponentOptions.h; sourceTree = "<group>"; };
435 449
 		50175CD0207A2AA1004FE91B /* RNNComponentOptions.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = RNNComponentOptions.m; sourceTree = "<group>"; };
450
+		5017D9DF239D2C6C00B74047 /* BottomTabsAttachModeFactory.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = BottomTabsAttachModeFactory.h; sourceTree = "<group>"; };
451
+		5017D9E0239D2C6C00B74047 /* BottomTabsAttachModeFactory.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = BottomTabsAttachModeFactory.m; sourceTree = "<group>"; };
452
+		5017D9E4239D2D9E00B74047 /* BottomTabsBaseAttacher.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = BottomTabsBaseAttacher.h; sourceTree = "<group>"; };
453
+		5017D9E5239D2D9E00B74047 /* BottomTabsBaseAttacher.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = BottomTabsBaseAttacher.m; sourceTree = "<group>"; };
454
+		5017D9E8239D2F9D00B74047 /* BottomTabsTogetherAttacher.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = BottomTabsTogetherAttacher.h; sourceTree = "<group>"; };
455
+		5017D9E9239D2F9D00B74047 /* BottomTabsTogetherAttacher.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = BottomTabsTogetherAttacher.m; sourceTree = "<group>"; };
456
+		5017D9EC239D2FAF00B74047 /* BottomTabsAfterInitialTabAttacher.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = BottomTabsAfterInitialTabAttacher.h; sourceTree = "<group>"; };
457
+		5017D9ED239D2FAF00B74047 /* BottomTabsAfterInitialTabAttacher.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = BottomTabsAfterInitialTabAttacher.m; sourceTree = "<group>"; };
458
+		5017D9F0239D2FCB00B74047 /* BottomTabsOnSwitchToTabAttacher.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = BottomTabsOnSwitchToTabAttacher.h; sourceTree = "<group>"; };
459
+		5017D9F1239D2FCB00B74047 /* BottomTabsOnSwitchToTabAttacher.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = BottomTabsOnSwitchToTabAttacher.m; sourceTree = "<group>"; };
436 460
 		501CD31D214A5B6900A6E225 /* RNNLayoutInfo.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RNNLayoutInfo.h; sourceTree = "<group>"; };
437 461
 		501CD31E214A5B6900A6E225 /* RNNLayoutInfo.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = RNNLayoutInfo.m; sourceTree = "<group>"; };
438 462
 		501E0215213E7EA3003365C5 /* RNNReactView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RNNReactView.h; sourceTree = "<group>"; };
@@ -444,6 +468,8 @@
444 468
 		502F0E132178CF8200367CC3 /* UIViewController+RNNOptionsTest.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "UIViewController+RNNOptionsTest.m"; sourceTree = "<group>"; };
445 469
 		502F0E152178D09600367CC3 /* RNNBasePresenterTest.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = RNNBasePresenterTest.m; sourceTree = "<group>"; };
446 470
 		502F0E172179C39900367CC3 /* RNNTabBarPresenterTest.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = RNNTabBarPresenterTest.m; sourceTree = "<group>"; };
471
+		50344D2623A03DB4004B6A7C /* BottomTabsAttachMode.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = BottomTabsAttachMode.h; sourceTree = "<group>"; };
472
+		50344D2723A03DB4004B6A7C /* BottomTabsAttachMode.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = BottomTabsAttachMode.m; sourceTree = "<group>"; };
447 473
 		5038A372216CDDB6009280BC /* UIViewController+SideMenuController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "UIViewController+SideMenuController.h"; sourceTree = "<group>"; };
448 474
 		5038A373216CDDB6009280BC /* UIViewController+SideMenuController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "UIViewController+SideMenuController.m"; sourceTree = "<group>"; };
449 475
 		5038A376216CF252009280BC /* UITabBarController+RNNOptionsTest.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "UITabBarController+RNNOptionsTest.m"; sourceTree = "<group>"; };
@@ -571,6 +597,8 @@
571 597
 		50AB0B1A2255F8640039DAED /* UIViewController+LayoutProtocol.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "UIViewController+LayoutProtocol.h"; sourceTree = "<group>"; };
572 598
 		50AB0B1B2255F8640039DAED /* UIViewController+LayoutProtocol.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "UIViewController+LayoutProtocol.m"; sourceTree = "<group>"; };
573 599
 		50AB0B1E22562FA10039DAED /* UIViewController+LayoutProtocolTest.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "UIViewController+LayoutProtocolTest.m"; sourceTree = "<group>"; };
600
+		50BAFE492399405800798674 /* RNNExternalViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RNNExternalViewController.h; sourceTree = "<group>"; };
601
+		50BAFE4A2399405800798674 /* RNNExternalViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = RNNExternalViewController.m; sourceTree = "<group>"; };
574 602
 		50BE951020B5A787004F5DF5 /* RNNStatusBarOptions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RNNStatusBarOptions.m; sourceTree = "<group>"; };
575 603
 		50BE951120B5A787004F5DF5 /* RNNStatusBarOptions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RNNStatusBarOptions.h; sourceTree = "<group>"; };
576 604
 		50C4A494206BDDBB00DB292E /* RNNSubtitleOptions.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RNNSubtitleOptions.h; sourceTree = "<group>"; };
@@ -829,10 +857,37 @@
829 857
 				505EDD47214FC4A60071C7DE /* RNNLayoutProtocol.h */,
830 858
 				50AB0B1A2255F8640039DAED /* UIViewController+LayoutProtocol.h */,
831 859
 				50AB0B1B2255F8640039DAED /* UIViewController+LayoutProtocol.m */,
860
+				26916C941E4B9CCC00D13680 /* RNNComponentViewCreator.h */,
832 861
 			);
833 862
 			name = Protocols;
834 863
 			sourceTree = "<group>";
835 864
 		};
865
+		5017D9DE239D2C1300B74047 /* Factories */ = {
866
+			isa = PBXGroup;
867
+			children = (
868
+				7BC9346C1E26886E00EFA125 /* RNNControllerFactory.h */,
869
+				7BC9346D1E26886E00EFA125 /* RNNControllerFactory.m */,
870
+				5017D9DF239D2C6C00B74047 /* BottomTabsAttachModeFactory.h */,
871
+				5017D9E0239D2C6C00B74047 /* BottomTabsAttachModeFactory.m */,
872
+			);
873
+			name = Factories;
874
+			sourceTree = "<group>";
875
+		};
876
+		5017D9E3239D2CC300B74047 /* Attachers */ = {
877
+			isa = PBXGroup;
878
+			children = (
879
+				5017D9E4239D2D9E00B74047 /* BottomTabsBaseAttacher.h */,
880
+				5017D9E5239D2D9E00B74047 /* BottomTabsBaseAttacher.m */,
881
+				5017D9E8239D2F9D00B74047 /* BottomTabsTogetherAttacher.h */,
882
+				5017D9E9239D2F9D00B74047 /* BottomTabsTogetherAttacher.m */,
883
+				5017D9EC239D2FAF00B74047 /* BottomTabsAfterInitialTabAttacher.h */,
884
+				5017D9ED239D2FAF00B74047 /* BottomTabsAfterInitialTabAttacher.m */,
885
+				5017D9F0239D2FCB00B74047 /* BottomTabsOnSwitchToTabAttacher.h */,
886
+				5017D9F1239D2FCB00B74047 /* BottomTabsOnSwitchToTabAttacher.m */,
887
+			);
888
+			name = Attachers;
889
+			sourceTree = "<group>";
890
+		};
836 891
 		5038A3C3216E2D74009280BC /* Params */ = {
837 892
 			isa = PBXGroup;
838 893
 			children = (
@@ -875,6 +930,8 @@
875 930
 				503955962174864E00B0A663 /* NullDouble.m */,
876 931
 				309877ADF638DF25FF0DA8A1 /* NoColor.m */,
877 932
 				309874C5B132A51A03DAA3BF /* NoColor.h */,
933
+				50344D2623A03DB4004B6A7C /* BottomTabsAttachMode.h */,
934
+				50344D2723A03DB4004B6A7C /* BottomTabsAttachMode.m */,
878 935
 			);
879 936
 			name = Params;
880 937
 			sourceTree = "<group>";
@@ -1003,6 +1060,17 @@
1003 1060
 			name = HMSegmentControl;
1004 1061
 			sourceTree = "<group>";
1005 1062
 		};
1063
+		50BAFE482399403200798674 /* Child View Controllers */ = {
1064
+			isa = PBXGroup;
1065
+			children = (
1066
+				7BEF0D161E437684003E96B0 /* RNNComponentViewController.h */,
1067
+				7BEF0D171E437684003E96B0 /* RNNComponentViewController.m */,
1068
+				50BAFE492399405800798674 /* RNNExternalViewController.h */,
1069
+				50BAFE4A2399405800798674 /* RNNExternalViewController.m */,
1070
+			);
1071
+			name = "Child View Controllers";
1072
+			sourceTree = "<group>";
1073
+		};
1006 1074
 		50D031312005146C00386B3D /* Managers */ = {
1007 1075
 			isa = PBXGroup;
1008 1076
 			children = (
@@ -1031,23 +1099,19 @@
1031 1099
 				505EDD39214FA7E80071C7DE /* Presenters */,
1032 1100
 				5012242C2173E0A4000F5F98 /* Parent ViewControllers */,
1033 1101
 				5012242D2173E0E0000F5F98 /* Protocols */,
1102
+				50BAFE482399403200798674 /* Child View Controllers */,
1034 1103
 				50570BE82063E09B006A1B5C /* RNNTitleViewHelper.h */,
1104
+				50570BE92063E09B006A1B5C /* RNNTitleViewHelper.m */,
1035 1105
 				501CD31D214A5B6900A6E225 /* RNNLayoutInfo.h */,
1036 1106
 				501CD31E214A5B6900A6E225 /* RNNLayoutInfo.m */,
1037 1107
 				501E0215213E7EA3003365C5 /* RNNReactView.h */,
1038 1108
 				501E0216213E7EA3003365C5 /* RNNReactView.m */,
1039
-				50570BE92063E09B006A1B5C /* RNNTitleViewHelper.m */,
1040
-				26916C941E4B9CCC00D13680 /* RNNComponentViewCreator.h */,
1041 1109
 				26916C961E4B9E7700D13680 /* RNNReactRootViewCreator.h */,
1042 1110
 				26916C971E4B9E7700D13680 /* RNNReactRootViewCreator.m */,
1043 1111
 				7BA500761E254908001B9E1B /* RNNSplashScreen.h */,
1044 1112
 				7BA500771E254908001B9E1B /* RNNSplashScreen.m */,
1045 1113
 				7BEF0D1A1E43771B003E96B0 /* RNNLayoutNode.h */,
1046 1114
 				7BEF0D1B1E43771B003E96B0 /* RNNLayoutNode.m */,
1047
-				7BEF0D161E437684003E96B0 /* RNNComponentViewController.h */,
1048
-				7BEF0D171E437684003E96B0 /* RNNComponentViewController.m */,
1049
-				7BC9346C1E26886E00EFA125 /* RNNControllerFactory.h */,
1050
-				7BC9346D1E26886E00EFA125 /* RNNControllerFactory.m */,
1051 1115
 				21B85E5E1F44482A00B314B5 /* RNNNavigationButtons.h */,
1052 1116
 				21B85E5C1F44480200B314B5 /* RNNNavigationButtons.m */,
1053 1117
 				214545261F4DC164006E8DA1 /* RNNUIBarButtonItem.h */,
@@ -1136,6 +1200,8 @@
1136 1200
 			children = (
1137 1201
 				E5F6C39B22DB4CB90093C2CE /* Utils */,
1138 1202
 				214545271F4DC7ED006E8DA1 /* Helpers */,
1203
+				5017D9DE239D2C1300B74047 /* Factories */,
1204
+				5017D9E3239D2CC300B74047 /* Attachers */,
1139 1205
 				7BA500731E2544B9001B9E1B /* ReactNativeNavigation.h */,
1140 1206
 				7BA500741E2544B9001B9E1B /* ReactNativeNavigation.m */,
1141 1207
 				2DCD9193200014A900EDC75D /* RNNBridgeManager.h */,
@@ -1314,6 +1380,7 @@
1314 1380
 				7B1126A31E2D2B6C00F9B03B /* RNNSplashScreen.h in Headers */,
1315 1381
 				5038A3D2216E364C009280BC /* Text.h in Headers */,
1316 1382
 				261F0E641E6EC94900989DE2 /* RNNModalManager.h in Headers */,
1383
+				50344D2823A03DB4004B6A7C /* BottomTabsAttachMode.h in Headers */,
1317 1384
 				5012242621737278000F5F98 /* NullImage.h in Headers */,
1318 1385
 				5038A3B9216DFCFD009280BC /* UITabBarController+RNNOptions.h in Headers */,
1319 1386
 				50644A2020E11A720026709C /* Constants.h in Headers */,
@@ -1323,6 +1390,7 @@
1323 1390
 				E8367B801F7A8A4700675C05 /* VICMAImageView.h in Headers */,
1324 1391
 				263905E61E4CAC950023D7D3 /* RNNSideMenuChildVC.h in Headers */,
1325 1392
 				50F5DFC51F407AA0001A00BC /* RNNStackController.h in Headers */,
1393
+				50BAFE4B2399405800798674 /* RNNExternalViewController.h in Headers */,
1326 1394
 				5047E4F42267568800908DD3 /* RNNExternalComponentStore.h in Headers */,
1327 1395
 				21B85E5F1F44482A00B314B5 /* RNNNavigationButtons.h in Headers */,
1328 1396
 				7BEF0D181E437684003E96B0 /* RNNComponentViewController.h in Headers */,
@@ -1333,6 +1401,7 @@
1333 1401
 				50E5F7952240EBD6002AFEAD /* RNNAnimationsTransitionDelegate.h in Headers */,
1334 1402
 				7B1126A71E2D2B6C00F9B03B /* RNNEventEmitter.h in Headers */,
1335 1403
 				E8A430111F9CB87B00B61A20 /* RNNAnimatedView.h in Headers */,
1404
+				5017D9E1239D2C6C00B74047 /* BottomTabsAttachModeFactory.h in Headers */,
1336 1405
 				E5F6C3AC22DB4D0F0093C2CE /* UITabBarController+RNNUtils.h in Headers */,
1337 1406
 				506317AA220B547400B26FC3 /* UIImage+insets.h in Headers */,
1338 1407
 				5038A3C6216E2D93009280BC /* Number.h in Headers */,
@@ -1349,6 +1418,7 @@
1349 1418
 				50BE951320B5A787004F5DF5 /* RNNStatusBarOptions.h in Headers */,
1350 1419
 				50570BEA2063E09B006A1B5C /* RNNTitleViewHelper.h in Headers */,
1351 1420
 				50495956216F6B3D006D2B81 /* DictionaryParser.h in Headers */,
1421
+				5017D9E6239D2D9E00B74047 /* BottomTabsBaseAttacher.h in Headers */,
1352 1422
 				4534E72520CB6724009F8185 /* RNNLargeTitleOptions.h in Headers */,
1353 1423
 				390AD477200F499D00A8250D /* RNNSwizzles.h in Headers */,
1354 1424
 				263905B11E4C6F440023D7D3 /* MMDrawerController.h in Headers */,
@@ -1361,6 +1431,7 @@
1361 1431
 				50495952216F62BD006D2B81 /* NullNumber.h in Headers */,
1362 1432
 				5012242221736883000F5F98 /* NullColor.h in Headers */,
1363 1433
 				50570B262061473D006A1B5C /* RNNTitleOptions.h in Headers */,
1434
+				5017D9EE239D2FAF00B74047 /* BottomTabsAfterInitialTabAttacher.h in Headers */,
1364 1435
 				50395593217485B000B0A663 /* Double.h in Headers */,
1365 1436
 				5050465421F8F4490035497A /* RNNReactComponentRegistry.h in Headers */,
1366 1437
 				504AFE741FFFF0540076E904 /* RNNTopTabsOptions.h in Headers */,
@@ -1389,6 +1460,7 @@
1389 1460
 				505EDD4A214FDA800071C7DE /* RCTConvert+Modal.h in Headers */,
1390 1461
 				5049595A216F6B46006D2B81 /* NullDictionary.h in Headers */,
1391 1462
 				501224062173592D000F5F98 /* RNNBottomTabsPresenter.h in Headers */,
1463
+				5017D9F2239D2FCB00B74047 /* BottomTabsOnSwitchToTabAttacher.h in Headers */,
1392 1464
 				50706E6D20CE7CA5003345C3 /* UIImage+tint.h in Headers */,
1393 1465
 				50A246372395399700A192C5 /* RNNModalOptions.h in Headers */,
1394 1466
 				309874B40D202C9718F15CBD /* UIView+Utils.h in Headers */,
@@ -1398,6 +1470,7 @@
1398 1470
 				3098730BC3B4DE41104D9CC4 /* RNNDotIndicatorPresenter.h in Headers */,
1399 1471
 				309877F473AECC05FB3B9362 /* UITabBarController+RNNUtils.h in Headers */,
1400 1472
 				3098702E6833E5CC16D91CE3 /* NoColor.h in Headers */,
1473
+				5017D9EA239D2F9D00B74047 /* BottomTabsTogetherAttacher.h in Headers */,
1401 1474
 			);
1402 1475
 			runOnlyForDeploymentPostprocessing = 0;
1403 1476
 		};
@@ -1615,24 +1688,29 @@
1615 1688
 				507F43CA1FF4F9CC00D9425B /* RNNTopTabOptions.m in Sources */,
1616 1689
 				26916C991E4B9E7700D13680 /* RNNReactRootViewCreator.m in Sources */,
1617 1690
 				5064495E20DC62B90026709C /* RNNSideMenuSideOptions.m in Sources */,
1691
+				5017D9EB239D2F9D00B74047 /* BottomTabsTogetherAttacher.m in Sources */,
1618 1692
 				E5F6C3AD22DB4D0F0093C2CE /* UITabBarController+RNNUtils.m in Sources */,
1619 1693
 				214545251F4DC125006E8DA1 /* RNNUIBarButtonItem.m in Sources */,
1620 1694
 				263905B81E4C6F440023D7D3 /* UIViewController+MMDrawerController.m in Sources */,
1621 1695
 				505EDD3D214FA8000071C7DE /* RNNComponentPresenter.m in Sources */,
1622 1696
 				E33AC20820B5C4F90090DB8A /* RNNSplitViewOptions.m in Sources */,
1623 1697
 				E33AC20020B5BA0B0090DB8A /* RNNSplitViewController.m in Sources */,
1698
+				50BAFE4C2399405800798674 /* RNNExternalViewController.m in Sources */,
1624 1699
 				5038A3C2216E1E66009280BC /* RNNFontAttributesCreator.m in Sources */,
1625 1700
 				506317AB220B547400B26FC3 /* UIImage+insets.m in Sources */,
1626 1701
 				E8A430121F9CB87B00B61A20 /* RNNAnimatedView.m in Sources */,
1627 1702
 				507F43F51FF4FCFE00D9425B /* HMSegmentedControl.m in Sources */,
1703
+				5017D9E7239D2D9E00B74047 /* BottomTabsBaseAttacher.m in Sources */,
1628 1704
 				5012242321736883000F5F98 /* NullColor.m in Sources */,
1629 1705
 				50451D0A2042E20600695F00 /* RNNAnimationsOptions.m in Sources */,
1630 1706
 				5039558C2174829400B0A663 /* IntNumberParser.m in Sources */,
1631 1707
 				507F43C61FF4F17C00D9425B /* RNNTopTabsViewController.m in Sources */,
1632 1708
 				50706E6E20CE7CA5003345C3 /* UIImage+tint.m in Sources */,
1709
+				50344D2923A03DB4004B6A7C /* BottomTabsAttachMode.m in Sources */,
1633 1710
 				501224072173592D000F5F98 /* RNNBottomTabsPresenter.m in Sources */,
1634 1711
 				50A00C38200F84D6000F01A6 /* RNNOverlayOptions.m in Sources */,
1635 1712
 				5039559C2174867000B0A663 /* DoubleParser.m in Sources */,
1713
+				5017D9EF239D2FAF00B74047 /* BottomTabsAfterInitialTabAttacher.m in Sources */,
1636 1714
 				5008641223856A2D00A55BE9 /* UITabBar+utils.m in Sources */,
1637 1715
 				E5F6C3A822DB4D0F0093C2CE /* UIView+Utils.m in Sources */,
1638 1716
 				5049593F216F5D73006D2B81 /* BoolParser.m in Sources */,
@@ -1656,6 +1734,7 @@
1656 1734
 				E8E5182F1F83A48B000467AC /* RNNTransitionStateHolder.m in Sources */,
1657 1735
 				263905B61E4C6F440023D7D3 /* MMExampleDrawerVisualStateManager.m in Sources */,
1658 1736
 				5038A3D3216E364C009280BC /* Text.m in Sources */,
1737
+				5017D9E2239D2C6C00B74047 /* BottomTabsAttachModeFactory.m in Sources */,
1659 1738
 				5012240F21735999000F5F98 /* RNNBasePresenter.m in Sources */,
1660 1739
 				5038A375216CDDB6009280BC /* UIViewController+SideMenuController.m in Sources */,
1661 1740
 				E8E518331F83B3E0000467AC /* RNNUtils.m in Sources */,
@@ -1669,6 +1748,7 @@
1669 1748
 				30987AB5137F264FA06DA289 /* DotIndicatorOptions.m in Sources */,
1670 1749
 				30987D71FB4FEEAC8D8978E8 /* DotIndicatorParser.m in Sources */,
1671 1750
 				30987B23F288EB3A78B7F27C /* RNNDotIndicatorPresenter.m in Sources */,
1751
+				5017D9F3239D2FCB00B74047 /* BottomTabsOnSwitchToTabAttacher.m in Sources */,
1672 1752
 				309878CC9D33CE1CF991EBD1 /* NoColor.m in Sources */,
1673 1753
 			);
1674 1754
 			runOnlyForDeploymentPostprocessing = 0;

+ 9
- 1
lib/ios/UIViewController+LayoutProtocol.h View File

@@ -6,25 +6,33 @@ typedef void (^RNNReactViewReadyCompletionBlock)(void);
6 6
 
7 7
 @interface UIViewController (LayoutProtocol) <RNNLayoutProtocol>
8 8
 
9
-- (void)renderTreeAndWait:(BOOL)wait perform:(RNNReactViewReadyCompletionBlock)readyBlock;
9
+- (void)render;
10 10
 
11 11
 - (UIViewController *)getCurrentChild;
12 12
 
13
+- (UIViewController *)presentedComponentViewController;
14
+
13 15
 - (void)mergeOptions:(RNNNavigationOptions *)options;
14 16
 
15 17
 - (void)mergeChildOptions:(RNNNavigationOptions *)options;
16 18
 
17 19
 - (RNNNavigationOptions *)resolveOptions;
18 20
 
21
+- (RNNNavigationOptions *)resolveOptionsWithDefault;
22
+
19 23
 - (void)setDefaultOptions:(RNNNavigationOptions *)defaultOptions;
20 24
 
21 25
 - (void)overrideOptions:(RNNNavigationOptions *)options;
22 26
 
27
+- (void)readyForPresentation;
28
+
23 29
 @property (nonatomic, retain) RNNBasePresenter* presenter;
24 30
 @property (nonatomic, retain) RNNLayoutInfo* layoutInfo;
25 31
 @property (nonatomic, strong) RNNNavigationOptions* options;
26 32
 @property (nonatomic, strong) RNNNavigationOptions* defaultOptions;
27 33
 @property (nonatomic, strong) RNNEventEmitter* eventEmitter;
28 34
 @property (nonatomic) id<RNNComponentViewCreator> creator;
35
+@property (nonatomic) RNNReactViewReadyCompletionBlock reactViewReadyCallback;
36
+@property (nonatomic) BOOL waitForRender;
29 37
 
30 38
 @end

+ 44
- 23
lib/ios/UIViewController+LayoutProtocol.m View File

@@ -43,6 +43,10 @@
43 43
     return (RNNNavigationOptions *) [self.options mergeInOptions:self.getCurrentChild.resolveOptions.copy];
44 44
 }
45 45
 
46
+- (RNNNavigationOptions *)resolveOptionsWithDefault {
47
+    return [(RNNNavigationOptions *) [self.options mergeInOptions:self.getCurrentChild.resolveOptions.copy] withDefault:self.defaultOptions];
48
+}
49
+
46 50
 - (void)overrideOptions:(RNNNavigationOptions *)options {
47 51
 	[self.options overrideOptions:options];
48 52
 }
@@ -52,32 +56,35 @@
52 56
 	return interfaceOrientationMask;
53 57
 }
54 58
 
55
-- (void)renderTreeAndWait:(BOOL)wait perform:(RNNReactViewReadyCompletionBlock)readyBlock {
56
-	dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
57
-		dispatch_group_t group = dispatch_group_create();
58
-		for (UIViewController* childViewController in self.childViewControllers) {
59
-			dispatch_group_enter(group);
60
-			dispatch_async(dispatch_get_main_queue(), ^{
61
-				[childViewController renderTreeAndWait:wait perform:^{
62
-					dispatch_group_leave(group);
63
-				}];
64
-			});
65
-		}
66
-		
67
-		dispatch_group_enter(group);
68
-		[self.presenter renderComponents:self.resolveOptions perform:^{
69
-			dispatch_group_leave(group);
70
-		}];
71
-		dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
72
-		
73
-		dispatch_async(dispatch_get_main_queue(), ^{
74
-			readyBlock();
75
-		});
76
-	});
59
+- (void)render {
60
+    if (!self.waitForRender) {
61
+        [self readyForPresentation];
62
+    }
63
+    
64
+    [self.presentedComponentViewController setReactViewReadyCallback:^{
65
+        [self.presenter renderComponents:self.resolveOptionsWithDefault perform:^{
66
+            [self readyForPresentation];
67
+        }];
68
+    }];
69
+    
70
+    [self.presentedComponentViewController render];
71
+}
72
+
73
+- (void)readyForPresentation {
74
+    if (self.reactViewReadyCallback) {
75
+        self.reactViewReadyCallback();
76
+        self.reactViewReadyCallback = nil;
77
+    }
78
+    
79
+    [self.parentViewController readyForPresentation];
77 80
 }
78 81
 
79 82
 - (UIViewController *)getCurrentChild {
80
-	return nil;
83
+    return nil;
84
+}
85
+
86
+- (UIViewController *)presentedComponentViewController {
87
+    return self.getCurrentChild ? self.getCurrentChild.presentedComponentViewController : self;
81 88
 }
82 89
 
83 90
 - (CGFloat)getTopBarHeight {
@@ -159,6 +166,20 @@
159 166
 	objc_setAssociatedObject(self, @selector(creator), creator, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
160 167
 }
161 168
 
169
+- (RNNReactViewReadyCompletionBlock)reactViewReadyCallback {
170
+    return objc_getAssociatedObject(self, @selector(reactViewReadyCallback));
171
+}
172
+
173
+- (void)setReactViewReadyCallback:(RNNReactViewReadyCompletionBlock)reactViewReadyCallback {
174
+    objc_setAssociatedObject(self, @selector(reactViewReadyCallback), reactViewReadyCallback, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
175
+}
176
+
177
+- (BOOL)waitForRender {
178
+    return [objc_getAssociatedObject(self.parentViewController ?: self, @selector(waitForRender)) boolValue];
179
+}
162 180
 
181
+- (void)setWaitForRender:(BOOL)waitForRender {
182
+    objc_setAssociatedObject(self, @selector(waitForRender), [NSNumber numberWithBool:waitForRender], OBJC_ASSOCIATION_RETAIN_NONATOMIC);
183
+}
163 184
 
164 185
 @end

+ 5
- 1
lib/ios/Utils/UITabBarController+RNNUtils.h View File

@@ -2,7 +2,11 @@
2 2
 #import <UIKit/UIKit.h>
3 3
 
4 4
 @interface UITabBarController (RNNUtils)
5
+
5 6
 - (UIView *)getTabView:(int)tabIndex;
6 7
 
7 8
 - (UIView *)getTabIcon:(int)tabIndex;
8
-@end
9
+
10
+- (NSArray *)deselectedViewControllers;
11
+
12
+@end

+ 8
- 1
lib/ios/Utils/UITabBarController+RNNUtils.m View File

@@ -18,4 +18,11 @@
18 18
     UIView *tab = [self getTabView:tabIndex];
19 19
     return [tab findChildByClass:[UIImageView class]];
20 20
 }
21
-@end
21
+
22
+- (NSArray *)deselectedViewControllers {
23
+    NSMutableArray* childViewControllers = [NSMutableArray arrayWithArray:self.childViewControllers];
24
+    [childViewControllers removeObject:self.selectedViewController];
25
+    return [NSArray arrayWithArray:childViewControllers];
26
+}
27
+
28
+@end

+ 74
- 13
playground/ios/NavigationTests/RNNCommandsHandlerTest.m View File

@@ -8,6 +8,8 @@
8 8
 #import <ReactNativeNavigation/RNNErrorHandler.h>
9 9
 #import <OCMock/OCMock.h>
10 10
 #import "RNNLayoutManager.h"
11
+#import "RNNBottomTabsController.h"
12
+#import "BottomTabsAttachModeFactory.h"
11 13
 
12 14
 @interface MockUIApplication : NSObject
13 15
 
@@ -63,16 +65,19 @@
63 65
 	self.eventEmmiter = [OCMockObject partialMockForObject:[RNNEventEmitter new]];
64 66
 	self.overlayManager = [OCMockObject partialMockForObject:[RNNOverlayManager new]];
65 67
 	self.modalManager = [OCMockObject partialMockForObject:[RNNModalManager new]];
66
-	self.controllerFactory = [OCMockObject partialMockForObject:[[RNNControllerFactory alloc] initWithRootViewCreator:nil eventEmitter:self.eventEmmiter store:nil componentRegistry:nil andBridge:nil]];
68
+	self.controllerFactory = [OCMockObject partialMockForObject:[[RNNControllerFactory alloc] initWithRootViewCreator:nil eventEmitter:self.eventEmmiter store:nil componentRegistry:nil andBridge:nil bottomTabsAttachModeFactory:[BottomTabsAttachModeFactory new]]];
67 69
 	self.uut = [[RNNCommandsHandler alloc] initWithControllerFactory:self.controllerFactory eventEmitter:self.eventEmmiter stackManager:[RNNNavigationStackManager new] modalManager:self.modalManager overlayManager:self.overlayManager mainWindow:_mainWindow];
68
-	self.vc1 = [RNNComponentViewController new];
69
-	self.vc2 = [RNNComponentViewController new];
70
-	self.vc3 = [RNNComponentViewController new];
70
+	self.vc1 = self.generateComponent;
71
+	self.vc2 = self.generateComponent;
72
+	self.vc3 = self.generateComponent;
71 73
 	_nvc = [[MockUINavigationController alloc] init];
72 74
 	[_nvc setViewControllers:@[self.vc1, self.vc2, self.vc3]];
73 75
 	OCMStub([self.sharedApplication keyWindow]).andReturn(self.mainWindow);
74 76
 }
75 77
 
78
+- (RNNComponentViewController *)generateComponent {
79
+	return [[RNNComponentViewController alloc] initWithLayoutInfo:nil rootViewCreator:[[RNNTestRootViewCreator alloc] init] eventEmitter:nil presenter:[RNNComponentPresenter new] options:[[RNNNavigationOptions alloc] initWithDict:@{}] defaultOptions:nil];
80
+}
76 81
 
77 82
 - (void)testAssertReadyForEachMethodThrowsExceptoins {
78 83
 	NSArray* methods = [self getPublicMethodNamesForObject:self.uut];
@@ -182,7 +187,7 @@
182 187
 
183 188
 - (void)testShowOverlay_withCreatedLayout {
184 189
 	[self.uut setReadyToReceiveCommands:true];
185
-	UIViewController* layoutVC = [RNNComponentViewController new];
190
+	UIViewController* layoutVC = self.generateComponent;
186 191
 	OCMStub([self.controllerFactory createLayout:[OCMArg any]]).andReturn(layoutVC);
187 192
 	
188 193
 	[[self.overlayManager expect] showOverlayWindow:[OCMArg any]];
@@ -264,9 +269,10 @@
264 269
 	[self.uut setReadyToReceiveCommands:true];
265 270
 	id classMock = OCMClassMock([RNNLayoutManager class]);
266 271
 	OCMStub(ClassMethod([classMock findComponentForId:@"vc1"])).andReturn(_nvc);
272
+	self.vc2.options.animations.setStackRoot.enable = [[Bool alloc] initWithBOOL:NO];
267 273
 	
268 274
 	[self.uut setStackRoot:@"vc1" commandId:@"" children:nil completion:^{
269
-		
275
+
270 276
 	} rejection:^(NSString *code, NSString *message, NSError *error) {
271 277
 		
272 278
 	}];
@@ -281,8 +287,10 @@
281 287
 	OCMStub(ClassMethod([classMock findComponentForId:@"vc1"])).andReturn(_nvc);
282 288
 	OCMStub([self.controllerFactory createChildrenLayout:[OCMArg any]]).andReturn(newViewControllers);
283 289
 	[self.uut setReadyToReceiveCommands:true];
290
+	
291
+	_vc3.options.animations.setStackRoot.enable = [[Bool alloc] initWithBOOL:NO];
284 292
 	[self.uut setStackRoot:@"vc1" commandId:@"" children:nil completion:^{
285
-		
293
+	
286 294
 	} rejection:^(NSString *code, NSString *message, NSError *error) {
287 295
 		
288 296
 	}];
@@ -302,8 +310,8 @@
302 310
 		
303 311
 	}];
304 312
 	
305
-	[[vc1Mock expect] renderTreeAndWait:NO perform:[OCMArg any]];
306
-	[[vc2Mock expect] renderTreeAndWait:NO perform:[OCMArg any]];
313
+	[[vc1Mock expect] render];
314
+	[[vc2Mock expect] render];
307 315
 }
308 316
 
309 317
 - (void)testSetStackRoot_waitForRender {
@@ -322,8 +330,8 @@
322 330
 		
323 331
 	}];
324 332
 	
325
-	[[vc1Mock expect] renderTreeAndWait:NO perform:[OCMArg any]];
326
-	[[vc2Mock expect] renderTreeAndWait:YES perform:[OCMArg any]];
333
+	[[vc1Mock expect] render];
334
+	[[vc2Mock expect] render];
327 335
 }
328 336
 
329 337
 - (void)testSetRoot_waitForRenderTrue {
@@ -334,7 +342,7 @@
334 342
 	id mockedVC = [OCMockObject partialMockForObject:self.vc1];
335 343
 	OCMStub([self.controllerFactory createLayout:[OCMArg any]]).andReturn(mockedVC);
336 344
 	
337
-	[[mockedVC expect] renderTreeAndWait:YES perform:[OCMArg any]];
345
+	[[mockedVC expect] render];
338 346
 	[self.uut setRoot:@{} commandId:@"" completion:^{}];
339 347
 	[mockedVC verify];
340 348
 }
@@ -347,11 +355,64 @@
347 355
 	id mockedVC = [OCMockObject partialMockForObject:self.vc1];
348 356
 	OCMStub([self.controllerFactory createLayout:[OCMArg any]]).andReturn(mockedVC);
349 357
 	
350
-	[[mockedVC expect] renderTreeAndWait:NO perform:[OCMArg any]];
358
+	[[mockedVC expect] render];
351 359
 	[self.uut setRoot:@{} commandId:@"" completion:^{}];
352 360
 	[mockedVC verify];
353 361
 }
354 362
 
363
+- (void)testSetRoot_withBottomTabsAttachModeTogether {
364
+	[self.uut setReadyToReceiveCommands:true];
365
+	RNNNavigationOptions* options = [[RNNNavigationOptions alloc] initEmptyOptions];
366
+	options.bottomTabs.tabsAttachMode = [[BottomTabsAttachMode alloc] initWithValue:@"together"];
367
+	options.animations.setRoot.waitForRender = [[Bool alloc] initWithBOOL:YES];
368
+
369
+	BottomTabsBaseAttacher* attacher = [[[BottomTabsAttachModeFactory alloc] initWithDefaultOptions:nil] fromOptions:options];
370
+	RNNBottomTabsController* tabBarController = [[RNNBottomTabsController alloc] initWithLayoutInfo:nil creator:nil options:options defaultOptions:[[RNNNavigationOptions alloc] initEmptyOptions] presenter:[RNNBasePresenter new] eventEmitter:_eventEmmiter childViewControllers:@[_vc1, _vc2] bottomTabsAttacher:attacher];
371
+
372
+	OCMStub([self.controllerFactory createLayout:[OCMArg any]]).andReturn(tabBarController);
373
+	
374
+	[self.uut setRoot:@{} commandId:@"" completion:^{}];
375
+	XCTAssertTrue(_vc1.isViewLoaded);
376
+	XCTAssertTrue(_vc2.isViewLoaded);
377
+}
378
+
379
+- (void)testSetRoot_withBottomTabsAttachModeOnSwitchToTab {
380
+	[self.uut setReadyToReceiveCommands:true];
381
+	RNNNavigationOptions* options = [[RNNNavigationOptions alloc] initEmptyOptions];
382
+	options.bottomTabs.tabsAttachMode = [[BottomTabsAttachMode alloc] initWithValue:@"onSwitchToTab"];
383
+	options.animations.setRoot.waitForRender = [[Bool alloc] initWithBOOL:YES];
384
+	
385
+	BottomTabsBaseAttacher* attacher = [[[BottomTabsAttachModeFactory alloc] initWithDefaultOptions:nil] fromOptions:options];
386
+	RNNBottomTabsController* tabBarController = [[RNNBottomTabsController alloc] initWithLayoutInfo:nil creator:nil options:options defaultOptions:[[RNNNavigationOptions alloc] initEmptyOptions] presenter:[RNNBasePresenter new] eventEmitter:_eventEmmiter childViewControllers:@[_vc1, _vc2] bottomTabsAttacher:attacher];
387
+
388
+	OCMStub([self.controllerFactory createLayout:[OCMArg any]]).andReturn(tabBarController);
389
+	
390
+	[self.uut setRoot:@{} commandId:@"" completion:^{}];
391
+	XCTAssertTrue(_vc1.isViewLoaded);
392
+	XCTAssertFalse(_vc2.isViewLoaded);
393
+	[tabBarController setSelectedIndex:1];
394
+	XCTAssertTrue(_vc2.isViewLoaded);
395
+}
396
+
397
+- (void)testSetRoot_withBottomTabsAttachModeAfterInitialTab {
398
+	[self.uut setReadyToReceiveCommands:true];
399
+	RNNNavigationOptions* options = [[RNNNavigationOptions alloc] initEmptyOptions];
400
+	options.bottomTabs.tabsAttachMode = [[BottomTabsAttachMode alloc] initWithValue:@"afterInitialTab"];
401
+	options.animations.setRoot.waitForRender = [[Bool alloc] initWithBOOL:YES];
402
+
403
+	BottomTabsBaseAttacher* attacher = [[[BottomTabsAttachModeFactory alloc] initWithDefaultOptions:nil] fromOptions:options];
404
+	RNNBottomTabsController* tabBarController = [[RNNBottomTabsController alloc] initWithLayoutInfo:nil creator:nil options:options defaultOptions:[[RNNNavigationOptions alloc] initEmptyOptions] presenter:[RNNBasePresenter new] eventEmitter:_eventEmmiter childViewControllers:@[_vc1, _vc2] bottomTabsAttacher:attacher];
405
+
406
+	OCMStub([self.controllerFactory createLayout:[OCMArg any]]).andReturn(tabBarController);
407
+
408
+	[self.uut setRoot:@{} commandId:@"" completion:^{
409
+		XCTAssertFalse(self->_vc2.isViewLoaded);
410
+	}];
411
+
412
+	XCTAssertTrue(_vc1.isViewLoaded);
413
+	XCTAssertTrue(_vc2.isViewLoaded);
414
+}
415
+
355 416
 - (void)testShowModal_shouldShowAnimated {
356 417
 	[self.uut setReadyToReceiveCommands:true];
357 418
 	self.vc1.options = [[RNNNavigationOptions alloc] initEmptyOptions];

+ 4
- 2
playground/ios/NavigationTests/RNNControllerFactoryTest.m View File

@@ -8,6 +8,7 @@
8 8
 #import "RNNBottomTabsController.h"
9 9
 #import "RNNTopTabsViewController.h"
10 10
 #import "RNNSplitViewController.h"
11
+#import "RNNExternalViewController.h"
11 12
 
12 13
 @interface RNNControllerFactoryTest : XCTestCase
13 14
 
@@ -23,7 +24,7 @@
23 24
 	[super setUp];
24 25
 	self.creator = nil;
25 26
 	self.store = [RNNExternalComponentStore new];
26
-	self.factory = [[RNNControllerFactory alloc] initWithRootViewCreator:self.creator eventEmitter:nil store:self.store componentRegistry:nil andBridge:nil];
27
+	self.factory = [[RNNControllerFactory alloc] initWithRootViewCreator:self.creator eventEmitter:nil store:self.store componentRegistry:nil andBridge:nil bottomTabsAttachModeFactory:[BottomTabsAttachModeFactory new]];
27 28
 }
28 29
 
29 30
 - (void)tearDown {
@@ -53,7 +54,8 @@
53 54
 							  @"data": @{@"name": @"externalComponent"},
54 55
 							  @"children": @[]};
55 56
 	id ans = [self.factory createLayout:layout];
56
-	XCTAssertTrue([ans isMemberOfClass:[RNNComponentViewController class]]);
57
+	XCTAssertTrue([ans isKindOfClass:[RNNComponentViewController class]]);
58
+	XCTAssertTrue([ans isMemberOfClass:[RNNExternalViewController class]]);
57 59
 }
58 60
 
59 61
 - (void)testCreateLayout_ComponentStackLayout {

+ 10
- 3
playground/ios/NavigationTests/RNNSideMenuControllerTest.m View File

@@ -1,9 +1,11 @@
1 1
 #import <XCTest/XCTest.h>
2 2
 #import "RNNSideMenuController.h"
3 3
 #import "RNNComponentViewController.h"
4
+#import "RNNTestRootViewCreator.h"
4 5
 
5 6
 @interface RNNSideMenuControllerTest : XCTestCase
6 7
 @property (nonatomic, strong) RNNSideMenuController *uut;
8
+@property (nonatomic, strong) RNNTestRootViewCreator *creator;
7 9
 @property (nonatomic, strong) RNNSideMenuChildVC *centerVC;
8 10
 @property (nonatomic, strong) RNNSideMenuChildVC *leftVC;
9 11
 @property (nonatomic, strong) RNNSideMenuChildVC *rightVC;
@@ -13,12 +15,17 @@
13 15
 
14 16
 - (void)setUp {
15 17
     [super setUp];
16
-	_leftVC = [[RNNSideMenuChildVC alloc] initWithLayoutInfo:nil creator:nil options:nil defaultOptions:nil presenter:nil eventEmitter:nil childViewController:[RNNComponentViewController new] type:RNNSideMenuChildTypeLeft];
17
-	_rightVC = [[RNNSideMenuChildVC alloc] initWithLayoutInfo:nil creator:nil options:nil defaultOptions:nil presenter:nil eventEmitter:nil childViewController:[RNNComponentViewController new] type:RNNSideMenuChildTypeRight];
18
-	_centerVC =[[RNNSideMenuChildVC alloc] initWithLayoutInfo:nil creator:nil options:nil defaultOptions:nil presenter:nil eventEmitter:nil childViewController:[RNNComponentViewController new] type:RNNSideMenuChildTypeCenter];
18
+	_creator = [[RNNTestRootViewCreator alloc] init];
19
+	_leftVC = [[RNNSideMenuChildVC alloc] initWithLayoutInfo:nil creator:nil options:nil defaultOptions:nil presenter:nil eventEmitter:nil childViewController:self.generateComponent type:RNNSideMenuChildTypeLeft];
20
+	_rightVC = [[RNNSideMenuChildVC alloc] initWithLayoutInfo:nil creator:nil options:nil defaultOptions:nil presenter:nil eventEmitter:nil childViewController:self.generateComponent type:RNNSideMenuChildTypeRight];
21
+	_centerVC =[[RNNSideMenuChildVC alloc] initWithLayoutInfo:nil creator:nil options:nil defaultOptions:nil presenter:nil eventEmitter:nil childViewController:self.generateComponent type:RNNSideMenuChildTypeCenter];
19 22
 	self.uut = [[RNNSideMenuController alloc] initWithLayoutInfo:nil creator:nil childViewControllers:@[_leftVC, _centerVC, _rightVC] options:[[RNNNavigationOptions alloc] initEmptyOptions] defaultOptions:nil presenter:nil eventEmitter:nil];
20 23
 }
21 24
 
25
+- (RNNComponentViewController *)generateComponent {
26
+	return [[RNNComponentViewController alloc] initWithLayoutInfo:nil rootViewCreator:_creator eventEmitter:nil presenter:[RNNComponentPresenter new] options:[[RNNNavigationOptions alloc] initWithDict:@{}] defaultOptions:nil];
27
+}
28
+
22 29
 - (void)testSetSideMenuWidthShouldUpdateLeftReactViewFrameWidth {
23 30
 	[self.uut side:MMDrawerSideLeft width:100];
24 31
 	XCTAssertEqual(self.uut.left.child.view.frame.size.width, 100.f);

+ 1
- 1
playground/ios/NavigationTests/RNNTestRootViewCreator.h View File

@@ -1,5 +1,5 @@
1 1
 #import <Foundation/Foundation.h>
2
-#import <ReactNativeNavigation/RNNComponentViewCreator.h>
2
+#import "RNNComponentViewCreator.h"
3 3
 
4 4
 @interface RNNTestRootViewCreator : NSObject <RNNComponentViewCreator>
5 5
 

+ 2
- 2
playground/ios/Podfile.lock View File

@@ -220,7 +220,7 @@ PODS:
220 220
     - ReactCommon/jscallinvoker (= 0.61.4)
221 221
   - ReactNativeKeyboardTrackingView (5.6.1):
222 222
     - React
223
-  - ReactNativeNavigation (4.0.2):
223
+  - ReactNativeNavigation (4.0.4):
224 224
     - React
225 225
   - Yoga (1.14.0)
226 226
 
@@ -346,7 +346,7 @@ SPEC CHECKSUMS:
346 346
   React-RCTVibration: 0f76400ee3cec6edb9c125da49fed279340d145a
347 347
   ReactCommon: a6a294e7028ed67b926d29551aa9394fd989c24c
348 348
   ReactNativeKeyboardTrackingView: a240a6a0dba852bb107109a7ec7e98b884055977
349
-  ReactNativeNavigation: f3da2b103f01bc576bbb1a41df67486788dcf6af
349
+  ReactNativeNavigation: bf2951e30fb873b3ed1e736419c38ed1efe0f6c2
350 350
   Yoga: ba3d99dbee6c15ea6bbe3783d1f0cb1ffb79af0f
351 351
 
352 352
 PODFILE CHECKSUM: 8a6eee15d75935b9144e5228ce8fa2a5b98079e7

+ 6
- 0
playground/ios/playground.xcodeproj/project.pbxproj View File

@@ -43,6 +43,7 @@
43 43
 		E58D26602385888C003F36BA /* RNNModalManagerTest.m in Sources */ = {isa = PBXBuildFile; fileRef = E58D26432385888C003F36BA /* RNNModalManagerTest.m */; };
44 44
 		E58D26612385888C003F36BA /* RNNTestNoColor.m in Sources */ = {isa = PBXBuildFile; fileRef = E58D26452385888C003F36BA /* RNNTestNoColor.m */; };
45 45
 		EFC0DF770EBAEFC23D0CB155 /* Pods_NavigationTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 84E32151E3A71C2B7328BCB4 /* Pods_NavigationTests.framework */; };
46
+		FE548D2471FE535CFCE8B113 /* Pods_playground.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 50364D69238E7ECC000E62A2 /* Pods_playground.framework */; };
46 47
 /* End PBXBuildFile section */
47 48
 
48 49
 /* Begin PBXContainerItemProxy section */
@@ -65,6 +66,8 @@
65 66
 		13B07FB71A68108700A75B9A /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = "<group>"; };
66 67
 		4259AF43A23D928FE78B4A3A /* Pods-NavigationTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-NavigationTests.debug.xcconfig"; path = "Target Support Files/Pods-NavigationTests/Pods-NavigationTests.debug.xcconfig"; sourceTree = "<group>"; };
67 68
 		4A3340545EAAF11C1F146864 /* Pods_playground.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_playground.framework; sourceTree = BUILT_PRODUCTS_DIR; };
69
+		50364D69238E7ECC000E62A2 /* Pods_playground.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = Pods_playground.framework; sourceTree = BUILT_PRODUCTS_DIR; };
70
+		50364D6B238E7F0A000E62A2 /* ReactNativeNavigation.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = ReactNativeNavigation.framework; sourceTree = BUILT_PRODUCTS_DIR; };
68 71
 		50451D33204451A800695F00 /* RNNCustomViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = RNNCustomViewController.h; path = ../../../lib/ios/RNNCustomViewController.h; sourceTree = "<group>"; };
69 72
 		50451D34204451A800695F00 /* RNNCustomViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = RNNCustomViewController.m; path = ../../../lib/ios/RNNCustomViewController.m; sourceTree = "<group>"; };
70 73
 		7F8E255E2E08F6ECE7DF6FE3 /* Pods-playground.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-playground.release.xcconfig"; path = "Target Support Files/Pods-playground/Pods-playground.release.xcconfig"; sourceTree = "<group>"; };
@@ -114,6 +117,7 @@
114 117
 			files = (
115 118
 				E5046080227748EA00212BD8 /* JavaScriptCore.framework in Frameworks */,
116 119
 				9D204F3DC4FBCD81583BF99F /* Pods_playground.framework in Frameworks */,
120
+				FE548D2471FE535CFCE8B113 /* Pods_playground.framework in Frameworks */,
117 121
 			);
118 122
 			runOnlyForDeploymentPostprocessing = 0;
119 123
 		};
@@ -188,6 +192,8 @@
188 192
 		E504607E227748E900212BD8 /* Frameworks */ = {
189 193
 			isa = PBXGroup;
190 194
 			children = (
195
+				50364D6B238E7F0A000E62A2 /* ReactNativeNavigation.framework */,
196
+				50364D69238E7ECC000E62A2 /* Pods_playground.framework */,
191 197
 				E504607F227748EA00212BD8 /* JavaScriptCore.framework */,
192 198
 				4A3340545EAAF11C1F146864 /* Pods_playground.framework */,
193 199
 				84E32151E3A71C2B7328BCB4 /* Pods_NavigationTests.framework */,

+ 14
- 0
playground/ios/playground.xcodeproj/xcshareddata/xcschemes/playground.xcscheme View File

@@ -6,6 +6,20 @@
6 6
       parallelizeBuildables = "NO"
7 7
       buildImplicitDependencies = "YES">
8 8
       <BuildActionEntries>
9
+         <BuildActionEntry
10
+            buildForTesting = "YES"
11
+            buildForRunning = "YES"
12
+            buildForProfiling = "YES"
13
+            buildForArchiving = "YES"
14
+            buildForAnalyzing = "YES">
15
+            <BuildableReference
16
+               BuildableIdentifier = "primary"
17
+               BlueprintIdentifier = "6ADCF369490C694BEBF31C5B4517D035"
18
+               BuildableName = "ReactNativeNavigation.framework"
19
+               BlueprintName = "ReactNativeNavigation"
20
+               ReferencedContainer = "container:Pods/Pods.xcodeproj">
21
+            </BuildableReference>
22
+         </BuildActionEntry>
9 23
          <BuildActionEntry
10 24
             buildForTesting = "YES"
11 25
             buildForRunning = "YES"

+ 1
- 1
playground/src/commons/Options.js View File

@@ -60,4 +60,4 @@ const slowOpenScreenAnimations = {
60 60
 
61 61
 module.exports = {
62 62
   setDefaultOptions
63
-}
63
+}