react-native-navigation 8.8.5 → 8.8.6-snapshot.2571
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/android/src/main/java/com/reactnativenavigation/NavigationActivity.java +14 -1
- package/android/src/main/java/com/reactnativenavigation/NavigationApplication.java +3 -0
- package/android/src/main/java/com/reactnativenavigation/NavigationPackage.kt +27 -8
- package/android/src/main/java/com/reactnativenavigation/customrow/BottomTabsCustomRow.kt +262 -0
- package/android/src/main/java/com/reactnativenavigation/customrow/BottomTabsCustomRowAttacher.kt +205 -0
- package/android/src/main/java/com/reactnativenavigation/customrow/BottomTabsCustomRowConfigStore.kt +32 -0
- package/android/src/main/java/com/reactnativenavigation/customrow/BottomTabsCustomRowLayout.kt +139 -0
- package/android/src/main/java/com/reactnativenavigation/customrow/BottomTabsCustomRowModule.kt +37 -0
- package/android/src/main/java/com/reactnativenavigation/customrow/BottomTabsCustomRowOptions.kt +68 -0
- package/android/src/main/java/com/reactnativenavigation/options/BottomTabOptions.java +4 -1
- package/android/src/main/java/com/reactnativenavigation/options/NavigationBarOptions.java +19 -1
- package/android/src/main/java/com/reactnativenavigation/react/ReactView.java +13 -0
- package/android/src/main/java/com/reactnativenavigation/react/events/ComponentType.java +2 -1
- package/android/src/main/java/com/reactnativenavigation/utils/SystemUiUtils.kt +63 -9
- package/android/src/main/java/com/reactnativenavigation/viewcontrollers/bottomtabs/BottomTabPresenter.java +28 -0
- package/android/src/main/java/com/reactnativenavigation/viewcontrollers/bottomtabs/BottomTabsController.java +77 -6
- package/android/src/main/java/com/reactnativenavigation/viewcontrollers/bottomtabs/BottomTabsPresenter.kt +1 -0
- package/android/src/main/java/com/reactnativenavigation/viewcontrollers/component/ComponentViewController.java +2 -5
- package/android/src/main/java/com/reactnativenavigation/viewcontrollers/viewcontroller/Presenter.java +33 -13
- package/android/src/main/java/com/reactnativenavigation/views/bottomtabs/BottomTabs.java +76 -0
- package/android/src/main/java/com/reactnativenavigation/views/bottomtabs/CustomBottomTabItemView.kt +73 -0
- package/android/src/test/java/com/reactnativenavigation/presentation/PresenterTest.java +14 -0
- package/android/src/test/java/com/reactnativenavigation/utils/SystemUiUtilsTest.kt +64 -1
- package/ios/ARCHITECTURE.md +5 -0
- package/ios/BottomTabPresenter.h +7 -0
- package/ios/BottomTabPresenter.mm +27 -0
- package/ios/RNNAppDelegate.h +16 -0
- package/ios/RNNAppDelegate.mm +73 -0
- package/ios/RNNBottomTabOptions.h +2 -0
- package/ios/RNNBottomTabOptions.mm +5 -1
- package/ios/RNNBottomTabsController.h +2 -0
- package/ios/RNNBottomTabsController.mm +209 -1
- package/ios/RNNBottomTabsCustomRow.h +57 -0
- package/ios/RNNBottomTabsCustomRow.mm +252 -0
- package/ios/RNNBottomTabsCustomRowOptions.h +42 -0
- package/ios/RNNBottomTabsCustomRowOptions.mm +37 -0
- package/ios/RNNBottomTabsOptions.h +2 -0
- package/ios/RNNBottomTabsOptions.mm +2 -0
- package/ios/RNNComponentViewCreator.h +2 -1
- package/ios/RNNCustomTabBarItemView.h +26 -0
- package/ios/RNNCustomTabBarItemView.mm +83 -0
- package/ios/RNNReactRootViewCreator.mm +1 -0
- package/ios/RNNViewControllerFactory.mm +1 -0
- package/ios/ReactNativeNavigation.xcodeproj/project.pbxproj +24 -0
- package/lib/module/ARCHITECTURE.md +30 -0
- package/lib/module/Navigation.js +34 -1
- package/lib/module/Navigation.js.map +1 -1
- package/lib/module/NavigationDelegate.js +21 -0
- package/lib/module/NavigationDelegate.js.map +1 -1
- package/lib/module/adapters/AndroidCustomRowForwarder.js +75 -0
- package/lib/module/adapters/AndroidCustomRowForwarder.js.map +1 -0
- package/lib/module/commands/Commands.js +8 -0
- package/lib/module/commands/Commands.js.map +1 -1
- package/lib/module/index.js +1 -0
- package/lib/module/index.js.map +1 -1
- package/lib/module/interfaces/Options.js.map +1 -1
- package/lib/module/linking/DeferredLinkQueue.js +52 -0
- package/lib/module/linking/DeferredLinkQueue.js.map +1 -0
- package/lib/module/linking/DeferredLinkQueue.test.js +54 -0
- package/lib/module/linking/DeferredLinkQueue.test.js.map +1 -0
- package/lib/module/linking/LinkingHandler.js +139 -0
- package/lib/module/linking/LinkingHandler.js.map +1 -0
- package/lib/module/linking/LinkingHandler.test.js +384 -0
- package/lib/module/linking/LinkingHandler.test.js.map +1 -0
- package/lib/module/linking/ModalLayoutBuilder.js +56 -0
- package/lib/module/linking/ModalLayoutBuilder.js.map +1 -0
- package/lib/module/linking/ModalLayoutBuilder.test.js +154 -0
- package/lib/module/linking/ModalLayoutBuilder.test.js.map +1 -0
- package/lib/module/linking/RouteMatcher.js +104 -0
- package/lib/module/linking/RouteMatcher.js.map +1 -0
- package/lib/module/linking/RouteMatcher.test.js +164 -0
- package/lib/module/linking/RouteMatcher.test.js.map +1 -0
- package/lib/module/linking/URLParser.js +56 -0
- package/lib/module/linking/URLParser.js.map +1 -0
- package/lib/module/linking/URLParser.test.js +100 -0
- package/lib/module/linking/URLParser.test.js.map +1 -0
- package/lib/module/linking/types.js +4 -0
- package/lib/module/linking/types.js.map +1 -0
- package/lib/typescript/Navigation.d.ts +22 -0
- package/lib/typescript/Navigation.d.ts.map +1 -1
- package/lib/typescript/NavigationDelegate.d.ts +13 -0
- package/lib/typescript/NavigationDelegate.d.ts.map +1 -1
- package/lib/typescript/adapters/AndroidCustomRowForwarder.d.ts +23 -0
- package/lib/typescript/adapters/AndroidCustomRowForwarder.d.ts.map +1 -0
- package/lib/typescript/commands/Commands.d.ts +1 -0
- package/lib/typescript/commands/Commands.d.ts.map +1 -1
- package/lib/typescript/index.d.ts +1 -0
- package/lib/typescript/index.d.ts.map +1 -1
- package/lib/typescript/interfaces/Options.d.ts +90 -0
- package/lib/typescript/interfaces/Options.d.ts.map +1 -1
- package/lib/typescript/linking/DeferredLinkQueue.d.ts +26 -0
- package/lib/typescript/linking/DeferredLinkQueue.d.ts.map +1 -0
- package/lib/typescript/linking/DeferredLinkQueue.test.d.ts +2 -0
- package/lib/typescript/linking/DeferredLinkQueue.test.d.ts.map +1 -0
- package/lib/typescript/linking/LinkingHandler.d.ts +71 -0
- package/lib/typescript/linking/LinkingHandler.d.ts.map +1 -0
- package/lib/typescript/linking/LinkingHandler.test.d.ts +2 -0
- package/lib/typescript/linking/LinkingHandler.test.d.ts.map +1 -0
- package/lib/typescript/linking/ModalLayoutBuilder.d.ts +21 -0
- package/lib/typescript/linking/ModalLayoutBuilder.d.ts.map +1 -0
- package/lib/typescript/linking/ModalLayoutBuilder.test.d.ts +2 -0
- package/lib/typescript/linking/ModalLayoutBuilder.test.d.ts.map +1 -0
- package/lib/typescript/linking/RouteMatcher.d.ts +23 -0
- package/lib/typescript/linking/RouteMatcher.d.ts.map +1 -0
- package/lib/typescript/linking/RouteMatcher.test.d.ts +2 -0
- package/lib/typescript/linking/RouteMatcher.test.d.ts.map +1 -0
- package/lib/typescript/linking/URLParser.d.ts +16 -0
- package/lib/typescript/linking/URLParser.d.ts.map +1 -0
- package/lib/typescript/linking/URLParser.test.d.ts +2 -0
- package/lib/typescript/linking/URLParser.test.d.ts.map +1 -0
- package/lib/typescript/linking/types.d.ts +107 -0
- package/lib/typescript/linking/types.d.ts.map +1 -0
- package/package.json +1 -1
- package/src/ARCHITECTURE.md +30 -0
- package/src/Navigation.ts +36 -1
- package/src/NavigationDelegate.ts +22 -0
- package/src/adapters/AndroidCustomRowForwarder.ts +83 -0
- package/src/commands/Commands.ts +15 -0
- package/src/index.ts +1 -0
- package/src/interfaces/Options.ts +92 -0
- package/src/linking/DeferredLinkQueue.test.ts +60 -0
- package/src/linking/DeferredLinkQueue.ts +55 -0
- package/src/linking/LinkingHandler.test.ts +332 -0
- package/src/linking/LinkingHandler.ts +169 -0
- package/src/linking/ModalLayoutBuilder.test.ts +105 -0
- package/src/linking/ModalLayoutBuilder.ts +60 -0
- package/src/linking/RouteMatcher.test.ts +128 -0
- package/src/linking/RouteMatcher.ts +126 -0
- package/src/linking/URLParser.test.ts +105 -0
- package/src/linking/URLParser.ts +62 -0
- package/src/linking/types.ts +115 -0
package/ios/RNNAppDelegate.mm
CHANGED
|
@@ -8,6 +8,8 @@
|
|
|
8
8
|
#import <React/RCTCxxBridgeDelegate.h>
|
|
9
9
|
#endif
|
|
10
10
|
#import <React/RCTLegacyViewManagerInteropComponentView.h>
|
|
11
|
+
#import <React/RCTLinkingManager.h>
|
|
12
|
+
#import <React/RCTRootView.h>
|
|
11
13
|
#import <React/RCTSurfacePresenter.h>
|
|
12
14
|
#if __has_include(<React/RCTSurfacePresenterStub.h>)
|
|
13
15
|
#import <React/RCTSurfacePresenterStub.h>
|
|
@@ -36,6 +38,13 @@
|
|
|
36
38
|
|
|
37
39
|
#import <React/RCTComponentViewFactory.h>
|
|
38
40
|
|
|
41
|
+
// Deep-link URLs that arrive (openURL, universal link, or external dispatch)
|
|
42
|
+
// before the React runtime is ready are queued here and flushed when Fabric
|
|
43
|
+
// posts `RCTContentDidAppearNotification` — by which point
|
|
44
|
+
// `RCTLinkingManager` is instantiated and JS subscribers are listening.
|
|
45
|
+
static NSMutableArray<NSURL *> *gRNNPendingDeepLinkURLs = nil;
|
|
46
|
+
static BOOL gRNNReactRuntimeReady = NO;
|
|
47
|
+
|
|
39
48
|
|
|
40
49
|
static NSString *const kRNConcurrentRoot = @"concurrentRoot";
|
|
41
50
|
|
|
@@ -92,9 +101,73 @@ didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
|
|
|
92
101
|
[ReactNativeNavigation bootstrapWithHost:self.reactNativeFactory.rootViewFactory.reactHost];
|
|
93
102
|
#endif
|
|
94
103
|
|
|
104
|
+
[self rnn_installDeepLinkObservers];
|
|
105
|
+
|
|
95
106
|
return YES;
|
|
96
107
|
}
|
|
97
108
|
|
|
109
|
+
#pragma mark - Deep linking
|
|
110
|
+
|
|
111
|
+
// Forward OS-delivered custom-scheme URLs to React Native's Linking module.
|
|
112
|
+
- (BOOL)application:(UIApplication *)application
|
|
113
|
+
openURL:(NSURL *)url
|
|
114
|
+
options:(NSDictionary<UIApplicationOpenURLOptionsKey, id> *)options {
|
|
115
|
+
[self dispatchDeepLinkURL:url];
|
|
116
|
+
return YES;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Forward universal links (associated domains) to React Native's Linking
|
|
120
|
+
// module by extracting the underlying https URL and routing it through the
|
|
121
|
+
// same pre-bridge queue as everything else.
|
|
122
|
+
- (BOOL)application:(UIApplication *)application
|
|
123
|
+
continueUserActivity:(NSUserActivity *)userActivity
|
|
124
|
+
restorationHandler:
|
|
125
|
+
(void (^)(NSArray<id<UIUserActivityRestoring>> *_Nullable))restorationHandler {
|
|
126
|
+
if ([userActivity.activityType isEqualToString:NSUserActivityTypeBrowsingWeb]) {
|
|
127
|
+
[self dispatchDeepLinkURL:userActivity.webpageURL];
|
|
128
|
+
return YES;
|
|
129
|
+
}
|
|
130
|
+
return NO;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
- (void)dispatchDeepLinkURL:(NSURL *)url {
|
|
134
|
+
if (url == nil) {
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
if (gRNNReactRuntimeReady) {
|
|
138
|
+
[RCTLinkingManager application:[UIApplication sharedApplication]
|
|
139
|
+
openURL:url
|
|
140
|
+
options:@{}];
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
if (gRNNPendingDeepLinkURLs == nil) {
|
|
144
|
+
gRNNPendingDeepLinkURLs = [NSMutableArray array];
|
|
145
|
+
}
|
|
146
|
+
[gRNNPendingDeepLinkURLs addObject:url];
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
- (void)rnn_installDeepLinkObservers {
|
|
150
|
+
// `RCTContentDidAppearNotification` is posted by Fabric's root view
|
|
151
|
+
// once content has rendered. RNN forces bridgeless/new-arch, so the
|
|
152
|
+
// legacy `RCTJavaScriptDidLoadNotification` never fires; we rely on
|
|
153
|
+
// this Fabric signal exclusively.
|
|
154
|
+
[[NSNotificationCenter defaultCenter] addObserver:self
|
|
155
|
+
selector:@selector(rnn_handleReactRuntimeReady:)
|
|
156
|
+
name:RCTContentDidAppearNotification
|
|
157
|
+
object:nil];
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
- (void)rnn_handleReactRuntimeReady:(NSNotification *)notification {
|
|
161
|
+
gRNNReactRuntimeReady = YES;
|
|
162
|
+
NSArray<NSURL *> *pending = [gRNNPendingDeepLinkURLs copy];
|
|
163
|
+
[gRNNPendingDeepLinkURLs removeAllObjects];
|
|
164
|
+
for (NSURL *url in pending) {
|
|
165
|
+
[RCTLinkingManager application:[UIApplication sharedApplication]
|
|
166
|
+
openURL:url
|
|
167
|
+
options:@{}];
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
98
171
|
|
|
99
172
|
#if !RNN_RN_VERSION_79_OR_NEWER
|
|
100
173
|
- (NSURL *)sourceURLForBridge:(RCTBridge *)bridge {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
#import "RNNComponentOptions.h"
|
|
1
2
|
#import "RNNOptions.h"
|
|
2
3
|
|
|
3
4
|
@class DotIndicatorOptions;
|
|
@@ -5,6 +6,7 @@
|
|
|
5
6
|
@interface RNNBottomTabOptions : RNNOptions
|
|
6
7
|
|
|
7
8
|
@property(nonatomic) NSUInteger tag;
|
|
9
|
+
@property(nonatomic, strong) RNNComponentOptions *component;
|
|
8
10
|
@property(nonatomic, strong) Text *text;
|
|
9
11
|
@property(nonatomic, strong) Text *badge;
|
|
10
12
|
@property(nonatomic, strong) Color *badgeColor;
|
|
@@ -8,6 +8,9 @@
|
|
|
8
8
|
self = [super initWithDict:dict];
|
|
9
9
|
self.tag = arc4random();
|
|
10
10
|
|
|
11
|
+
self.component =
|
|
12
|
+
[[RNNComponentOptions alloc] initWithDict:[dict objectForKey:@"component"]];
|
|
13
|
+
|
|
11
14
|
self.text = [TextParser parse:dict key:@"text"];
|
|
12
15
|
self.badge = [TextParser parse:dict key:@"badge"];
|
|
13
16
|
self.fontFamily = [TextParser parse:dict key:@"fontFamily"];
|
|
@@ -38,6 +41,7 @@
|
|
|
38
41
|
|
|
39
42
|
- (void)mergeOptions:(RNNBottomTabOptions *)options {
|
|
40
43
|
[self.dotIndicator mergeOptions:options.dotIndicator];
|
|
44
|
+
[self.component mergeOptions:options.component];
|
|
41
45
|
|
|
42
46
|
if (options.text.hasValue)
|
|
43
47
|
self.text = options.text;
|
|
@@ -88,7 +92,7 @@
|
|
|
88
92
|
self.iconColor.hasValue || self.selectedIconColor.hasValue ||
|
|
89
93
|
self.selectedTextColor.hasValue || self.iconInsets.hasValue || self.textColor.hasValue ||
|
|
90
94
|
self.visible.hasValue || self.selectTabOnPress.hasValue || self.sfSymbol.hasValue ||
|
|
91
|
-
self.sfSelectedSymbol.hasValue || self.role.hasValue;
|
|
95
|
+
self.sfSelectedSymbol.hasValue || self.role.hasValue || self.component.hasValue;
|
|
92
96
|
}
|
|
93
97
|
|
|
94
98
|
@end
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
#import "RNNBottomTabsPresenter.h"
|
|
4
4
|
#import "RNNDotIndicatorPresenter.h"
|
|
5
5
|
#import "RNNEventEmitter.h"
|
|
6
|
+
#import "RNNReactComponentRegistry.h"
|
|
6
7
|
#import "UIViewController+LayoutProtocol.h"
|
|
7
8
|
#import <UIKit/UIKit.h>
|
|
8
9
|
|
|
@@ -16,6 +17,7 @@
|
|
|
16
17
|
presenter:(RNNBasePresenter *)presenter
|
|
17
18
|
bottomTabPresenter:(BottomTabPresenter *)bottomTabPresenter
|
|
18
19
|
dotIndicatorPresenter:(RNNDotIndicatorPresenter *)dotIndicatorPresenter
|
|
20
|
+
componentRegistry:(RNNReactComponentRegistry *)componentRegistry
|
|
19
21
|
eventEmitter:(RNNEventEmitter *)eventEmitter
|
|
20
22
|
childViewControllers:(NSArray *)childViewControllers
|
|
21
23
|
bottomTabsAttacher:(BottomTabsBaseAttacher *)bottomTabsAttacher;
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
#import "RNNBottomTabsController.h"
|
|
2
|
+
#import "RNNBottomTabsCustomRow.h"
|
|
3
|
+
#import "RNNCustomTabBarItemView.h"
|
|
2
4
|
#import "RNNTabBarItemCreator.h"
|
|
3
5
|
#import "UITabBarController+RNNOptions.h"
|
|
4
6
|
#import "UITabBarController+RNNUtils.h"
|
|
7
|
+
#import <React/RCTLog.h>
|
|
5
8
|
|
|
6
9
|
@interface RNNBottomTabsController ()
|
|
7
10
|
@property(nonatomic, strong) BottomTabPresenter *bottomTabPresenter;
|
|
@@ -21,6 +24,10 @@
|
|
|
21
24
|
BOOL _didFinishSetup;
|
|
22
25
|
BOOL _rnnDidApplyInitialTabBarSelectionFix;
|
|
23
26
|
BOOL _rnnSuppressTabSelectionEvents;
|
|
27
|
+
RNNReactComponentRegistry *_componentRegistry;
|
|
28
|
+
NSMutableArray<RNNCustomTabBarItemView *> *_customTabItemViews;
|
|
29
|
+
BOOL _useCustomItemViews;
|
|
30
|
+
RNNBottomTabsCustomRow *_customRow;
|
|
24
31
|
}
|
|
25
32
|
|
|
26
33
|
- (instancetype)initWithLayoutInfo:(RNNLayoutInfo *)layoutInfo
|
|
@@ -30,14 +37,18 @@
|
|
|
30
37
|
presenter:(RNNBasePresenter *)presenter
|
|
31
38
|
bottomTabPresenter:(BottomTabPresenter *)bottomTabPresenter
|
|
32
39
|
dotIndicatorPresenter:(RNNDotIndicatorPresenter *)dotIndicatorPresenter
|
|
40
|
+
componentRegistry:(RNNReactComponentRegistry *)componentRegistry
|
|
33
41
|
eventEmitter:(RNNEventEmitter *)eventEmitter
|
|
34
42
|
childViewControllers:(NSArray *)childViewControllers
|
|
35
43
|
bottomTabsAttacher:(BottomTabsBaseAttacher *)bottomTabsAttacher {
|
|
36
44
|
_bottomTabsAttacher = bottomTabsAttacher;
|
|
37
45
|
_bottomTabPresenter = bottomTabPresenter;
|
|
38
46
|
_dotIndicatorPresenter = dotIndicatorPresenter;
|
|
47
|
+
_componentRegistry = componentRegistry;
|
|
39
48
|
_options = options;
|
|
40
49
|
_didFinishSetup = NO;
|
|
50
|
+
_customTabItemViews = [NSMutableArray new];
|
|
51
|
+
_useCustomItemViews = NO;
|
|
41
52
|
|
|
42
53
|
IntNumber *currentTabIndex = options.bottomTabs.currentTabIndex;
|
|
43
54
|
if ([currentTabIndex hasValue]) {
|
|
@@ -88,14 +99,20 @@
|
|
|
88
99
|
[selectedChild pushViewController: [UIViewController new] animated:NO];
|
|
89
100
|
[selectedChild popViewControllerAnimated:NO];
|
|
90
101
|
}
|
|
102
|
+
|
|
103
|
+
if (_useCustomItemViews) {
|
|
104
|
+
[self ensureCustomRowAttached];
|
|
105
|
+
[self layoutCustomRow];
|
|
106
|
+
}
|
|
91
107
|
}
|
|
92
108
|
|
|
93
109
|
- (void)viewDidAppear:(BOOL)animated {
|
|
94
110
|
[super viewDidAppear:animated];
|
|
95
111
|
// iOS 26: first layout can misplace tab item titles; cycling selection (then restoring) forces a
|
|
96
112
|
// correct layout without user interaction. Defer so all tab children are in the hierarchy.
|
|
113
|
+
// Skipped when custom item views are active — the native tab bar visuals are hidden anyway.
|
|
97
114
|
if (@available(iOS 26.0, *)) {
|
|
98
|
-
if (!_rnnDidApplyInitialTabBarSelectionFix) {
|
|
115
|
+
if (!_useCustomItemViews && !_rnnDidApplyInitialTabBarSelectionFix) {
|
|
99
116
|
_rnnDidApplyInitialTabBarSelectionFix = YES;
|
|
100
117
|
__weak RNNBottomTabsController *weakSelf = self;
|
|
101
118
|
dispatch_async(dispatch_get_main_queue(), ^{
|
|
@@ -128,13 +145,182 @@
|
|
|
128
145
|
|
|
129
146
|
- (void)createTabBarItems:(NSArray<UIViewController *> *)childViewControllers {
|
|
130
147
|
_bottomTabPresenter.tabCreator.searchRoleUsed = NO;
|
|
148
|
+
[self resolveCustomItemViewMode:childViewControllers];
|
|
149
|
+
_bottomTabPresenter.useCustomItemViews = _useCustomItemViews;
|
|
131
150
|
for (UIViewController *child in childViewControllers) {
|
|
132
151
|
[_bottomTabPresenter applyOptions:child.resolveOptions child:child];
|
|
133
152
|
}
|
|
134
153
|
|
|
154
|
+
if (_useCustomItemViews) {
|
|
155
|
+
[self buildCustomTabItemViews:childViewControllers];
|
|
156
|
+
[self applyCustomItemViewsTabBarConfiguration];
|
|
157
|
+
[self ensureCustomRowAttached];
|
|
158
|
+
}
|
|
159
|
+
|
|
135
160
|
[self syncTabBarItemTestIDs];
|
|
136
161
|
}
|
|
137
162
|
|
|
163
|
+
- (void)applyCustomItemViewsTabBarConfiguration {
|
|
164
|
+
// Hide the native tab bar visuals so our custom row is the only thing
|
|
165
|
+
// shown. The bar itself stays in the view hierarchy so that
|
|
166
|
+
// `UITabBarController` keeps reserving the bottom safe-area inset for
|
|
167
|
+
// the selected child controller and exposes the right frame for the
|
|
168
|
+
// row to match.
|
|
169
|
+
for (UIView *subview in self.tabBar.subviews) {
|
|
170
|
+
subview.hidden = YES;
|
|
171
|
+
}
|
|
172
|
+
self.tabBar.tintColor = UIColor.clearColor;
|
|
173
|
+
self.tabBar.unselectedItemTintColor = UIColor.clearColor;
|
|
174
|
+
self.tabBar.backgroundColor = UIColor.clearColor;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
- (void)resolveCustomItemViewMode:(NSArray<UIViewController *> *)childViewControllers {
|
|
178
|
+
if (childViewControllers.count == 0) {
|
|
179
|
+
_useCustomItemViews = NO;
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
NSUInteger withComponent = 0;
|
|
184
|
+
for (UIViewController *child in childViewControllers) {
|
|
185
|
+
RNNNavigationOptions *resolved = child.resolveOptions;
|
|
186
|
+
if (resolved.bottomTab.component.name.hasValue) {
|
|
187
|
+
withComponent++;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
if (withComponent == 0) {
|
|
192
|
+
_useCustomItemViews = NO;
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
if (withComponent != childViewControllers.count) {
|
|
197
|
+
RCTLogWarn(
|
|
198
|
+
@"[RNN] Mixed bottomTab.component usage detected (%lu of %lu tabs). All tabs must "
|
|
199
|
+
@"declare a component or none — falling back to native rendering for all tabs.",
|
|
200
|
+
(unsigned long)withComponent, (unsigned long)childViewControllers.count);
|
|
201
|
+
_useCustomItemViews = NO;
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
_useCustomItemViews = YES;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
- (void)buildCustomTabItemViews:(NSArray<UIViewController *> *)childViewControllers {
|
|
209
|
+
[self destroyCustomTabItemViews];
|
|
210
|
+
|
|
211
|
+
NSString *parentComponentId = self.layoutInfo.componentId;
|
|
212
|
+
for (NSUInteger i = 0; i < childViewControllers.count; i++) {
|
|
213
|
+
UIViewController *child = childViewControllers[i];
|
|
214
|
+
RNNNavigationOptions *resolved = child.resolveOptions;
|
|
215
|
+
RNNComponentOptions *componentOptions = resolved.bottomTab.component;
|
|
216
|
+
|
|
217
|
+
RNNReactView *reactView =
|
|
218
|
+
[_componentRegistry createComponentIfNotExists:componentOptions
|
|
219
|
+
parentComponentId:parentComponentId
|
|
220
|
+
componentType:RNNComponentTypeBottomTabItem
|
|
221
|
+
reactViewReadyBlock:nil];
|
|
222
|
+
|
|
223
|
+
NSString *badge = [resolved.bottomTab.badge withDefault:nil];
|
|
224
|
+
RNNCustomTabBarItemView *itemView =
|
|
225
|
+
[[RNNCustomTabBarItemView alloc] initWithReactView:reactView
|
|
226
|
+
tabIndex:i
|
|
227
|
+
selected:(i == _currentTabIndex)
|
|
228
|
+
badge:badge];
|
|
229
|
+
[_customTabItemViews addObject:itemView];
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
- (void)destroyCustomTabItemViews {
|
|
234
|
+
for (RNNCustomTabBarItemView *itemView in _customTabItemViews) {
|
|
235
|
+
[itemView removeFromSuperview];
|
|
236
|
+
}
|
|
237
|
+
[_customTabItemViews removeAllObjects];
|
|
238
|
+
[_customRow removeFromSuperview];
|
|
239
|
+
_customRow = nil;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
- (void)ensureCustomRowAttached {
|
|
243
|
+
if (!_useCustomItemViews) {
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
246
|
+
if (!_customRow) {
|
|
247
|
+
_customRow = [[RNNBottomTabsCustomRow alloc] initWithFrame:CGRectZero];
|
|
248
|
+
__weak RNNBottomTabsController *weakSelf = self;
|
|
249
|
+
_customRow.onTapAtIndex = ^(NSUInteger index) {
|
|
250
|
+
[weakSelf handleCustomRowTapAtIndex:index];
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
[_customRow setItemViews:_customTabItemViews];
|
|
254
|
+
[_customRow applyOptions:_options.bottomTabs.customRow];
|
|
255
|
+
if (_customRow.superview != self.view) {
|
|
256
|
+
[self.view addSubview:_customRow];
|
|
257
|
+
} else {
|
|
258
|
+
[self.view bringSubviewToFront:_customRow];
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
- (void)layoutCustomRow {
|
|
263
|
+
if (!_useCustomItemViews || !_customRow) {
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
266
|
+
CGRect tabBarFrame = self.tabBar.frame;
|
|
267
|
+
if (CGRectIsEmpty(tabBarFrame)) {
|
|
268
|
+
return;
|
|
269
|
+
}
|
|
270
|
+
CGRect tabBarInView = [self.view convertRect:tabBarFrame fromView:self.tabBar.superview];
|
|
271
|
+
|
|
272
|
+
CGFloat desiredHeight = [_customRow
|
|
273
|
+
desiredRowHeightForNativeTabBarHeight:tabBarInView.size.height
|
|
274
|
+
safeBottom:0];
|
|
275
|
+
|
|
276
|
+
CGFloat bottomMargin = [_customRow effectiveBottomMargin];
|
|
277
|
+
CGFloat rowBottom;
|
|
278
|
+
UIWindow *window = self.view.window;
|
|
279
|
+
if (window) {
|
|
280
|
+
// Map the window's safe-area bottom into this controller's view — reliable
|
|
281
|
+
// for modals where `self.view.safeAreaInsets` is often zero.
|
|
282
|
+
CGFloat yInWindow = CGRectGetHeight(window.bounds) - window.safeAreaInsets.bottom;
|
|
283
|
+
CGPoint pInView = [self.view convertPoint:CGPointMake(0, yInWindow) fromView:window];
|
|
284
|
+
rowBottom = pInView.y - bottomMargin;
|
|
285
|
+
} else {
|
|
286
|
+
rowBottom = CGRectGetMaxY(self.view.safeAreaLayoutGuide.layoutFrame) - bottomMargin;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
CGRect rowFrame = CGRectMake(tabBarInView.origin.x, rowBottom - desiredHeight,
|
|
290
|
+
tabBarInView.size.width, desiredHeight);
|
|
291
|
+
|
|
292
|
+
_customRow.frame = rowFrame;
|
|
293
|
+
_customRow.hidden = self.tabBar.hidden;
|
|
294
|
+
[_customRow setSelectedIndex:_currentTabIndex];
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
- (void)handleCustomRowTapAtIndex:(NSUInteger)index {
|
|
298
|
+
if (index >= self.childViewControllers.count) {
|
|
299
|
+
return;
|
|
300
|
+
}
|
|
301
|
+
UIViewController *target = self.childViewControllers[index];
|
|
302
|
+
[self.eventEmitter sendBottomTabPressed:@(index)];
|
|
303
|
+
BOOL select = [[target resolveOptions].bottomTab.selectTabOnPress withDefault:YES];
|
|
304
|
+
if (!select) {
|
|
305
|
+
return;
|
|
306
|
+
}
|
|
307
|
+
NSUInteger previous = _currentTabIndex;
|
|
308
|
+
[self setSelectedIndex:index];
|
|
309
|
+
if (!_rnnSuppressTabSelectionEvents) {
|
|
310
|
+
[self.eventEmitter sendBottomTabSelected:@(index) unselected:@(previous)];
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
- (void)updateCustomTabItemSelection {
|
|
315
|
+
if (!_useCustomItemViews) {
|
|
316
|
+
return;
|
|
317
|
+
}
|
|
318
|
+
for (NSUInteger i = 0; i < _customTabItemViews.count; i++) {
|
|
319
|
+
[_customTabItemViews[i] setSelected:(i == _currentTabIndex)];
|
|
320
|
+
}
|
|
321
|
+
[_customRow setSelectedIndex:_currentTabIndex];
|
|
322
|
+
}
|
|
323
|
+
|
|
138
324
|
- (void)mergeChildOptions:(RNNNavigationOptions *)options child:(UIViewController *)child {
|
|
139
325
|
[super mergeChildOptions:options child:child];
|
|
140
326
|
UIViewController *childViewController = [self findViewController:child];
|
|
@@ -145,6 +331,13 @@
|
|
|
145
331
|
resolvedOptions:childViewController.resolveOptions
|
|
146
332
|
child:childViewController];
|
|
147
333
|
|
|
334
|
+
if (_useCustomItemViews && options.bottomTab.badge.hasValue) {
|
|
335
|
+
NSUInteger index = [self.childViewControllers indexOfObject:childViewController];
|
|
336
|
+
if (index != NSNotFound && index < _customTabItemViews.count) {
|
|
337
|
+
[_customTabItemViews[index] setBadge:[options.bottomTab.badge withDefault:nil]];
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
|
|
148
341
|
[self syncTabBarItemTestIDs];
|
|
149
342
|
}
|
|
150
343
|
|
|
@@ -161,6 +354,12 @@
|
|
|
161
354
|
[self syncTabBarItemTestIDs];
|
|
162
355
|
[self.presenter viewDidLayoutSubviews];
|
|
163
356
|
[_dotIndicatorPresenter bottomTabsDidLayoutSubviews:self];
|
|
357
|
+
if (_useCustomItemViews) {
|
|
358
|
+
// Re-hide native subviews; UIKit recreates them on bounds changes.
|
|
359
|
+
[self applyCustomItemViewsTabBarConfiguration];
|
|
360
|
+
[self ensureCustomRowAttached];
|
|
361
|
+
[self layoutCustomRow];
|
|
362
|
+
}
|
|
164
363
|
}
|
|
165
364
|
|
|
166
365
|
- (UIViewController *)getCurrentChild {
|
|
@@ -195,6 +394,7 @@
|
|
|
195
394
|
}
|
|
196
395
|
|
|
197
396
|
[super setSelectedIndex:_currentTabIndex];
|
|
397
|
+
[self updateCustomTabItemSelection];
|
|
198
398
|
}
|
|
199
399
|
|
|
200
400
|
- (UIViewController *)selectedViewController {
|
|
@@ -205,6 +405,7 @@
|
|
|
205
405
|
_previousTabIndex = _currentTabIndex;
|
|
206
406
|
_currentTabIndex = [self.childViewControllers indexOfObject:selectedViewController];
|
|
207
407
|
[super setSelectedViewController:selectedViewController];
|
|
408
|
+
[self updateCustomTabItemSelection];
|
|
208
409
|
}
|
|
209
410
|
|
|
210
411
|
- (void)setTabBarVisible:(BOOL)visible animated:(BOOL)animated {
|
|
@@ -283,4 +484,11 @@
|
|
|
283
484
|
return [self.presenter hidesBottomBarWhenPushed];
|
|
284
485
|
}
|
|
285
486
|
|
|
487
|
+
- (void)dealloc {
|
|
488
|
+
[self destroyCustomTabItemViews];
|
|
489
|
+
if (_componentRegistry && self.layoutInfo.componentId) {
|
|
490
|
+
[_componentRegistry clearComponentsForParentId:self.layoutInfo.componentId];
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
|
|
286
494
|
@end
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
#import "RNNBottomTabsCustomRowOptions.h"
|
|
2
|
+
#import "RNNCustomTabBarItemView.h"
|
|
3
|
+
#import <UIKit/UIKit.h>
|
|
4
|
+
|
|
5
|
+
NS_ASSUME_NONNULL_BEGIN
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Replaces the visual content of `UITabBar` when `bottomTab.component` is
|
|
9
|
+
* declared on every tab. Hosts the React-rendered cell views in equal-width
|
|
10
|
+
* slots and dispatches taps back to the bottom-tabs controller.
|
|
11
|
+
*
|
|
12
|
+
* The native `UITabBar` is kept (with its visuals hidden) so that
|
|
13
|
+
* `UITabBarController` keeps managing the bottom safe-area inset for the
|
|
14
|
+
* selected child controller — this row is laid out on top of it.
|
|
15
|
+
*
|
|
16
|
+
* Visual chrome (height, background, corner radius, margins) is configured
|
|
17
|
+
* via `RNNBottomTabsCustomRowOptions` and pushed in by the controller.
|
|
18
|
+
*/
|
|
19
|
+
@interface RNNBottomTabsCustomRow : UIView
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Block invoked when the user taps a cell. The argument is the 0-based index
|
|
23
|
+
* of the tapped cell.
|
|
24
|
+
*/
|
|
25
|
+
@property(nonatomic, copy, nullable) void (^onTapAtIndex)(NSUInteger index);
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Replaces the cells displayed by this row. The previously held views are
|
|
29
|
+
* removed from the hierarchy. Each cell is rendered at equal width inside
|
|
30
|
+
* the content rect (safe area is reserved in the row frame, not inset here).
|
|
31
|
+
*/
|
|
32
|
+
- (void)setItemViews:(NSArray<RNNCustomTabBarItemView *> *)itemViews;
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Forwards the selected state to each hosted item view.
|
|
36
|
+
*/
|
|
37
|
+
- (void)setSelectedIndex:(NSUInteger)selectedIndex;
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Applies user-supplied chrome options (background, corner radius, margins).
|
|
41
|
+
* Pass `nil` (or an options instance with no values) to use defaults.
|
|
42
|
+
*/
|
|
43
|
+
- (void)applyOptions:(nullable RNNBottomTabsCustomRowOptions *)options;
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Returns the chrome height (plus bottom margin). The controller positions
|
|
47
|
+
* the row above the home-indicator safe area.
|
|
48
|
+
*/
|
|
49
|
+
- (CGFloat)desiredRowHeightForNativeTabBarHeight:(CGFloat)nativeTabBarHeight
|
|
50
|
+
safeBottom:(CGFloat)safeBottom;
|
|
51
|
+
|
|
52
|
+
/** User `bottomMargin` option, or 0. */
|
|
53
|
+
- (CGFloat)effectiveBottomMargin;
|
|
54
|
+
|
|
55
|
+
@end
|
|
56
|
+
|
|
57
|
+
NS_ASSUME_NONNULL_END
|