Browse Source

Detach bottomTabPresenter from basePresenter (#5970)

Refactor bottomTabs options applying mechanism. Both bottomTab and bottomTabs presenters were part of the base UIViewController class which was wrong. This commits moves them to the BottomTabsController.

This refactor mostly contains the following

* Detach bottomTabPresenter from basePresenter
* Create tabBarItem appearance on presenter init
* Refactor bottomTabPresenter
* Refactor bottomTabPresenter
* Move appearance creation to bottomTabsController
* Merge child options with direct child controller
Guy Carmeli 4 years ago
parent
commit
d2ba232d5e
No account linked to committer's email address
30 changed files with 279 additions and 208 deletions
  1. 2
    0
      lib/ios/BottomTabAppearancePresenter.h
  2. 8
    5
      lib/ios/BottomTabAppearancePresenter.m
  3. 6
    2
      lib/ios/BottomTabPresenter.h
  4. 13
    46
      lib/ios/BottomTabPresenter.m
  5. 1
    1
      lib/ios/BottomTabPresenterCreator.h
  6. 2
    2
      lib/ios/BottomTabPresenterCreator.m
  7. 0
    6
      lib/ios/BottomTabsAppearancePresenter.m
  8. 0
    2
      lib/ios/RNNBasePresenter.h
  9. 2
    15
      lib/ios/RNNBasePresenter.m
  10. 4
    0
      lib/ios/RNNBottomTabsController.h
  11. 26
    1
      lib/ios/RNNBottomTabsController.m
  12. 0
    2
      lib/ios/RNNBottomTabsPresenter.h
  13. 0
    12
      lib/ios/RNNBottomTabsPresenter.m
  14. 6
    1
      lib/ios/RNNControllerFactory.m
  15. 5
    0
      lib/ios/RNNDotIndicatorPresenter.h
  16. 27
    9
      lib/ios/RNNDotIndicatorPresenter.m
  17. 4
    0
      lib/ios/UIViewController+LayoutProtocol.h
  18. 22
    6
      lib/ios/UIViewController+LayoutProtocol.m
  19. 0
    22
      playground/ios/NavigationIOS12Tests/RNNBottomTabsPresenterTest.m
  20. 3
    3
      playground/ios/NavigationIOS12Tests/RNNRootViewControllerTest.m
  21. 55
    0
      playground/ios/NavigationTests/BottomTabPresenterTest.m
  22. 2
    23
      playground/ios/NavigationTests/RNNBasePresenterTest.m
  23. 12
    31
      playground/ios/NavigationTests/RNNBottomTabsAppearancePresenterTest.m
  24. 10
    0
      playground/ios/NavigationTests/RNNBottomTabsController+Helpers.h
  25. 17
    0
      playground/ios/NavigationTests/RNNBottomTabsController+Helpers.m
  26. 3
    3
      playground/ios/NavigationTests/RNNCommandsHandlerTest.m
  27. 13
    4
      playground/ios/NavigationTests/RNNDotIndicatorPresenterTest.m
  28. 7
    8
      playground/ios/NavigationTests/RNNRootViewControllerTest.m
  29. 17
    4
      playground/ios/NavigationTests/RNNTabBarControllerTest.m
  30. 12
    0
      playground/ios/playground.xcodeproj/project.pbxproj

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

@@ -3,4 +3,6 @@
3 3
 API_AVAILABLE(ios(13.0))
4 4
 @interface BottomTabAppearancePresenter : BottomTabPresenter
5 5
 
6
+- (instancetype)initWithDefaultOptions:(RNNNavigationOptions *)defaultOptions children:(NSArray<UIViewController *> *)children;
7
+
6 8
 @end

+ 8
- 5
lib/ios/BottomTabAppearancePresenter.m View File

@@ -3,13 +3,16 @@
3 3
 
4 4
 @implementation BottomTabAppearancePresenter
5 5
 
6
-- (void)bindViewController:(UIViewController *)boundViewController {
7
-    [super bindViewController:boundViewController];
8
-    boundViewController.tabBarItem.standardAppearance = [[UITabBarAppearance alloc] init];
6
+- (instancetype)initWithDefaultOptions:(RNNNavigationOptions *)defaultOptions children:(NSArray<UIViewController *> *)children {
7
+    self = [super initWithDefaultOptions:defaultOptions];
8
+    for (UIViewController* child in children) {
9
+        child.tabBarItem.standardAppearance = [[UITabBarAppearance alloc] init];
10
+    }
11
+    return self;
9 12
 }
10 13
 
11
-- (void)updateTabBarItem:(UITabBarItem *)tabItem bottomTabOptions:(RNNBottomTabOptions *)bottomTabOptions {
12
-    self.boundViewController.tabBarItem = [TabBarItemAppearanceCreator updateTabBarItem:self.boundViewController.tabBarItem bottomTabOptions:bottomTabOptions];
14
+- (void)createTabBarItem:(UIViewController *)child bottomTabOptions:(RNNBottomTabOptions *)bottomTabOptions {
15
+    child.tabBarItem = [TabBarItemAppearanceCreator updateTabBarItem:child.tabBarItem bottomTabOptions:bottomTabOptions];
13 16
 }
14 17
 
15 18
 @end

+ 6
- 2
lib/ios/BottomTabPresenter.h View File

@@ -2,8 +2,12 @@
2 2
 
3 3
 @interface BottomTabPresenter : RNNBasePresenter
4 4
 
5
-- (instancetype)initWithDefaultOptions:(RNNNavigationOptions *)defaultOptions;
5
+- (void)applyOptions:(RNNNavigationOptions *)options child:(UIViewController *)child;
6 6
 
7
-- (void)applyDotIndicator:(UIViewController *)child;
7
+- (void)applyOptionsOnWillMoveToParentViewController:(RNNNavigationOptions *)options  child:(UIViewController *)child;
8
+
9
+- (void)createTabBarItem:(UIViewController *)child bottomTabOptions:(RNNBottomTabOptions *)bottomTabOptions;
10
+
11
+- (void)mergeOptions:(RNNNavigationOptions *)options resolvedOptions:(RNNNavigationOptions *)resolvedOptions child:(UIViewController *)child;
8 12
 
9 13
 @end

+ 13
- 46
lib/ios/BottomTabPresenter.m View File

@@ -1,68 +1,35 @@
1 1
 #import "BottomTabPresenter.h"
2 2
 #import "RNNTabBarItemCreator.h"
3 3
 #import "UIViewController+RNNOptions.h"
4
-#import "RNNDotIndicatorPresenter.h"
5 4
 #import "UIViewController+LayoutProtocol.h"
6 5
 
7
-@interface BottomTabPresenter ()
8
-@property(nonatomic, strong) RNNDotIndicatorPresenter* dotIndicatorPresenter;
9
-@end
10
-
11 6
 @implementation BottomTabPresenter
12 7
 
13
-- (instancetype)initWithDefaultOptions:(RNNNavigationOptions *)defaultOptions {
14
-    self = [super init];
15
-    self.defaultOptions = defaultOptions;
16
-    self.dotIndicatorPresenter = [[RNNDotIndicatorPresenter alloc] initWithDefaultOptions:defaultOptions];
17
-    return self;
18
-}
19
-
20
-- (void)applyOptions:(RNNNavigationOptions *)options {
8
+- (void)applyOptions:(RNNNavigationOptions *)options child:(UIViewController *)child {
21 9
     RNNNavigationOptions * withDefault = [options withDefault:self.defaultOptions];
22 10
     
23
-    if (withDefault.bottomTab.badge.hasValue && [self.boundViewController.parentViewController isKindOfClass:[UITabBarController class]]) {
24
-        [self.boundViewController setTabBarItemBadge:withDefault.bottomTab.badge.get];
25
-    }
26
-    
27
-    if (withDefault.bottomTab.badgeColor.hasValue && [self.boundViewController.parentViewController isKindOfClass:[UITabBarController class]]) {
28
-        [self.boundViewController setTabBarItemBadgeColor:withDefault.bottomTab.badgeColor.get];
29
-    }
11
+    [child setTabBarItemBadge:[withDefault.bottomTab.badge getWithDefaultValue:[NSNull null]]];
12
+    [child setTabBarItemBadgeColor:[withDefault.bottomTab.badgeColor getWithDefaultValue:nil]];
30 13
 }
31 14
 
32
-- (void)applyOptionsOnWillMoveToParentViewController:(RNNNavigationOptions *)options {
15
+- (void)applyOptionsOnWillMoveToParentViewController:(RNNNavigationOptions *)options  child:(UIViewController *)child {
33 16
     RNNNavigationOptions * withDefault = [options withDefault:self.defaultOptions];
34 17
     
35
-    if (withDefault.bottomTab.hasValue) {
36
-        [self updateTabBarItem:self.boundViewController.tabBarItem bottomTabOptions:withDefault.bottomTab];
37
-    }
18
+    [child setTabBarItemBadge:[withDefault.bottomTab.badge getWithDefaultValue:[NSNull null]]];
19
+    [child setTabBarItemBadgeColor:[withDefault.bottomTab.badgeColor getWithDefaultValue:nil]];
20
+    [self createTabBarItem:child bottomTabOptions:withDefault.bottomTab];
38 21
 }
39 22
 
40
-- (void)mergeOptions:(RNNNavigationOptions *)options resolvedOptions:(RNNNavigationOptions *)resolvedOptions {
23
+- (void)mergeOptions:(RNNNavigationOptions *)options resolvedOptions:(RNNNavigationOptions *)resolvedOptions child:(UIViewController *)child {
41 24
     RNNNavigationOptions* withDefault = (RNNNavigationOptions *) [[resolvedOptions withDefault:self.defaultOptions] overrideOptions:options];
42 25
     
43
-    if (options.bottomTab.badge.hasValue) {
44
-        [self.boundViewController setTabBarItemBadge:options.bottomTab.badge.get];
45
-    }
46
-    
47
-    if (options.bottomTab.badgeColor.hasValue && [self.boundViewController.parentViewController isKindOfClass:[UITabBarController class]]) {
48
-        [self.boundViewController setTabBarItemBadgeColor:options.bottomTab.badgeColor.get];
49
-    }
50
-    
51
-    if ([options.bottomTab.dotIndicator hasValue] && [self.boundViewController.parentViewController isKindOfClass:[UITabBarController class]]) {
52
-        [[self dotIndicatorPresenter] apply:self.boundViewController:options.bottomTab.dotIndicator];
53
-    }
54
-    
55
-    if (options.bottomTab.hasValue) {
56
-        [self updateTabBarItem:self.boundViewController.tabBarItem bottomTabOptions:withDefault.bottomTab];
57
-    }
58
-}
59
-
60
-- (void)updateTabBarItem:(UITabBarItem *)tabItem bottomTabOptions:(RNNBottomTabOptions *)bottomTabOptions {
61
-    self.boundViewController.tabBarItem = [RNNTabBarItemCreator updateTabBarItem:self.boundViewController.tabBarItem bottomTabOptions:bottomTabOptions];
26
+    if (options.bottomTab.badge.hasValue) [child setTabBarItemBadge:options.bottomTab.badge.get];
27
+    if (options.bottomTab.badgeColor.hasValue) [child setTabBarItemBadgeColor:options.bottomTab.badgeColor.get];
28
+    if (options.bottomTab.hasValue) [self createTabBarItem:child bottomTabOptions:withDefault.bottomTab];
62 29
 }
63 30
 
64
-- (void)applyDotIndicator:(UIViewController *)child {
65
-    [_dotIndicatorPresenter apply:child:[child resolveOptions].bottomTab.dotIndicator];
31
+- (void)createTabBarItem:(UIViewController *)child bottomTabOptions:(RNNBottomTabOptions *)bottomTabOptions {
32
+    child.tabBarItem = [RNNTabBarItemCreator updateTabBarItem:child.tabBarItem bottomTabOptions:bottomTabOptions];
66 33
 }
67 34
 
68 35
 @end

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

@@ -3,6 +3,6 @@
3 3
 
4 4
 @interface BottomTabPresenterCreator : NSObject
5 5
 
6
-+ (BottomTabPresenter *)createWithDefaultOptions:(RNNNavigationOptions *)defaultOptions;
6
++ (BottomTabPresenter *)createWithDefaultOptions:(RNNNavigationOptions *)defaultOptions children:(NSArray<UIViewController *> *)children;
7 7
 
8 8
 @end

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

@@ -3,9 +3,9 @@
3 3
 
4 4
 @implementation BottomTabPresenterCreator
5 5
 
6
-+ (BottomTabPresenter *)createWithDefaultOptions:(RNNNavigationOptions *)defaultOptions {
6
++ (BottomTabPresenter *)createWithDefaultOptions:(RNNNavigationOptions *)defaultOptions children:(NSArray<UIViewController *> *)children {
7 7
 	if (@available(iOS 13.0, *)) {
8
-		return [[BottomTabAppearancePresenter alloc] initWithDefaultOptions:defaultOptions];
8
+		return [[BottomTabAppearancePresenter alloc] initWithDefaultOptions:defaultOptions children:children];
9 9
 	} else {
10 10
 		return [[BottomTabPresenter alloc] initWithDefaultOptions:defaultOptions];
11 11
 	}

+ 0
- 6
lib/ios/BottomTabsAppearancePresenter.m View File

@@ -2,12 +2,6 @@
2 2
 
3 3
 @implementation BottomTabsAppearancePresenter
4 4
 
5
-- (void)applyOptionsOnInit:(RNNNavigationOptions *)options {
6
-    [super applyOptionsOnInit:options];
7
-    UITabBarController *bottomTabs = self.tabBarController;
8
-    bottomTabs.tabBar.standardAppearance = [UITabBarAppearance new];
9
-}
10
-
11 5
 - (void)setTabBarBackgroundColor:(UIColor *)backgroundColor {
12 6
     UITabBarController *bottomTabs = self.tabBarController;
13 7
     for (UIViewController* childViewController in bottomTabs.childViewControllers) {

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

@@ -27,8 +27,6 @@ typedef void (^RNNReactViewReadyCompletionBlock)(void);
27 27
 
28 28
 - (void)applyOptionsOnWillMoveToParentViewController:(RNNNavigationOptions *)options;
29 29
 
30
-- (void)applyDotIndicator:(UIViewController *)child;
31
-
32 30
 - (void)mergeOptions:(RNNNavigationOptions *)options resolvedOptions:(RNNNavigationOptions *)resolvedOptions;
33 31
 
34 32
 - (void)renderComponents:(RNNNavigationOptions *)options perform:(RNNReactViewReadyCompletionBlock)readyBlock;

+ 2
- 15
lib/ios/RNNBasePresenter.m View File

@@ -5,17 +5,12 @@
5 5
 #import "UIViewController+LayoutProtocol.h"
6 6
 #import "DotIndicatorOptions.h"
7 7
 #import "RCTConvert+Modal.h"
8
-#import "BottomTabPresenterCreator.h"
9 8
 
10
-@interface RNNBasePresenter ()
11
-@property(nonatomic, strong) BottomTabPresenter* bottomTabPresenter;
12
-@end
13 9
 @implementation RNNBasePresenter
14 10
 
15 11
 - (instancetype)initWithDefaultOptions:(RNNNavigationOptions *)defaultOptions {
16 12
     self = [super init];
17 13
     _defaultOptions = defaultOptions;
18
-    _bottomTabPresenter = [BottomTabPresenterCreator createWithDefaultOptions:self.defaultOptions];
19 14
     return self;
20 15
 }
21 16
 
@@ -28,7 +23,6 @@
28 23
 - (void)bindViewController:(UIViewController *)boundViewController {
29 24
     self.boundComponentId = boundViewController.layoutInfo.componentId;
30 25
     _boundViewController = boundViewController;
31
-    _bottomTabPresenter.boundViewController = boundViewController;
32 26
 }
33 27
 
34 28
 - (void)setDefaultOptions:(RNNNavigationOptions *)defaultOptions {
@@ -61,18 +55,15 @@
61 55
 }
62 56
 
63 57
 - (void)applyOptionsOnWillMoveToParentViewController:(RNNNavigationOptions *)options {
64
-    [_bottomTabPresenter applyOptionsOnWillMoveToParentViewController:options];
58
+
65 59
 }
66 60
 
67 61
 - (void)applyOptions:(RNNNavigationOptions *)options {
68
-    [_bottomTabPresenter applyOptions:options];
62
+
69 63
 }
70 64
 
71 65
 - (void)mergeOptions:(RNNNavigationOptions *)options resolvedOptions:(RNNNavigationOptions *)resolvedOptions {
72
-    UIViewController* viewController = self.boundViewController;
73 66
     RNNNavigationOptions* withDefault = (RNNNavigationOptions *) [[resolvedOptions withDefault:_defaultOptions] overrideOptions:options];
74
-    
75
-    [_bottomTabPresenter mergeOptions:options resolvedOptions:resolvedOptions];
76 67
 	
77 68
 	if (options.window.backgroundColor.hasValue) {
78 69
 		UIApplication.sharedApplication.delegate.window.backgroundColor = withDefault.window.backgroundColor.get;
@@ -90,10 +81,6 @@
90 81
 
91 82
 }
92 83
 
93
-- (void)applyDotIndicator:(UIViewController *)child {
94
-    [_bottomTabPresenter applyDotIndicator:child];
95
-}
96
-
97 84
 - (UIStatusBarStyle)getStatusBarStyle:(RNNNavigationOptions *)resolvedOptions {
98 85
     RNNNavigationOptions *withDefault = [resolvedOptions withDefault:[self defaultOptions]];
99 86
     NSString* statusBarStyle = [withDefault.statusBar.style getWithDefaultValue:@"default"];

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

@@ -3,6 +3,8 @@
3 3
 #import "RNNBottomTabsPresenter.h"
4 4
 #import "UIViewController+LayoutProtocol.h"
5 5
 #import "BottomTabsBaseAttacher.h"
6
+#import "BottomTabPresenter.h"
7
+#import "RNNDotIndicatorPresenter.h"
6 8
 
7 9
 @interface RNNBottomTabsController : UITabBarController <RNNLayoutProtocol, UITabBarControllerDelegate>
8 10
 
@@ -11,6 +13,8 @@
11 13
                            options:(RNNNavigationOptions *)options
12 14
                     defaultOptions:(RNNNavigationOptions *)defaultOptions
13 15
                          presenter:(RNNBasePresenter *)presenter
16
+                bottomTabPresenter:(BottomTabPresenter *)bottomTabPresenter
17
+                dotIndicatorPresenter:(RNNDotIndicatorPresenter *)dotIndicatorPresenter
14 18
                       eventEmitter:(RNNEventEmitter *)eventEmitter
15 19
               childViewControllers:(NSArray *)childViewControllers
16 20
                 bottomTabsAttacher:(BottomTabsBaseAttacher *)bottomTabsAttacher;

+ 26
- 1
lib/ios/RNNBottomTabsController.m View File

@@ -1,9 +1,15 @@
1 1
 #import "RNNBottomTabsController.h"
2 2
 #import "UITabBarController+RNNUtils.h"
3 3
 
4
+@interface RNNBottomTabsController ()
5
+@property (nonatomic, strong) BottomTabPresenter* bottomTabPresenter;
6
+@property (nonatomic, strong) RNNDotIndicatorPresenter* dotIndicatorPresenter;
7
+@end
8
+
4 9
 @implementation RNNBottomTabsController {
5 10
 	NSUInteger _currentTabIndex;
6 11
     BottomTabsBaseAttacher* _bottomTabsAttacher;
12
+    
7 13
 }
8 14
 
9 15
 - (instancetype)initWithLayoutInfo:(RNNLayoutInfo *)layoutInfo
@@ -11,14 +17,31 @@
11 17
                            options:(RNNNavigationOptions *)options
12 18
                     defaultOptions:(RNNNavigationOptions *)defaultOptions
13 19
                          presenter:(RNNBasePresenter *)presenter
20
+                bottomTabPresenter:(BottomTabPresenter *)bottomTabPresenter
21
+             dotIndicatorPresenter:(RNNDotIndicatorPresenter *)dotIndicatorPresenter
14 22
                       eventEmitter:(RNNEventEmitter *)eventEmitter
15 23
               childViewControllers:(NSArray *)childViewControllers
16 24
                 bottomTabsAttacher:(BottomTabsBaseAttacher *)bottomTabsAttacher {
17
-    self = [super initWithLayoutInfo:layoutInfo creator:creator options:options defaultOptions:defaultOptions presenter:presenter eventEmitter:eventEmitter childViewControllers:childViewControllers];
18 25
     _bottomTabsAttacher = bottomTabsAttacher;
26
+    _bottomTabPresenter = bottomTabPresenter;
27
+    _dotIndicatorPresenter = dotIndicatorPresenter;
28
+    self = [super initWithLayoutInfo:layoutInfo creator:creator options:options defaultOptions:defaultOptions presenter:presenter eventEmitter:eventEmitter childViewControllers:childViewControllers];
29
+    if (@available(iOS 13.0, *)) {
30
+        self.tabBar.standardAppearance = [UITabBarAppearance new];
31
+    }
19 32
     return self;
20 33
 }
21 34
 
35
+- (void)onChildAddToParent:(UIViewController *)child options:(RNNNavigationOptions *)options {
36
+    [_bottomTabPresenter applyOptionsOnWillMoveToParentViewController:options child:child];
37
+}
38
+
39
+- (void)mergeChildOptions:(RNNNavigationOptions *)options child:(UIViewController *)child {
40
+    [super mergeChildOptions:options child:child];
41
+    [_bottomTabPresenter mergeOptions:options resolvedOptions:self.resolveOptions child:[self findViewController:child]];
42
+    [_dotIndicatorPresenter mergeOptions:options resolvedOptions:self.resolveOptions child:[self findViewController:child]];
43
+}
44
+
22 45
 - (id<UITabBarControllerDelegate>)delegate {
23 46
 	return self;
24 47
 }
@@ -36,6 +59,8 @@
36 59
               [view addGestureRecognizer: longPressGesture];
37 60
           }
38 61
     }
62
+    
63
+    [_dotIndicatorPresenter bottomTabsDidLayoutSubviews:self];
39 64
 }
40 65
 
41 66
 - (UIViewController *)getCurrentChild {

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

@@ -2,8 +2,6 @@
2 2
 
3 3
 @interface RNNBottomTabsPresenter : RNNBasePresenter
4 4
 
5
-- (void)applyDotIndicator;
6
-
7 5
 - (void)setTabBarBackgroundColor:(UIColor *)backgroundColor;
8 6
 
9 7
 - (UITabBarController *)tabBarController;

+ 0
- 12
lib/ios/RNNBottomTabsPresenter.m View File

@@ -88,16 +88,4 @@
88 88
     return self.tabBarController.tabBar;
89 89
 }
90 90
 
91
-- (void)viewDidLayoutSubviews {
92
-    dispatch_async(dispatch_get_main_queue(), ^{
93
-        [self applyDotIndicator];
94
-    });
95
-}
96
-
97
-- (void)applyDotIndicator {
98
-    [self.boundViewController forEachChild:^(UIViewController *child) {
99
-        [self applyDotIndicator:child];
100
-    }];
101
-}
102
-
103 91
 @end

+ 6
- 1
lib/ios/RNNControllerFactory.m View File

@@ -9,6 +9,7 @@
9 9
 #import "BottomTabsBaseAttacher.h"
10 10
 #import "BottomTabsAttachModeFactory.h"
11 11
 #import "BottomTabsPresenterCreator.h"
12
+#import "BottomTabPresenterCreator.h"
12 13
 
13 14
 @implementation RNNControllerFactory {
14 15
 	id<RNNComponentViewCreator> _creator;
@@ -150,14 +151,18 @@
150 151
     RNNLayoutInfo* layoutInfo = [[RNNLayoutInfo alloc] initWithNode:node];
151 152
     RNNNavigationOptions* options = [[RNNNavigationOptions alloc] initWithDict:node.data[@"options"]];
152 153
     RNNBottomTabsPresenter* presenter = [BottomTabsPresenterCreator createWithDefaultOptions:_defaultOptions];
154
+    NSArray *childViewControllers = [self extractChildrenViewControllersFromNode:node];
155
+    BottomTabPresenter* bottomTabPresenter = [BottomTabPresenterCreator createWithDefaultOptions:_defaultOptions children:childViewControllers];;
156
+    RNNDotIndicatorPresenter* dotIndicatorPresenter = [[RNNDotIndicatorPresenter alloc] initWithDefaultOptions:_defaultOptions];
153 157
 	BottomTabsBaseAttacher* bottomTabsAttacher = [_bottomTabsAttachModeFactory fromOptions:options];
154 158
     
155
-    NSArray *childViewControllers = [self extractChildrenViewControllersFromNode:node];
156 159
     return [[RNNBottomTabsController alloc] initWithLayoutInfo:layoutInfo
157 160
                                                        creator:_creator
158 161
                                                        options:options
159 162
                                                 defaultOptions:_defaultOptions
160 163
                                                      presenter:presenter
164
+                                            bottomTabPresenter:bottomTabPresenter
165
+                                         dotIndicatorPresenter:dotIndicatorPresenter
161 166
                                                   eventEmitter:_eventEmitter
162 167
                                           childViewControllers:childViewControllers
163 168
                                             bottomTabsAttacher:bottomTabsAttacher

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

@@ -8,4 +8,9 @@
8 8
 - (instancetype)initWithDefaultOptions:(RNNNavigationOptions *)defaultOptions;
9 9
 
10 10
 - (void)apply:(UIViewController *)child :(DotIndicatorOptions *)options;
11
+
12
+- (void)bottomTabsDidLayoutSubviews:(UITabBarController *)bottomTabs;
13
+
14
+- (void)mergeOptions:(RNNNavigationOptions *)options resolvedOptions:(RNNNavigationOptions *)resolvedOptions child:(UIViewController *)child;
15
+
11 16
 @end

+ 27
- 9
lib/ios/RNNDotIndicatorPresenter.m View File

@@ -13,20 +13,38 @@
13 13
     return self;
14 14
 }
15 15
 
16
+- (void)bottomTabsDidLayoutSubviews:(UITabBarController *)bottomTabs {
17
+    for (UIViewController* child in bottomTabs.childViewControllers) {
18
+        [self applyDotIndicator:child];
19
+    }
20
+}
21
+
22
+- (void)applyDotIndicator:(UIViewController *)child {
23
+    [self apply:child:[child resolveOptions].bottomTab.dotIndicator];
24
+}
25
+
26
+- (void)mergeOptions:(RNNNavigationOptions *)options resolvedOptions:(RNNNavigationOptions *)resolvedOptions child:(UIViewController *)child {
27
+    RNNNavigationOptions* withDefault = (RNNNavigationOptions *) [[resolvedOptions withDefault:self.defaultOptions] overrideOptions:options];
28
+    
29
+    if ([options.bottomTab.dotIndicator hasValue]) {
30
+        [self apply:child:withDefault.bottomTab.dotIndicator];
31
+    }
32
+}
33
+
16 34
 - (void)apply:(UIViewController *)child :(DotIndicatorOptions *)options {
17 35
     if (![options hasValue]) return;
18
-
36
+    
19 37
     if ([options.visible isFalse]) {
20 38
         if ([child tabBarItem].tag > 0) [self remove:child];
21 39
         return;
22 40
     }
23 41
     if ([self currentIndicatorEquals:child :options]) return;
24
-
42
+    
25 43
     if ([self hasIndicator:child]) [self remove:child];
26
-
44
+    
27 45
     UIView *indicator = [self createIndicator:options];
28 46
     [child tabBarItem].tag = indicator.tag;
29
-
47
+    
30 48
     UITabBarController *bottomTabs = [self getTabBarController:child];
31 49
     int index = (int) [[bottomTabs childViewControllers] indexOfObject:child];
32 50
     [[bottomTabs getTabView:index] addSubview:indicator];
@@ -47,10 +65,10 @@
47 65
     UIView *icon = [bottomTabs getTabIcon:index];
48 66
     float size = [[options.size getWithDefaultValue:@6] floatValue];
49 67
     [NSLayoutConstraint activateConstraints:@[
50
-            [badge.leftAnchor constraintEqualToAnchor:icon.rightAnchor constant:-size / 2],
51
-            [badge.topAnchor constraintEqualToAnchor:icon.topAnchor constant:-size / 2],
52
-            [badge.widthAnchor constraintEqualToConstant:size],
53
-            [badge.heightAnchor constraintEqualToConstant:size]
68
+        [badge.leftAnchor constraintEqualToAnchor:icon.rightAnchor constant:-size / 2],
69
+        [badge.topAnchor constraintEqualToAnchor:icon.topAnchor constant:-size / 2],
70
+        [badge.widthAnchor constraintEqualToConstant:size],
71
+        [badge.heightAnchor constraintEqualToConstant:size]
54 72
     ]];
55 73
 }
56 74
 
@@ -80,4 +98,4 @@
80 98
     return [viewController isKindOfClass:[UITabBarController class]] ? viewController : [viewController tabBarController];
81 99
 }
82 100
 
83
-@end
101
+@end

+ 4
- 0
lib/ios/UIViewController+LayoutProtocol.h View File

@@ -12,12 +12,16 @@ typedef void (^RNNReactViewReadyCompletionBlock)(void);
12 12
 
13 13
 - (UIViewController *)presentedComponentViewController;
14 14
 
15
+- (UIViewController *)findViewController:(UIViewController *)child;
16
+
15 17
 - (UIViewController *)topMostViewController;
16 18
 
17 19
 - (void)mergeOptions:(RNNNavigationOptions *)options;
18 20
 
19 21
 - (void)mergeChildOptions:(RNNNavigationOptions *)options child:(UIViewController *)child;
20 22
 
23
+- (void)onChildAddToParent:(UIViewController *)child options:(RNNNavigationOptions *)options;
24
+
21 25
 - (UINavigationController *)stack;
22 26
 
23 27
 - (RNNNavigationOptions *)resolveOptions;

+ 22
- 6
lib/ios/UIViewController+LayoutProtocol.m View File

@@ -17,13 +17,13 @@
17 17
 	self.defaultOptions = defaultOptions;
18 18
 	self.layoutInfo = layoutInfo;
19 19
 	self.creator = creator;
20
-	self.eventEmitter = eventEmitter;
21
-	if ([self respondsToSelector:@selector(setViewControllers:)]) {
22
-		[self performSelector:@selector(setViewControllers:) withObject:childViewControllers];
23
-	}
24
-	self.presenter = presenter;
20
+    self.eventEmitter = eventEmitter;
21
+    self.presenter = presenter;
25 22
     [self.presenter bindViewController:self];
26
-	[self.presenter applyOptionsOnInit:self.resolveOptions];
23
+    if ([self respondsToSelector:@selector(setViewControllers:)]) {
24
+        [self performSelector:@selector(setViewControllers:) withObject:childViewControllers];
25
+    }
26
+    [self.presenter applyOptionsOnInit:self.resolveOptions];
27 27
 
28 28
 	return self;
29 29
 }
@@ -117,6 +117,17 @@
117 117
         return self;
118 118
 }
119 119
 
120
+- (UIViewController *)findViewController:(UIViewController *)child {
121
+    if (self == child) return child;
122
+    
123
+    for (UIViewController* childController in self.childViewControllers) {
124
+        UIViewController* fromChild = [childController findViewController:child];
125
+        if (fromChild) return childController;
126
+    }
127
+    
128
+    return nil;
129
+}
130
+
120 131
 - (CGFloat)getTopBarHeight {
121 132
     for(UIViewController * child in [self childViewControllers]) {
122 133
         CGFloat childTopBarHeight = [child getTopBarHeight];
@@ -140,6 +151,10 @@
140 151
 	[self.parentViewController onChildWillAppear];
141 152
 }
142 153
 
154
+- (void)onChildAddToParent:(UIViewController *)child options:(RNNNavigationOptions *)options {
155
+    [self.parentViewController onChildAddToParent:child options:options];
156
+}
157
+
143 158
 - (void)componentDidAppear {
144 159
     [self.presenter componentDidAppear];
145 160
     [self.parentViewController componentDidAppear];
@@ -153,6 +168,7 @@
153 168
 - (void)willMoveToParentViewController:(UIViewController *)parent {
154 169
 	if (parent) {
155 170
 		[self.presenter applyOptionsOnWillMoveToParentViewController:self.resolveOptions];
171
+        [self onChildAddToParent:self options:self.resolveOptions];
156 172
 	}
157 173
 }
158 174
 

+ 0
- 22
playground/ios/NavigationIOS12Tests/RNNBottomTabsPresenterTest.m View File

@@ -70,28 +70,6 @@
70 70
 	[self.boundViewController verify];
71 71
 }
72 72
 
73
-- (void)testViewDidLayoutSubviews_appliesBadgeOnNextRunLoop {
74
-    id uut = [self uut];
75
-    [[uut expect] applyDotIndicator];
76
-    [uut viewDidLayoutSubviews];
77
-    [[NSRunLoop mainRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.01]];
78
-    [uut verify];
79
-}
80
-
81
-- (void)testApplyDotIndicator_callsAppliesBadgeWithEachChild {
82
-    id uut = [self uut];
83
-    id child1 = [UIViewController new];
84
-    id child2 = [UIViewController new];
85
-
86
-    [[uut expect] applyDotIndicator:child1];
87
-    [[uut expect] applyDotIndicator:child2];
88
-    [[self boundViewController] addChildViewController:child1];
89
-    [[self boundViewController] addChildViewController:child2];
90
-
91
-    [uut applyDotIndicator];
92
-    [uut verify];
93
-}
94
-
95 73
 - (void)testBackgroundColor_validColor {
96 74
 	UIColor* inputColor = [RCTConvert UIColor:@(0xFFFF0000)];
97 75
 	self.options.layout.backgroundColor = [[Color alloc] initWithValue:inputColor];

+ 3
- 3
playground/ios/NavigationIOS12Tests/RNNRootViewControllerTest.m View File

@@ -8,6 +8,7 @@
8 8
 #import "RNNStackController.h"
9 9
 #import "RNNBottomTabsController.h"
10 10
 #import "RNNUIBarButtonItem.h"
11
+#import "RNNBottomTabsController+Helpers.h"
11 12
 
12 13
 
13 14
 @interface RNNComponentViewController (EmbedInTabBar)
@@ -166,13 +167,12 @@
166 167
 -(void)testTabBadge {
167 168
 	NSString* tabBadgeInput = @"5";
168 169
 	self.options.bottomTab.badge = [[Text alloc] initWithValue:tabBadgeInput];
169
-	__unused RNNBottomTabsController* vc = [[RNNBottomTabsController alloc] init];
170 170
 	NSMutableArray* controllers = [NSMutableArray new];
171 171
 	UITabBarItem* item = [[UITabBarItem alloc] initWithTitle:@"A Tab" image:nil tag:1];
172 172
 	[self.uut setTabBarItem:item];
173 173
 	[controllers addObject:self.uut];
174
-	[vc setViewControllers:controllers];
175
-	[self.uut viewWillAppear:false];
174
+	__unused RNNBottomTabsController* vc = [RNNBottomTabsController createWithChildren:controllers];
175
+	[self.uut willMoveToParentViewController:vc];
176 176
 	XCTAssertTrue([self.uut.tabBarItem.badgeValue isEqualToString:tabBadgeInput]);
177 177
 
178 178
 }

+ 55
- 0
playground/ios/NavigationTests/BottomTabPresenterTest.m View File

@@ -0,0 +1,55 @@
1
+#import <XCTest/XCTest.h>
2
+#import "RNNBasePresenter.h"
3
+#import <OCMock/OCMock.h>
4
+#import "UIViewController+RNNOptions.h"
5
+#import <ReactNativeNavigation/RNNComponentViewController.h>
6
+#import <ReactNativeNavigation/BottomTabAppearancePresenter.h>
7
+#import "RNNBottomTabsController+Helpers.h"
8
+
9
+@interface RNNBottomTabPresenterTest : XCTestCase
10
+
11
+@property(nonatomic, strong) BottomTabAppearancePresenter *uut;
12
+@property(nonatomic, strong) RNNNavigationOptions *options;
13
+@property(nonatomic, strong) RNNBottomTabsController *boundViewController;
14
+@property(nonatomic, strong) RNNComponentViewController *componentViewController;
15
+@property(nonatomic, strong) id mockBoundViewController;
16
+
17
+@end
18
+
19
+@implementation RNNBottomTabPresenterTest
20
+
21
+- (void)setUp {
22
+    [super setUp];
23
+    self.uut = [[BottomTabAppearancePresenter alloc] initWithDefaultOptions:[[RNNNavigationOptions alloc] initEmptyOptions]];
24
+	self.componentViewController = [RNNComponentViewController new];
25
+    self.boundViewController = [RNNBottomTabsController createWithChildren:@[self.componentViewController]];
26
+    self.mockBoundViewController = [OCMockObject partialMockForObject:self.boundViewController];
27
+	[self.uut bindViewController:self.mockBoundViewController];
28
+    self.options = [[RNNNavigationOptions alloc] initEmptyOptions];
29
+}
30
+
31
+- (void)tearDown {
32
+    [super tearDown];
33
+    [self.mockBoundViewController stopMocking];
34
+    self.boundViewController = nil;
35
+}
36
+
37
+- (void)testApplyOptions_shouldSetTabBarItemBadgeWithValue {
38
+    self.options.bottomTab.badge = [[Text alloc] initWithValue:@"badge"];
39
+    [self.uut applyOptions:self.options child:self.componentViewController];
40
+	XCTAssertEqual(self.componentViewController.tabBarItem.badgeValue, @"badge");
41
+}
42
+
43
+- (void)testMergeOptions_shouldSetTabBarItemColorWithDefaultOptions {
44
+	RNNNavigationOptions* defaultOptions = [[RNNNavigationOptions alloc] initEmptyOptions];
45
+	defaultOptions.bottomTab.selectedIconColor = [Color withColor:UIColor.greenColor];
46
+	self.uut.defaultOptions = defaultOptions;
47
+	
48
+	RNNNavigationOptions* mergeOptions = [[RNNNavigationOptions alloc] initEmptyOptions];
49
+	mergeOptions.bottomTab.text = [[Text alloc] initWithValue:@"title"];
50
+	
51
+	[self.uut mergeOptions:mergeOptions resolvedOptions:self.options child:self.componentViewController];
52
+	XCTAssertEqual(self.componentViewController.tabBarItem.title, @"title");
53
+}
54
+
55
+@end

+ 2
- 23
playground/ios/NavigationTests/RNNBasePresenterTest.m View File

@@ -4,7 +4,7 @@
4 4
 #import "UIViewController+RNNOptions.h"
5 5
 #import "RNNComponentViewController.h"
6 6
 
7
-@interface RNNBottomTabPresenterTest : XCTestCase
7
+@interface RNNBasePresenterTest : XCTestCase
8 8
 
9 9
 @property(nonatomic, strong) RNNBasePresenter *uut;
10 10
 @property(nonatomic, strong) RNNNavigationOptions *options;
@@ -13,7 +13,7 @@
13 13
 
14 14
 @end
15 15
 
16
-@implementation RNNBottomTabPresenterTest
16
+@implementation RNNBasePresenterTest
17 17
 
18 18
 - (void)setUp {
19 19
     [super setUp];
@@ -36,14 +36,6 @@
36 36
     [self.mockBoundViewController verify];
37 37
 }
38 38
 
39
-- (void)testApplyOptions_shouldSetTabBarItemBadgeWithValue {
40
-    OCMStub([self.mockBoundViewController parentViewController]).andReturn([UITabBarController new]);
41
-    self.options.bottomTab.badge = [[Text alloc] initWithValue:@"badge"];
42
-    [[self.mockBoundViewController expect] setTabBarItemBadge:self.options.bottomTab.badge.get];
43
-    [self.uut applyOptions:self.options];
44
-    [self.mockBoundViewController verify];
45
-}
46
-
47 39
 - (void)testApplyOptions_setTabBarItemBadgeShouldNotCalledOnUITabBarController {
48 40
     [self.uut bindViewController:self.mockBoundViewController];
49 41
     self.options.bottomTab.badge = [[Text alloc] initWithValue:@"badge"];
@@ -96,17 +88,4 @@
96 88
 	XCTAssertTrue(_boundViewController.modalInPresentation);
97 89
 }
98 90
 
99
-- (void)testMergeOptions_shouldSetTabBarItemColorWithDefaultOptions {
100
-	RNNNavigationOptions* defaultOptions = [[RNNNavigationOptions alloc] initEmptyOptions];
101
-	defaultOptions.bottomTab.selectedIconColor = [Color withColor:UIColor.greenColor];
102
-	self.uut.defaultOptions = defaultOptions;
103
-	
104
-	RNNNavigationOptions* mergeOptions = [[RNNNavigationOptions alloc] initEmptyOptions];
105
-	mergeOptions.bottomTab.text = [[Text alloc] initWithValue:@"title"];
106
-    OCMStub([self.mockBoundViewController parentViewController]).andReturn([UITabBarController new]);
107
-	
108
-    [self.uut mergeOptions:mergeOptions resolvedOptions:self.options];
109
-	XCTAssertEqual(self.uut.boundViewController.tabBarItem.title, @"title");
110
-}
111
-
112 91
 @end

+ 12
- 31
playground/ios/NavigationTests/RNNBottomTabsAppearancePresenterTest.m View File

@@ -1,13 +1,18 @@
1 1
 #import <XCTest/XCTest.h>
2 2
 #import <OCMock/OCMock.h>
3
-#import "BottomTabsAppearancePresenter.h"
3
+#import "BottomTabsPresenterCreator.h"
4
+#import "BottomTabPresenterCreator.h"
4 5
 #import "UITabBarController+RNNOptions.h"
5 6
 #import "RNNBottomTabsController.h"
6 7
 #import "RNNComponentViewController.h"
8
+#import "RNNBottomTabsPresenter.h"
9
+#import "RNNDotIndicatorPresenter.h"
7 10
 
8 11
 @interface RNNBottomTabsAppearancePresenterTest : XCTestCase
9 12
 
10
-@property(nonatomic, strong) BottomTabsAppearancePresenter *uut;
13
+@property(nonatomic, strong) RNNBottomTabsPresenter *uut;
14
+@property(nonatomic, strong) NSArray<UIViewController *> *children;
15
+@property(nonatomic, strong) id dotIndicatorPresenter;
11 16
 @property(nonatomic, strong) RNNNavigationOptions *options;
12 17
 @property(nonatomic, strong) id boundViewController;
13 18
 
@@ -17,8 +22,10 @@
17 22
 
18 23
 - (void)setUp {
19 24
     [super setUp];
20
-    self.uut = [OCMockObject partialMockForObject:[BottomTabsAppearancePresenter new]];
21
-    self.boundViewController = [OCMockObject partialMockForObject:[RNNBottomTabsController new]];
25
+	self.children = @[[[RNNComponentViewController alloc] initWithLayoutInfo:nil rootViewCreator:nil eventEmitter:nil presenter:[[RNNComponentPresenter alloc] initWithDefaultOptions:nil] options:nil defaultOptions:nil]];
26
+	self.dotIndicatorPresenter = [OCMockObject partialMockForObject:[[RNNDotIndicatorPresenter alloc] initWithDefaultOptions:nil]];
27
+    self.uut = [OCMockObject partialMockForObject:[BottomTabsPresenterCreator createWithDefaultOptions:nil]];
28
+	self.boundViewController = [OCMockObject partialMockForObject:[[RNNBottomTabsController alloc] initWithLayoutInfo:nil creator:nil options:nil defaultOptions:nil presenter:self.uut bottomTabPresenter:[BottomTabPresenterCreator createWithDefaultOptions:nil children:self.children] dotIndicatorPresenter:self.dotIndicatorPresenter eventEmitter:nil childViewControllers:self.children bottomTabsAttacher:nil]];
22 29
     [self.uut bindViewController:self.boundViewController];
23 30
     self.options = [[RNNNavigationOptions alloc] initEmptyOptions];
24 31
 }
@@ -71,28 +78,6 @@
71 78
 	[self.boundViewController verify];
72 79
 }
73 80
 
74
-- (void)testViewDidLayoutSubviews_appliesBadgeOnNextRunLoop {
75
-    id uut = [self uut];
76
-    [[uut expect] applyDotIndicator];
77
-    [uut viewDidLayoutSubviews];
78
-    [[NSRunLoop mainRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.01]];
79
-    [uut verify];
80
-}
81
-
82
-- (void)testApplyDotIndicator_callsAppliesBadgeWithEachChild {
83
-    id uut = [self uut];
84
-    id child1 = [UIViewController new];
85
-    id child2 = [UIViewController new];
86
-
87
-    [[uut expect] applyDotIndicator:child1];
88
-    [[uut expect] applyDotIndicator:child2];
89
-    [[self boundViewController] addChildViewController:child1];
90
-    [[self boundViewController] addChildViewController:child2];
91
-
92
-    [uut applyDotIndicator];
93
-    [uut verify];
94
-}
95
-
96 81
 - (void)testBackgroundColor_validColor {
97 82
 	UIColor* inputColor = [RCTConvert UIColor:@(0xFFFF0000)];
98 83
 	self.options.layout.backgroundColor = [[Color alloc] initWithValue:inputColor];
@@ -103,12 +88,8 @@
103 88
 
104 89
 - (void)testTabBarBackgroundColor {
105 90
 	UIColor* tabBarBackgroundColor = [UIColor redColor];
106
-	RNNComponentPresenter* vcPresenter = [[RNNComponentPresenter alloc] initWithDefaultOptions:nil];
107
-	UIViewController* vc = [[RNNComponentViewController alloc] initWithLayoutInfo:nil rootViewCreator:nil eventEmitter:nil presenter:vcPresenter options:nil defaultOptions:nil];
108
-	
109
-	[((UITabBarController *)self.boundViewController) setViewControllers:@[vc]];
110 91
 	[self.uut setTabBarBackgroundColor:tabBarBackgroundColor];
111
-	XCTAssertTrue([vc.tabBarItem.standardAppearance.backgroundColor isEqual:tabBarBackgroundColor]);
92
+	XCTAssertTrue([self.children.lastObject.tabBarItem.standardAppearance.backgroundColor isEqual:tabBarBackgroundColor]);
112 93
 }
113 94
 
114 95
 @end

+ 10
- 0
playground/ios/NavigationTests/RNNBottomTabsController+Helpers.h View File

@@ -0,0 +1,10 @@
1
+#import <ReactNativeNavigation/ReactNativeNavigation.h>
2
+#import "RNNBottomTabsController.h"
3
+
4
+@interface RNNBottomTabsController (Helpers)
5
+
6
++ (RNNBottomTabsController *)create;
7
+
8
++ (RNNBottomTabsController *)createWithChildren:(NSArray *)children;
9
+
10
+@end

+ 17
- 0
playground/ios/NavigationTests/RNNBottomTabsController+Helpers.m View File

@@ -0,0 +1,17 @@
1
+#import "RNNBottomTabsController+Helpers.h"
2
+#import "BottomTabsPresenterCreator.h"
3
+#import "BottomTabPresenterCreator.h"
4
+
5
+@implementation RNNBottomTabsController (Helpers)
6
+
7
++ (RNNBottomTabsController *)create {
8
+	RNNNavigationOptions* defaultOptions = [[RNNNavigationOptions alloc] initEmptyOptions];
9
+	return [self createWithChildren:nil];
10
+}
11
+
12
++ (RNNBottomTabsController *)createWithChildren:(NSArray *)children {
13
+	RNNNavigationOptions* defaultOptions = [[RNNNavigationOptions alloc] initEmptyOptions];
14
+	return [[RNNBottomTabsController alloc] initWithLayoutInfo:nil creator:nil options:[[RNNNavigationOptions alloc] initEmptyOptions] defaultOptions:defaultOptions presenter:[BottomTabsPresenterCreator createWithDefaultOptions:defaultOptions] bottomTabPresenter:[BottomTabPresenterCreator createWithDefaultOptions:defaultOptions children:children] dotIndicatorPresenter:[[RNNDotIndicatorPresenter alloc] initWithDefaultOptions:defaultOptions] eventEmitter:nil childViewControllers:children bottomTabsAttacher:nil];
15
+}
16
+
17
+@end

+ 3
- 3
playground/ios/NavigationTests/RNNCommandsHandlerTest.m View File

@@ -372,7 +372,7 @@
372 372
 	options.bottomTabs.tabsAttachMode = [[BottomTabsAttachMode alloc] initWithValue:@"together"];
373 373
 
374 374
 	BottomTabsBaseAttacher* attacher = [[[BottomTabsAttachModeFactory alloc] initWithDefaultOptions:nil] fromOptions:options];
375
-	RNNBottomTabsController* tabBarController = [[RNNBottomTabsController alloc] initWithLayoutInfo:nil creator:nil options:options defaultOptions:[[RNNNavigationOptions alloc] initEmptyOptions] presenter:[RNNBasePresenter new] eventEmitter:_eventEmmiter childViewControllers:@[_vc1, _vc2] bottomTabsAttacher:attacher];
375
+	RNNBottomTabsController* tabBarController = [[RNNBottomTabsController alloc] initWithLayoutInfo:nil creator:nil options:options defaultOptions:[[RNNNavigationOptions alloc] initEmptyOptions] presenter:[RNNBasePresenter new] bottomTabPresenter:nil dotIndicatorPresenter:nil eventEmitter:_eventEmmiter childViewControllers:@[_vc1, _vc2] bottomTabsAttacher:attacher];
376 376
 
377 377
 	OCMStub([self.controllerFactory createLayout:[OCMArg any]]).andReturn(tabBarController);
378 378
 	
@@ -390,7 +390,7 @@
390 390
 	options.animations.setRoot.waitForRender = [[Bool alloc] initWithBOOL:YES];
391 391
 	
392 392
 	BottomTabsBaseAttacher* attacher = [[[BottomTabsAttachModeFactory alloc] initWithDefaultOptions:nil] fromOptions:options];
393
-	RNNBottomTabsController* tabBarController = [[RNNBottomTabsController alloc] initWithLayoutInfo:nil creator:nil options:options defaultOptions:[[RNNNavigationOptions alloc] initEmptyOptions] presenter:[RNNBasePresenter new] eventEmitter:_eventEmmiter childViewControllers:@[_vc1, _vc2] bottomTabsAttacher:attacher];
393
+	RNNBottomTabsController* tabBarController = [[RNNBottomTabsController alloc] initWithLayoutInfo:nil creator:nil options:options defaultOptions:[[RNNNavigationOptions alloc] initEmptyOptions] presenter:[RNNBasePresenter new] bottomTabPresenter:nil dotIndicatorPresenter:nil eventEmitter:_eventEmmiter childViewControllers:@[_vc1, _vc2] bottomTabsAttacher:attacher];
394 394
 
395 395
 	OCMStub([self.controllerFactory createLayout:[OCMArg any]]).andReturn(tabBarController);
396 396
 	
@@ -408,7 +408,7 @@
408 408
 	options.animations.setRoot.waitForRender = [[Bool alloc] initWithBOOL:YES];
409 409
 
410 410
 	BottomTabsBaseAttacher* attacher = [[[BottomTabsAttachModeFactory alloc] initWithDefaultOptions:nil] fromOptions:options];
411
-	RNNBottomTabsController* tabBarController = [[RNNBottomTabsController alloc] initWithLayoutInfo:nil creator:nil options:options defaultOptions:[[RNNNavigationOptions alloc] initEmptyOptions] presenter:[RNNBasePresenter new] eventEmitter:_eventEmmiter childViewControllers:@[_vc1, _vc2] bottomTabsAttacher:attacher];
411
+	RNNBottomTabsController* tabBarController = [[RNNBottomTabsController alloc] initWithLayoutInfo:nil creator:nil options:options defaultOptions:[[RNNNavigationOptions alloc] initEmptyOptions] presenter:[RNNBasePresenter new] bottomTabPresenter:nil dotIndicatorPresenter:nil eventEmitter:_eventEmmiter childViewControllers:@[_vc1, _vc2] bottomTabsAttacher:attacher];
412 412
 
413 413
 	OCMStub([self.controllerFactory createLayout:[OCMArg any]]).andReturn(tabBarController);
414 414
 

+ 13
- 4
playground/ios/NavigationTests/RNNDotIndicatorPresenterTest.m View File

@@ -3,23 +3,26 @@
3 3
 #import "RNNDotIndicatorPresenter.h"
4 4
 #import "DotIndicatorOptions.h"
5 5
 #import "RNNBottomTabsController.h"
6
-#import "RNNComponentViewController.h"
6
+#import <ReactNativeNavigation/RNNComponentViewController.h>
7 7
 #import "RNNTestBase.h"
8 8
 #import "UITabBarController+RNNUtils.h"
9
+#import <ReactNativeNavigation/BottomTabPresenterCreator.h>
10
+#import "RNNBottomTabsController+Helpers.h"
9 11
 
10 12
 @interface RNNDotIndicatorPresenterTest : RNNTestBase
11 13
 @property(nonatomic, strong) id uut;
12 14
 @property(nonatomic, strong) RNNComponentViewController *child;
13 15
 @property(nonatomic, strong) id bottomTabs;
16
+@property(nonatomic, strong) BottomTabPresenter* bottomTabPresenter;
14 17
 @end
15 18
 
16 19
 @implementation RNNDotIndicatorPresenterTest
17 20
 - (void)setUp {
18 21
     [super setUp];
22
+	self.child = [self createChild];
23
+	self.bottomTabPresenter = [BottomTabPresenterCreator createWithDefaultOptions:nil children:@[self.child]];
19 24
     self.uut = [OCMockObject partialMockForObject:[RNNDotIndicatorPresenter new]];
20
-    self.bottomTabs = [OCMockObject partialMockForObject:[RNNBottomTabsController new]];
21
-    self.child = [self createChild];
22
-    [self.bottomTabs addChildViewController:self.child];
25
+    self.bottomTabs = [OCMockObject partialMockForObject:[RNNBottomTabsController createWithChildren:@[self.child]]];
23 26
 
24 27
     [self setupTopLevelUI:self.bottomTabs];
25 28
 }
@@ -128,6 +131,12 @@
128 131
     XCTAssertEqual([sizeConstraints[1] constant], 8);
129 132
 }
130 133
 
134
+- (void)testApply_onBottomTabsViewDidLayout {
135
+	[[self.uut expect] apply:self.child :self.child.resolveOptions.bottomTab.dotIndicator];
136
+	[self.uut bottomTabsDidLayoutSubviews:self.bottomTabs];
137
+	[self.uut verify];
138
+}
139
+
131 140
 - (void)applyIndicator {
132 141
     [self applyIndicator:[UIColor redColor]];
133 142
 }

+ 7
- 8
playground/ios/NavigationTests/RNNRootViewControllerTest.m View File

@@ -8,7 +8,7 @@
8 8
 #import "RNNStackController.h"
9 9
 #import "RNNBottomTabsController.h"
10 10
 #import "RNNUIBarButtonItem.h"
11
-
11
+#import "RNNBottomTabsController+Helpers.h"
12 12
 
13 13
 @interface RNNComponentViewController (EmbedInTabBar)
14 14
 - (void)embedInTabBarController;
@@ -17,7 +17,7 @@
17 17
 @implementation RNNComponentViewController (EmbedInTabBar)
18 18
 
19 19
 - (void)embedInTabBarController {
20
-	RNNBottomTabsController* tabVC = [[RNNBottomTabsController alloc] init];
20
+	RNNBottomTabsController* tabVC = [RNNBottomTabsController create];
21 21
 	tabVC.viewControllers = @[self];
22 22
 	[self viewWillAppear:false];
23 23
 }
@@ -179,13 +179,12 @@
179 179
 - (void)testTabBadge {
180 180
 	NSString* tabBadgeInput = @"5";
181 181
 	self.options.bottomTab.badge = [[Text alloc] initWithValue:tabBadgeInput];
182
-	__unused RNNBottomTabsController* vc = [[RNNBottomTabsController alloc] init];
183 182
 	NSMutableArray* controllers = [NSMutableArray new];
184 183
 	UITabBarItem* item = [[UITabBarItem alloc] initWithTitle:@"A Tab" image:nil tag:1];
185 184
 	[self.uut setTabBarItem:item];
186 185
 	[controllers addObject:self.uut];
187
-	[vc setViewControllers:controllers];
188
-	[self.uut viewWillAppear:false];
186
+	__unused RNNBottomTabsController* vc = [RNNBottomTabsController createWithChildren:controllers];
187
+	[self.uut willMoveToParentViewController:vc];
189 188
 	XCTAssertTrue([self.uut.tabBarItem.badgeValue isEqualToString:tabBadgeInput]);
190 189
 
191 190
 }
@@ -371,7 +370,7 @@
371 370
 	NSArray* supportedOrientations = @[@"portrait"];
372 371
 	self.options.layout.orientation = supportedOrientations;
373 372
 	NSMutableArray* controllers = [[NSMutableArray alloc] initWithArray:@[self.uut]];
374
-    __unused RNNBottomTabsController* vc = [[RNNBottomTabsController alloc] initWithLayoutInfo:nil creator:nil options:[[RNNNavigationOptions alloc] initEmptyOptions] defaultOptions:nil presenter:[RNNComponentPresenter new] eventEmitter:nil childViewControllers:controllers];
373
+    __unused RNNBottomTabsController* vc = [RNNBottomTabsController createWithChildren:controllers];
375 374
 
376 375
 	[self.uut viewWillAppear:false];
377 376
 
@@ -383,7 +382,7 @@
383 382
 	NSArray* supportedOrientations = @[@"portrait", @"landscape"];
384 383
 	self.options.layout.orientation = supportedOrientations;
385 384
     NSMutableArray* controllers = [[NSMutableArray alloc] initWithArray:@[self.uut]];
386
-    __unused RNNBottomTabsController* vc = [[RNNBottomTabsController alloc] initWithLayoutInfo:nil creator:nil options:[[RNNNavigationOptions alloc] initEmptyOptions] defaultOptions:nil presenter:[RNNComponentPresenter new] eventEmitter:nil childViewControllers:controllers];
385
+    __unused RNNBottomTabsController* vc = [RNNBottomTabsController createWithChildren:controllers];
387 386
 
388 387
 	[self.uut viewWillAppear:false];
389 388
 
@@ -395,7 +394,7 @@
395 394
 	NSArray* supportedOrientations = @[@"all"];
396 395
 	self.options.layout.orientation = supportedOrientations;
397 396
 	NSMutableArray* controllers = [[NSMutableArray alloc] initWithArray:@[self.uut]];
398
-	__unused RNNBottomTabsController* vc = [[RNNBottomTabsController alloc] initWithLayoutInfo:nil creator:nil options:[[RNNNavigationOptions alloc] initEmptyOptions] defaultOptions:nil presenter:[RNNComponentPresenter new] eventEmitter:nil childViewControllers:controllers];
397
+	__unused RNNBottomTabsController* vc = [RNNBottomTabsController createWithChildren:controllers];
399 398
 
400 399
 	[self.uut viewWillAppear:false];
401 400
 

+ 17
- 4
playground/ios/NavigationTests/RNNTabBarControllerTest.m View File

@@ -1,9 +1,11 @@
1 1
 #import <XCTest/XCTest.h>
2
-#import "RNNBottomTabsController.h"
3
-#import "RNNComponentViewController.h"
2
+#import <ReactNativeNavigation/RNNBottomTabsController.h>
3
+#import <ReactNativeNavigation/RNNComponentViewController.h>
4 4
 #import "RNNStackController.h"
5 5
 #import <OCMock/OCMock.h>
6 6
 #import "RCTConvert+Modal.h"
7
+#import <ReactNativeNavigation/BottomTabPresenterCreator.h>
8
+#import "RNNBottomTabsController+Helpers.h"
7 9
 
8 10
 @interface RNNTabBarControllerTest : XCTestCase
9 11
 
@@ -22,11 +24,11 @@
22 24
 
23 25
     id tabBarClassMock = OCMClassMock([RNNBottomTabsController class]);
24 26
     OCMStub([tabBarClassMock parentViewController]).andReturn([OCMockObject partialMockForObject:[RNNBottomTabsController new]]);
25
-
27
+	NSArray* children = @[[[UIViewController alloc] init]];
26 28
     self.mockTabBarPresenter = [OCMockObject partialMockForObject:[[RNNBottomTabsPresenter alloc] init]];
27 29
     self.mockChildViewController = [OCMockObject partialMockForObject:[RNNComponentViewController new]];
28 30
     self.mockEventEmitter = [OCMockObject partialMockForObject:[RNNEventEmitter new]];
29
-	self.originalUut = [[RNNBottomTabsController alloc] initWithLayoutInfo:nil creator:nil options:[[RNNNavigationOptions alloc] initWithDict:@{}] defaultOptions:nil presenter:self.mockTabBarPresenter eventEmitter:self.mockEventEmitter childViewControllers:@[[[UIViewController alloc] init]]];
31
+	self.originalUut = [[RNNBottomTabsController alloc] initWithLayoutInfo:nil creator:nil options:[[RNNNavigationOptions alloc] initWithDict:@{}] defaultOptions:nil presenter:self.mockTabBarPresenter bottomTabPresenter:[BottomTabPresenterCreator createWithDefaultOptions:nil children:children] dotIndicatorPresenter:[[RNNDotIndicatorPresenter alloc] initWithDefaultOptions:nil] eventEmitter:self.mockEventEmitter childViewControllers:children bottomTabsAttacher:nil];
30 32
     self.uut = [OCMockObject partialMockForObject:self.originalUut];
31 33
     OCMStub([self.uut selectedViewController]).andReturn(self.mockChildViewController);
32 34
 }
@@ -171,5 +173,16 @@
171 173
     XCTAssertTrue(uut.selectedIndex == 1);
172 174
 }
173 175
 
176
+- (void)testOnViewDidLayoutSubviews_ShouldUpdateDotIndicatorForChildren {
177
+	id dotIndicator = [OCMockObject partialMockForObject:[[RNNDotIndicatorPresenter alloc] initWithDefaultOptions:nil]];
178
+    RNNComponentViewController *vc = [[RNNComponentViewController alloc] initWithLayoutInfo:nil rootViewCreator:nil eventEmitter:nil presenter:nil options:nil defaultOptions:nil];
179
+	RNNBottomTabsController *uut = [[RNNBottomTabsController alloc] initWithLayoutInfo:nil creator:nil options:nil defaultOptions:nil presenter:nil bottomTabPresenter:nil dotIndicatorPresenter:dotIndicator eventEmitter:nil childViewControllers:@[[UIViewController new], vc] bottomTabsAttacher:nil];
180
+	
181
+	[[dotIndicator expect] bottomTabsDidLayoutSubviews:uut];
182
+	[uut viewDidLayoutSubviews];
183
+	[dotIndicator verify];
184
+	
185
+}
186
+
174 187
 
175 188
 @end

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

@@ -11,6 +11,7 @@
11 11
 		13B07FBD1A68108700A75B9A /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB11A68108700A75B9A /* LaunchScreen.xib */; };
12 12
 		13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB51A68108700A75B9A /* Images.xcassets */; };
13 13
 		13B07FC11A68108700A75B9A /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB71A68108700A75B9A /* main.m */; };
14
+		500E9FE72406A52100C61231 /* BottomTabPresenterTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 500E9FE62406A4E200C61231 /* BottomTabPresenterTest.m */; };
14 15
 		501C86B9239FE9C400E0B631 /* UIImage+Utils.m in Sources */ = {isa = PBXBuildFile; fileRef = 501C86B8239FE9C400E0B631 /* UIImage+Utils.m */; };
15 16
 		5022EDCD2405522000852BA6 /* RNNBottomTabsPresenterTest.m in Sources */ = {isa = PBXBuildFile; fileRef = E58D263A2385888C003F36BA /* RNNBottomTabsPresenterTest.m */; };
16 17
 		5022EDCE2405524700852BA6 /* RNNBottomTabsAppearancePresenterTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 5022EDCB240551EE00852BA6 /* RNNBottomTabsAppearancePresenterTest.m */; };
@@ -19,6 +20,8 @@
19 20
 		50996C6823AA477400008F89 /* RNNRootViewControllerTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 50996C6723AA477400008F89 /* RNNRootViewControllerTest.m */; };
20 21
 		50996C6923AA487800008F89 /* RNNTestRootViewCreator.m in Sources */ = {isa = PBXBuildFile; fileRef = E58D263D2385888C003F36BA /* RNNTestRootViewCreator.m */; };
21 22
 		50BCB27623F1A2B100D6C8E5 /* TopBarAppearancePresenterTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 50BCB27523F1A26600D6C8E5 /* TopBarAppearancePresenterTest.m */; };
23
+		50C9A8D1240EB95F00BD699F /* RNNBottomTabsController+Helpers.m in Sources */ = {isa = PBXBuildFile; fileRef = 50CF233C240695B10098042D /* RNNBottomTabsController+Helpers.m */; };
24
+		50CF233D240695B10098042D /* RNNBottomTabsController+Helpers.m in Sources */ = {isa = PBXBuildFile; fileRef = 50CF233C240695B10098042D /* RNNBottomTabsController+Helpers.m */; };
22 25
 		50C9A8D4240FB9D000BD699F /* RNNComponentViewController+Utils.m in Sources */ = {isa = PBXBuildFile; fileRef = 50C9A8D3240FB9D000BD699F /* RNNComponentViewController+Utils.m */; };
23 26
 		67C681D42B662A53F29C19DA /* Pods_NavigationIOS12Tests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DEE0B5D45FD34FBABC6586CF /* Pods_NavigationIOS12Tests.framework */; };
24 27
 		9D204F3DC4FBCD81583BF99F /* Pods_playground.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4A3340545EAAF11C1F146864 /* Pods_playground.framework */; };
@@ -81,6 +84,7 @@
81 84
 		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>"; };
82 85
 		4A3340545EAAF11C1F146864 /* Pods_playground.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_playground.framework; sourceTree = BUILT_PRODUCTS_DIR; };
83 86
 		4AE37ACF6BFBAB211EE8E7E9 /* Pods-NavigationIOS12Tests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-NavigationIOS12Tests.release.xcconfig"; path = "Target Support Files/Pods-NavigationIOS12Tests/Pods-NavigationIOS12Tests.release.xcconfig"; sourceTree = "<group>"; };
87
+		500E9FE62406A4E200C61231 /* BottomTabPresenterTest.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = BottomTabPresenterTest.m; sourceTree = "<group>"; };
84 88
 		501C86B7239FE9C400E0B631 /* UIImage+Utils.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "UIImage+Utils.h"; sourceTree = "<group>"; };
85 89
 		501C86B8239FE9C400E0B631 /* UIImage+Utils.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "UIImage+Utils.m"; sourceTree = "<group>"; };
86 90
 		5022EDCB240551EE00852BA6 /* RNNBottomTabsAppearancePresenterTest.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = RNNBottomTabsAppearancePresenterTest.m; sourceTree = "<group>"; };
@@ -93,6 +97,8 @@
93 97
 		50996C6123AA46DD00008F89 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
94 98
 		50996C6723AA477400008F89 /* RNNRootViewControllerTest.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = RNNRootViewControllerTest.m; sourceTree = "<group>"; };
95 99
 		50BCB27523F1A26600D6C8E5 /* TopBarAppearancePresenterTest.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = TopBarAppearancePresenterTest.m; sourceTree = "<group>"; };
100
+		50CF233B240695B10098042D /* RNNBottomTabsController+Helpers.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "RNNBottomTabsController+Helpers.h"; sourceTree = "<group>"; };
101
+		50CF233C240695B10098042D /* RNNBottomTabsController+Helpers.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "RNNBottomTabsController+Helpers.m"; sourceTree = "<group>"; };
96 102
 		50C9A8D2240FB9D000BD699F /* RNNComponentViewController+Utils.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "RNNComponentViewController+Utils.h"; sourceTree = "<group>"; };
97 103
 		50C9A8D3240FB9D000BD699F /* RNNComponentViewController+Utils.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "RNNComponentViewController+Utils.m"; sourceTree = "<group>"; };
98 104
 		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>"; };
@@ -255,6 +261,7 @@
255 261
 			children = (
256 262
 				E58D26442385888C003F36BA /* Options */,
257 263
 				E58D26422385888C003F36BA /* RNNBasePresenterTest.m */,
264
+				500E9FE62406A4E200C61231 /* BottomTabPresenterTest.m */,
258 265
 				50BCB27523F1A26600D6C8E5 /* TopBarAppearancePresenterTest.m */,
259 266
 				E58D26412385888C003F36BA /* RNNCommandsHandlerTest.m */,
260 267
 				E58D26352385888B003F36BA /* RNNComponentPresenterTest.m */,
@@ -282,6 +289,8 @@
282 289
 				E58D263B2385888C003F36BA /* UITabBarController+RNNOptionsTest.m */,
283 290
 				E58D26252385888B003F36BA /* UIViewController+LayoutProtocolTest.m */,
284 291
 				E58D263E2385888C003F36BA /* UIViewController+RNNOptionsTest.m */,
292
+				50CF233B240695B10098042D /* RNNBottomTabsController+Helpers.h */,
293
+				50CF233C240695B10098042D /* RNNBottomTabsController+Helpers.m */,
285 294
 				50647FE223E3196800B92025 /* RNNExternalViewControllerTests.m */,
286 295
 				E58D262E2385888B003F36BA /* utils */,
287 296
 				E58D261F238587F4003F36BA /* Info.plist */,
@@ -740,6 +749,7 @@
740 749
 			buildActionMask = 2147483647;
741 750
 			files = (
742 751
 				5022EDCD2405522000852BA6 /* RNNBottomTabsPresenterTest.m in Sources */,
752
+				50C9A8D1240EB95F00BD699F /* RNNBottomTabsController+Helpers.m in Sources */,
743 753
 				50996C6923AA487800008F89 /* RNNTestRootViewCreator.m in Sources */,
744 754
 				50996C6823AA477400008F89 /* RNNRootViewControllerTest.m in Sources */,
745 755
 			);
@@ -773,7 +783,9 @@
773 783
 				E58D26502385888C003F36BA /* RNNNavigationOptionsTest.m in Sources */,
774 784
 				E58D264E2385888C003F36BA /* RNNTestBase.m in Sources */,
775 785
 				5022EDCE2405524700852BA6 /* RNNBottomTabsAppearancePresenterTest.m in Sources */,
786
+				500E9FE72406A52100C61231 /* BottomTabPresenterTest.m in Sources */,
776 787
 				E58D26612385888C003F36BA /* RNNTestNoColor.m in Sources */,
788
+				50CF233D240695B10098042D /* RNNBottomTabsController+Helpers.m in Sources */,
777 789
 				E58D265D2385888C003F36BA /* RNNControllerFactoryTest.m in Sources */,
778 790
 				E58D265C2385888C003F36BA /* RNNStackControllerTest.m in Sources */,
779 791
 				E58D26482385888C003F36BA /* RNNDotIndicatorPresenterTest.m in Sources */,