From 0f9bc25816fedd8d0eca214f02c416fa5922a96a Mon Sep 17 00:00:00 2001 From: Konrad Michalik Date: Mon, 29 Sep 2025 14:07:45 +0200 Subject: [PATCH 1/4] Implement ScrollViewProviding --- ios/RNSScreen.mm | 5 ++- ios/RNSScreenStack.mm | 2 +- ios/RNSScrollViewFinder.h | 13 ------ ios/RNSScrollViewFinder.mm | 22 ---------- .../RNSBottomTabsScreenComponentView.mm | 5 ++- .../RNSTabsScreenViewController.mm | 2 +- .../RNSContentScrollViewProviding.h | 9 ++++ ios/helpers/scroll-view/RNSScrollViewFinder.h | 25 +++++++++++ .../scroll-view/RNSScrollViewFinder.mm | 41 +++++++++++++++++++ .../scroll-view}/RNSScrollViewHelper.h | 0 .../scroll-view}/RNSScrollViewHelper.mm | 2 +- 11 files changed, 84 insertions(+), 42 deletions(-) delete mode 100644 ios/RNSScrollViewFinder.h delete mode 100644 ios/RNSScrollViewFinder.mm create mode 100644 ios/helpers/scroll-view/RNSContentScrollViewProviding.h create mode 100644 ios/helpers/scroll-view/RNSScrollViewFinder.h create mode 100644 ios/helpers/scroll-view/RNSScrollViewFinder.mm rename ios/{ => helpers/scroll-view}/RNSScrollViewHelper.h (100%) rename ios/{ => helpers/scroll-view}/RNSScrollViewHelper.mm (79%) diff --git a/ios/RNSScreen.mm b/ios/RNSScreen.mm index c0801a3605..61398f8394 100644 --- a/ios/RNSScreen.mm +++ b/ios/RNSScreen.mm @@ -1242,8 +1242,9 @@ - (void)overrideScrollViewBehaviorInFirstDescendantChainIfNeeded - (void)updateContentScrollViewEdgeEffectsIfExists { - [RNSScrollEdgeEffectApplicator applyToScrollView:[RNSScrollViewFinder findScrollViewInFirstDescendantChainFrom:self] - withProvider:self]; + [RNSScrollEdgeEffectApplicator + applyToScrollView:[RNSScrollViewFinder findContentScrollViewWithFirstDescendantsChain:self] + withProvider:self]; } #pragma mark - RNSSafeAreaProviding diff --git a/ios/RNSScreenStack.mm b/ios/RNSScreenStack.mm index 957dac70c1..070b8034f6 100644 --- a/ios/RNSScreenStack.mm +++ b/ios/RNSScreenStack.mm @@ -194,7 +194,7 @@ - (bool)onRepeatedTabSelectionOfTabScreenController:(RNSTabsScreenViewController return [[self popToRootViewControllerAnimated:true] count] > 0; } else if (tabScreenController.tabScreenComponentView.shouldUseRepeatedTabSelectionScrollToTopSpecialEffect) { UIScrollView *scrollView = - [RNSScrollViewFinder findScrollViewInFirstDescendantChainFrom:[[self topViewController] view]]; + [RNSScrollViewFinder findContentScrollViewWithFirstDescendantsChain:[[self topViewController] view]]; return [scrollView rnscreens_scrollToTop]; } diff --git a/ios/RNSScrollViewFinder.h b/ios/RNSScrollViewFinder.h deleted file mode 100644 index 0a716402b5..0000000000 --- a/ios/RNSScrollViewFinder.h +++ /dev/null @@ -1,13 +0,0 @@ -#import - -@interface RNSScrollViewFinder : NSObject - -/** - * Finds UIScrollView by traversing down the hierarchy using first subview, similar to UIKit behavior. - * It will fail if: - * - UIScrollView is not a first subview of view or one of its descendants in the hierarchy, - * - if UIScrollView's parent is not yet attached. - */ -+ (nullable UIScrollView *)findScrollViewInFirstDescendantChainFrom:(nullable UIView *)view; - -@end diff --git a/ios/RNSScrollViewFinder.mm b/ios/RNSScrollViewFinder.mm deleted file mode 100644 index e44604a9b0..0000000000 --- a/ios/RNSScrollViewFinder.mm +++ /dev/null @@ -1,22 +0,0 @@ -#import "RNSScrollViewFinder.h" - -@implementation RNSScrollViewFinder - -+ (UIScrollView *)findScrollViewInFirstDescendantChainFrom:(UIView *)view -{ - UIView *currentView = view; - - while (currentView != nil) { - if ([currentView isKindOfClass:UIScrollView.class]) { - return static_cast(currentView); - } else if ([currentView.subviews count] > 0) { - currentView = currentView.subviews[0]; - } else { - break; - } - } - - return nil; -} - -@end diff --git a/ios/bottom-tabs/RNSBottomTabsScreenComponentView.mm b/ios/bottom-tabs/RNSBottomTabsScreenComponentView.mm index 58876cbb0d..668e6ba5c5 100644 --- a/ios/bottom-tabs/RNSBottomTabsScreenComponentView.mm +++ b/ios/bottom-tabs/RNSBottomTabsScreenComponentView.mm @@ -165,8 +165,9 @@ - (void)overrideScrollViewBehaviorInFirstDescendantChainIfNeeded - (void)updateContentScrollViewEdgeEffectsIfExists { - [RNSScrollEdgeEffectApplicator applyToScrollView:[RNSScrollViewFinder findScrollViewInFirstDescendantChainFrom:self] - withProvider:self]; + [RNSScrollEdgeEffectApplicator + applyToScrollView:[RNSScrollViewFinder findContentScrollViewWithFirstDescendantsChain:self] + withProvider:self]; } #pragma mark - Prop update utils diff --git a/ios/bottom-tabs/RNSTabsScreenViewController.mm b/ios/bottom-tabs/RNSTabsScreenViewController.mm index ba94d37395..c886ea6046 100644 --- a/ios/bottom-tabs/RNSTabsScreenViewController.mm +++ b/ios/bottom-tabs/RNSTabsScreenViewController.mm @@ -107,7 +107,7 @@ - (bool)tabScreenSelectedRepeatedly return [[self tabsSpecialEffectsDelegate] onRepeatedTabSelectionOfTabScreenController:self]; } else if (self.tabScreenComponentView.shouldUseRepeatedTabSelectionScrollToTopSpecialEffect) { UIScrollView *scrollView = - [RNSScrollViewFinder findScrollViewInFirstDescendantChainFrom:[self tabScreenComponentView]]; + [RNSScrollViewFinder findContentScrollViewWithFirstDescendantsChain:[self tabScreenComponentView]]; return [scrollView rnscreens_scrollToTop]; } diff --git a/ios/helpers/scroll-view/RNSContentScrollViewProviding.h b/ios/helpers/scroll-view/RNSContentScrollViewProviding.h new file mode 100644 index 0000000000..b8ace6cfad --- /dev/null +++ b/ios/helpers/scroll-view/RNSContentScrollViewProviding.h @@ -0,0 +1,9 @@ +@protocol RNSContentScrollViewProviding +/** + * Finds content ScrollView within provider's hierarchy. The content ScrollView serves as a main interaction on the + * given screen. Implementations should use `RNSScrollViewFinder.findContentScrollViewWithDelegatingToProvider` to + * continue continue the search when no more custom logic is necessary (e.g Stack determining the topmost screen (custom + * part) and forwarding the call directly to its view (going back to regular search)). + */ +- (nullable UIScrollView *)findContentScrollView; +@end diff --git a/ios/helpers/scroll-view/RNSScrollViewFinder.h b/ios/helpers/scroll-view/RNSScrollViewFinder.h new file mode 100644 index 0000000000..95c422658a --- /dev/null +++ b/ios/helpers/scroll-view/RNSScrollViewFinder.h @@ -0,0 +1,25 @@ +#import +#import "RNSContentScrollViewProviding.h" + +@interface RNSScrollViewFinder : NSObject +/** + * Searches for content ScrollView by traversing down the hierarchy using first subview, similar to UIKit behavior. + * It will fail if: + * - UIScrollView is not a first subview of view or one of its descendants in the hierarchy, + * - if UIScrollView's parent is not yet attached. + */ ++ (nullable UIScrollView *)findContentScrollViewWithFirstDescendantsChain:(nullable UIView *)view; + +/** + * Looks for UIScrollView in a similar way to `findContentScrollViewWithFirstDescendantsChain`, until it finds + * `RNSContentScrollViewProviding`. Then, it delegates the task to the provider, and returns the results. This can + * overcome the problems of subviews' children not being mounted yet, or ScrollView being mounted at index different + * than 0. + * + * Caveat: when traversing the hierarchy, we don't check for conformance to protocol, + * but whether the view responds to `RNSContentScrollViewProviding.findContentScrollView`. + * This doesn't place locks and is faster. + */ ++ (nullable UIScrollView *)findContentScrollViewWithDelegatingToProvider:(nullable UIView *)view; + +@end diff --git a/ios/helpers/scroll-view/RNSScrollViewFinder.mm b/ios/helpers/scroll-view/RNSScrollViewFinder.mm new file mode 100644 index 0000000000..1f858a8bea --- /dev/null +++ b/ios/helpers/scroll-view/RNSScrollViewFinder.mm @@ -0,0 +1,41 @@ +#import "RNSScrollViewFinder.h" + +@implementation RNSScrollViewFinder + ++ (UIScrollView *)findContentScrollViewWithFirstDescendantsChain:(UIView *)view +{ + UIView *currentView = view; + + while (currentView != nil) { + if ([currentView isKindOfClass:UIScrollView.class]) { + return static_cast(currentView); + } else if ([currentView.subviews count] > 0) { + currentView = currentView.subviews[0]; + } else { + break; + } + } + + return nil; +} + ++ (nullable UIScrollView *)findContentScrollViewWithDelegatingToProvider:(nullable UIView *)view +{ + UIView *currentView = view; + + while (currentView != nil) { + if ([currentView isKindOfClass:UIScrollView.class]) { + return static_cast(currentView); + } else if ([currentView respondsToSelector:@selector(findContentScrollView)]) { + return [static_cast>(currentView) findContentScrollView]; + } else if ([currentView.subviews count] > 0) { + currentView = currentView.subviews[0]; + } else { + break; + } + } + + return nil; +} + +@end diff --git a/ios/RNSScrollViewHelper.h b/ios/helpers/scroll-view/RNSScrollViewHelper.h similarity index 100% rename from ios/RNSScrollViewHelper.h rename to ios/helpers/scroll-view/RNSScrollViewHelper.h diff --git a/ios/RNSScrollViewHelper.mm b/ios/helpers/scroll-view/RNSScrollViewHelper.mm similarity index 79% rename from ios/RNSScrollViewHelper.mm rename to ios/helpers/scroll-view/RNSScrollViewHelper.mm index a0ebf406dd..907fbeb3eb 100644 --- a/ios/RNSScrollViewHelper.mm +++ b/ios/helpers/scroll-view/RNSScrollViewHelper.mm @@ -5,7 +5,7 @@ @implementation RNSScrollViewHelper + (void)overrideScrollViewBehaviorInFirstDescendantChainFrom:(nullable UIView *)view { - UIScrollView *scrollView = [RNSScrollViewFinder findScrollViewInFirstDescendantChainFrom:view]; + UIScrollView *scrollView = [RNSScrollViewFinder findContentScrollViewWithFirstDescendantsChain:view]; if ([scrollView contentInsetAdjustmentBehavior] == UIScrollViewContentInsetAdjustmentNever) { [scrollView setContentInsetAdjustmentBehavior:UIScrollViewContentInsetAdjustmentAutomatic]; From 0fcf2f341020399077bff8f9d8c59dee785e652f Mon Sep 17 00:00:00 2001 From: Konrad Michalik Date: Mon, 29 Sep 2025 14:56:33 +0200 Subject: [PATCH 2/4] Redo documentation for ContentScrollViewProviding --- ios/helpers/scroll-view/RNSContentScrollViewProviding.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ios/helpers/scroll-view/RNSContentScrollViewProviding.h b/ios/helpers/scroll-view/RNSContentScrollViewProviding.h index b8ace6cfad..d314d9a352 100644 --- a/ios/helpers/scroll-view/RNSContentScrollViewProviding.h +++ b/ios/helpers/scroll-view/RNSContentScrollViewProviding.h @@ -1,9 +1,9 @@ @protocol RNSContentScrollViewProviding + /** * Finds content ScrollView within provider's hierarchy. The content ScrollView serves as a main interaction on the - * given screen. Implementations should use `RNSScrollViewFinder.findContentScrollViewWithDelegatingToProvider` to - * continue continue the search when no more custom logic is necessary (e.g Stack determining the topmost screen (custom - * part) and forwarding the call directly to its view (going back to regular search)). + * given screen. Implementations may use `RNSScrollViewFinder` to continue the search however they see fit. */ - (nullable UIScrollView *)findContentScrollView; + @end From 565df0a6954413c31bb347a4a2d24ad800c11aea Mon Sep 17 00:00:00 2001 From: Konrad Michalik Date: Mon, 29 Sep 2025 15:16:13 +0200 Subject: [PATCH 3/4] Rename RNSScrollViewFinder methods & redo docs --- ios/RNSScreen.mm | 5 ++--- ios/RNSScreenStack.mm | 2 +- ios/bottom-tabs/RNSBottomTabsScreenComponentView.mm | 5 ++--- ios/bottom-tabs/RNSTabsScreenViewController.mm | 2 +- ios/helpers/scroll-view/RNSScrollViewFinder.h | 11 ++++++----- ios/helpers/scroll-view/RNSScrollViewFinder.mm | 5 ++++- ios/helpers/scroll-view/RNSScrollViewHelper.mm | 2 +- 7 files changed, 17 insertions(+), 15 deletions(-) diff --git a/ios/RNSScreen.mm b/ios/RNSScreen.mm index 61398f8394..c0801a3605 100644 --- a/ios/RNSScreen.mm +++ b/ios/RNSScreen.mm @@ -1242,9 +1242,8 @@ - (void)overrideScrollViewBehaviorInFirstDescendantChainIfNeeded - (void)updateContentScrollViewEdgeEffectsIfExists { - [RNSScrollEdgeEffectApplicator - applyToScrollView:[RNSScrollViewFinder findContentScrollViewWithFirstDescendantsChain:self] - withProvider:self]; + [RNSScrollEdgeEffectApplicator applyToScrollView:[RNSScrollViewFinder findScrollViewInFirstDescendantChainFrom:self] + withProvider:self]; } #pragma mark - RNSSafeAreaProviding diff --git a/ios/RNSScreenStack.mm b/ios/RNSScreenStack.mm index 070b8034f6..957dac70c1 100644 --- a/ios/RNSScreenStack.mm +++ b/ios/RNSScreenStack.mm @@ -194,7 +194,7 @@ - (bool)onRepeatedTabSelectionOfTabScreenController:(RNSTabsScreenViewController return [[self popToRootViewControllerAnimated:true] count] > 0; } else if (tabScreenController.tabScreenComponentView.shouldUseRepeatedTabSelectionScrollToTopSpecialEffect) { UIScrollView *scrollView = - [RNSScrollViewFinder findContentScrollViewWithFirstDescendantsChain:[[self topViewController] view]]; + [RNSScrollViewFinder findScrollViewInFirstDescendantChainFrom:[[self topViewController] view]]; return [scrollView rnscreens_scrollToTop]; } diff --git a/ios/bottom-tabs/RNSBottomTabsScreenComponentView.mm b/ios/bottom-tabs/RNSBottomTabsScreenComponentView.mm index 668e6ba5c5..58876cbb0d 100644 --- a/ios/bottom-tabs/RNSBottomTabsScreenComponentView.mm +++ b/ios/bottom-tabs/RNSBottomTabsScreenComponentView.mm @@ -165,9 +165,8 @@ - (void)overrideScrollViewBehaviorInFirstDescendantChainIfNeeded - (void)updateContentScrollViewEdgeEffectsIfExists { - [RNSScrollEdgeEffectApplicator - applyToScrollView:[RNSScrollViewFinder findContentScrollViewWithFirstDescendantsChain:self] - withProvider:self]; + [RNSScrollEdgeEffectApplicator applyToScrollView:[RNSScrollViewFinder findScrollViewInFirstDescendantChainFrom:self] + withProvider:self]; } #pragma mark - Prop update utils diff --git a/ios/bottom-tabs/RNSTabsScreenViewController.mm b/ios/bottom-tabs/RNSTabsScreenViewController.mm index c886ea6046..ba94d37395 100644 --- a/ios/bottom-tabs/RNSTabsScreenViewController.mm +++ b/ios/bottom-tabs/RNSTabsScreenViewController.mm @@ -107,7 +107,7 @@ - (bool)tabScreenSelectedRepeatedly return [[self tabsSpecialEffectsDelegate] onRepeatedTabSelectionOfTabScreenController:self]; } else if (self.tabScreenComponentView.shouldUseRepeatedTabSelectionScrollToTopSpecialEffect) { UIScrollView *scrollView = - [RNSScrollViewFinder findContentScrollViewWithFirstDescendantsChain:[self tabScreenComponentView]]; + [RNSScrollViewFinder findScrollViewInFirstDescendantChainFrom:[self tabScreenComponentView]]; return [scrollView rnscreens_scrollToTop]; } diff --git a/ios/helpers/scroll-view/RNSScrollViewFinder.h b/ios/helpers/scroll-view/RNSScrollViewFinder.h index 95c422658a..64f5a9e79c 100644 --- a/ios/helpers/scroll-view/RNSScrollViewFinder.h +++ b/ios/helpers/scroll-view/RNSScrollViewFinder.h @@ -2,23 +2,24 @@ #import "RNSContentScrollViewProviding.h" @interface RNSScrollViewFinder : NSObject + /** * Searches for content ScrollView by traversing down the hierarchy using first subview, similar to UIKit behavior. * It will fail if: * - UIScrollView is not a first subview of view or one of its descendants in the hierarchy, * - if UIScrollView's parent is not yet attached. + * + * When `view == nil`, it should also return `nil`. */ -+ (nullable UIScrollView *)findContentScrollViewWithFirstDescendantsChain:(nullable UIView *)view; ++ (nullable UIScrollView *)findScrollViewInFirstDescendantChainFrom:(nullable UIView *)view; /** - * Looks for UIScrollView in a similar way to `findContentScrollViewWithFirstDescendantsChain`, until it finds + * Looks for UIScrollView in a similar way to `findScrollViewInFirstDescendantChainFrom`, until it finds * `RNSContentScrollViewProviding`. Then, it delegates the task to the provider, and returns the results. This can * overcome the problems of subviews' children not being mounted yet, or ScrollView being mounted at index different * than 0. * - * Caveat: when traversing the hierarchy, we don't check for conformance to protocol, - * but whether the view responds to `RNSContentScrollViewProviding.findContentScrollView`. - * This doesn't place locks and is faster. + * When `view == nil`, it should also return `nil`. */ + (nullable UIScrollView *)findContentScrollViewWithDelegatingToProvider:(nullable UIView *)view; diff --git a/ios/helpers/scroll-view/RNSScrollViewFinder.mm b/ios/helpers/scroll-view/RNSScrollViewFinder.mm index 1f858a8bea..2434c61295 100644 --- a/ios/helpers/scroll-view/RNSScrollViewFinder.mm +++ b/ios/helpers/scroll-view/RNSScrollViewFinder.mm @@ -2,7 +2,7 @@ @implementation RNSScrollViewFinder -+ (UIScrollView *)findContentScrollViewWithFirstDescendantsChain:(UIView *)view ++ (UIScrollView *)findScrollViewInFirstDescendantChainFrom:(UIView *)view { UIView *currentView = view; @@ -27,6 +27,9 @@ + (nullable UIScrollView *)findContentScrollViewWithDelegatingToProvider:(nullab if ([currentView isKindOfClass:UIScrollView.class]) { return static_cast(currentView); } else if ([currentView respondsToSelector:@selector(findContentScrollView)]) { + // When traversing the hierarchy, we don't check for conformance to protocol, + // but whether the view responds to `RNSContentScrollViewProviding.findContentScrollView`. + // This doesn't place locks and is faster. return [static_cast>(currentView) findContentScrollView]; } else if ([currentView.subviews count] > 0) { currentView = currentView.subviews[0]; diff --git a/ios/helpers/scroll-view/RNSScrollViewHelper.mm b/ios/helpers/scroll-view/RNSScrollViewHelper.mm index 907fbeb3eb..a0ebf406dd 100644 --- a/ios/helpers/scroll-view/RNSScrollViewHelper.mm +++ b/ios/helpers/scroll-view/RNSScrollViewHelper.mm @@ -5,7 +5,7 @@ @implementation RNSScrollViewHelper + (void)overrideScrollViewBehaviorInFirstDescendantChainFrom:(nullable UIView *)view { - UIScrollView *scrollView = [RNSScrollViewFinder findContentScrollViewWithFirstDescendantsChain:view]; + UIScrollView *scrollView = [RNSScrollViewFinder findScrollViewInFirstDescendantChainFrom:view]; if ([scrollView contentInsetAdjustmentBehavior] == UIScrollViewContentInsetAdjustmentNever) { [scrollView setContentInsetAdjustmentBehavior:UIScrollViewContentInsetAdjustmentAutomatic]; From 7004170e36250576d6b40c9374f76d7db1291ced Mon Sep 17 00:00:00 2001 From: Konrad Michalik Date: Mon, 6 Oct 2025 11:11:55 +0200 Subject: [PATCH 4/4] Implement & use RNSContentScrollViewProviding for BottomTabsHost --- ios/RNSScreen.mm | 5 +++-- .../RNSBottomTabsHostComponentView.h | 2 ++ .../RNSBottomTabsHostComponentView.mm | 19 +++++++++++++++++++ 3 files changed, 24 insertions(+), 2 deletions(-) diff --git a/ios/RNSScreen.mm b/ios/RNSScreen.mm index c0801a3605..4f2863576a 100644 --- a/ios/RNSScreen.mm +++ b/ios/RNSScreen.mm @@ -1242,8 +1242,9 @@ - (void)overrideScrollViewBehaviorInFirstDescendantChainIfNeeded - (void)updateContentScrollViewEdgeEffectsIfExists { - [RNSScrollEdgeEffectApplicator applyToScrollView:[RNSScrollViewFinder findScrollViewInFirstDescendantChainFrom:self] - withProvider:self]; + [RNSScrollEdgeEffectApplicator + applyToScrollView:[RNSScrollViewFinder findContentScrollViewWithDelegatingToProvider:self] + withProvider:self]; } #pragma mark - RNSSafeAreaProviding diff --git a/ios/bottom-tabs/RNSBottomTabsHostComponentView.h b/ios/bottom-tabs/RNSBottomTabsHostComponentView.h index 11059f8bec..77d5ce8f37 100644 --- a/ios/bottom-tabs/RNSBottomTabsHostComponentView.h +++ b/ios/bottom-tabs/RNSBottomTabsHostComponentView.h @@ -4,6 +4,7 @@ #import "RNSEnums.h" #import "RNSReactBaseView.h" #import "RNSScreenContainer.h" +#import "RNSScrollViewFinder.h" #ifdef RCT_NEW_ARCH_ENABLED #import "RNSViewControllerInvalidating.h" @@ -27,6 +28,7 @@ NS_ASSUME_NONNULL_BEGIN */ @interface RNSBottomTabsHostComponentView : RNSReactBaseView < RNSScreenContainerDelegate, + RNSContentScrollViewProviding, #ifdef RCT_NEW_ARCH_ENABLED RNSViewControllerInvalidating #else diff --git a/ios/bottom-tabs/RNSBottomTabsHostComponentView.mm b/ios/bottom-tabs/RNSBottomTabsHostComponentView.mm index fe7d959117..0f3b8de38f 100644 --- a/ios/bottom-tabs/RNSBottomTabsHostComponentView.mm +++ b/ios/bottom-tabs/RNSBottomTabsHostComponentView.mm @@ -194,6 +194,25 @@ - (void)invalidate #endif +#pragma mark - RNSContentScrollViewProviding + +- (UIScrollView *)findContentScrollView +{ +#if !TARGET_OS_TV && !TARGET_OS_VISION + if (_controller.selectedViewController == _controller.moreNavigationController) { + // Logic for More Controller; user is shown the native list of tabs. + // This we want to keep as-is, we're not styling the ScrollView here, + // so let's pretend there isn't one + return nil; + } +#endif // check for build target != tvOS, visionOS + + // User selected regular tab with our BottomTabScreenComponentView. We start directly from it. + // This has the advantage of being able to continue searching even if the TabScreen + // hasn't been mounted yet (see mountChildComponentView(), _reactSubviews). + return [RNSScrollViewFinder findContentScrollViewWithDelegatingToProvider:_controller.selectedViewController.view]; +} + #pragma mark - React events - (nonnull RNSBottomTabsHostEventEmitter *)reactEventEmitter