react-native-navigation 8.8.2 → 8.8.3-snapshot.2506
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/hierarchy/root/RootAnimator.kt +5 -2
- package/android/src/main/java/com/reactnativenavigation/react/DevBundleDownloadListenerAdapter.java +5 -8
- package/android/src/main/java/com/reactnativenavigation/react/JsDevReloadHandlerFacade.java +5 -8
- package/android/src/main/java/com/reactnativenavigation/react/NavigationTurboModule.kt +5 -0
- package/android/src/main/java/com/reactnativenavigation/react/ReloadHandlerFacade.java +5 -8
- package/android/src/main/java/com/reactnativenavigation/viewcontrollers/stack/StackControllerBuilder.java +2 -1
- package/android/src/main/java/com/reactnativenavigation/viewcontrollers/viewcontroller/LayoutDirectionApplier.kt +4 -1
- package/android/src/main/java/com/reactnativenavigation/viewcontrollers/viewcontroller/Presenter.java +3 -1
- package/android/src/main/java/com/reactnativenavigation/viewcontrollers/viewcontroller/RootPresenter.java +11 -1
- package/android/src/main/java/com/reactnativenavigation/viewcontrollers/viewcontroller/ViewController.java +1 -1
- package/android/src/test/java/com/reactnativenavigation/BaseRobolectricTest.kt +1 -1
- package/android/src/test/java/com/reactnativenavigation/BaseTest.kt +3 -2
- package/android/src/test/java/com/reactnativenavigation/ShadowReactView.java +45 -0
- package/android/src/test/java/com/reactnativenavigation/ShadowSoLoader.java +28 -0
- package/android/src/test/java/com/reactnativenavigation/TestApplication.kt +2 -2
- package/android/src/test/java/com/reactnativenavigation/mocks/SimpleViewController.java +14 -7
- package/android/src/test/java/com/reactnativenavigation/presentation/PresenterTest.java +0 -1
- package/android/src/test/java/com/reactnativenavigation/utils/ButtonPresenterTest.java +0 -1
- package/android/src/test/java/com/reactnativenavigation/utils/LayoutFactoryTest.java +0 -1
- package/android/src/test/java/com/reactnativenavigation/viewcontrollers/OptionsApplyingTest.java +0 -1
- package/android/src/test/java/com/reactnativenavigation/viewcontrollers/bottomtabs/BottomTabPresenterTest.java +0 -1
- package/android/src/test/java/com/reactnativenavigation/viewcontrollers/bottomtabs/BottomTabsControllerTest.kt +0 -1
- package/android/src/test/java/com/reactnativenavigation/viewcontrollers/bottomtabs/BottomTabsPresenterTest.kt +0 -1
- package/android/src/test/java/com/reactnativenavigation/viewcontrollers/component/ComponentViewControllerTest.java +0 -1
- package/android/src/test/java/com/reactnativenavigation/viewcontrollers/externalcomponent/ExternalComponentViewControllerTest.java +0 -1
- package/android/src/test/java/com/reactnativenavigation/viewcontrollers/modal/ModalAnimatorTest.kt +0 -1
- package/android/src/test/java/com/reactnativenavigation/viewcontrollers/modal/ModalPresenterTest.java +0 -1
- package/android/src/test/java/com/reactnativenavigation/viewcontrollers/modal/ModalStackTest.java +0 -1
- package/android/src/test/java/com/reactnativenavigation/viewcontrollers/navigator/NavigatorTest.java +0 -1
- package/android/src/test/java/com/reactnativenavigation/viewcontrollers/navigator/RootPresenterTest.kt +0 -1
- package/android/src/test/java/com/reactnativenavigation/viewcontrollers/overlay/OverlayManagerTest.java +0 -1
- package/android/src/test/java/com/reactnativenavigation/viewcontrollers/parent/ParentControllerTest.java +0 -1
- package/android/src/test/java/com/reactnativenavigation/viewcontrollers/sidemenu/SideMenuControllerTest.java +0 -1
- package/android/src/test/java/com/reactnativenavigation/viewcontrollers/stack/BackButtonHelperTest.java +0 -1
- package/android/src/test/java/com/reactnativenavigation/viewcontrollers/stack/FloatingActionButtonTest.java +0 -1
- package/android/src/test/java/com/reactnativenavigation/viewcontrollers/stack/StackAnimatorTest.kt +0 -1
- package/android/src/test/java/com/reactnativenavigation/viewcontrollers/stack/StackControllerTest.kt +0 -1
- package/android/src/test/java/com/reactnativenavigation/viewcontrollers/stack/StackPresenterTest.kt +0 -1
- package/android/src/test/java/com/reactnativenavigation/viewcontrollers/stack/TitleBarButtonControllerTest.java +0 -1
- package/android/src/test/java/com/reactnativenavigation/viewcontrollers/stack/TitleBarReactViewControllerTest.java +0 -1
- package/android/src/test/java/com/reactnativenavigation/viewcontrollers/stack/TopBarButtonControllerTest.java +0 -1
- package/android/src/test/java/com/reactnativenavigation/viewcontrollers/stack/TopBarControllerTest.kt +0 -1
- package/android/src/test/java/com/reactnativenavigation/viewcontrollers/toptabs/TopTabsViewControllerTest.java +0 -1
- package/android/src/test/java/com/reactnativenavigation/viewcontrollers/viewcontroller/ViewControllerTest.java +0 -1
- package/android/src/test/java/com/reactnativenavigation/views/TitleAndButtonsContainerTest.kt +0 -1
- package/android/src/test/java/com/reactnativenavigation/views/TitleSubTitleLayoutTest.kt +0 -1
- package/ios/BottomTabPresenter.h +2 -0
- package/ios/BottomTabPresenter.mm +1 -3
- package/ios/BottomTabsAfterInitialTabAttacher.mm +4 -0
- package/ios/BottomTabsTogetherAttacher.mm +4 -0
- package/ios/RNNAppDelegate.mm +15 -4
- package/ios/RNNBackButtonOptions.h +1 -0
- package/ios/RNNBackButtonOptions.mm +3 -0
- package/ios/RNNBottomTabOptions.h +1 -0
- package/ios/RNNBottomTabOptions.mm +4 -1
- package/ios/RNNBottomTabsController.mm +48 -8
- package/ios/RNNButtonOptions.h +1 -0
- package/ios/RNNButtonOptions.mm +4 -0
- package/ios/RNNComponentOptions.mm +1 -1
- package/ios/RNNComponentViewController.mm +5 -0
- package/ios/RNNScreenTransition.mm +1 -1
- package/ios/RNNStackController.mm +76 -1
- package/ios/RNNTabBarItemCreator.h +2 -0
- package/ios/RNNTabBarItemCreator.mm +98 -0
- package/ios/RNNUIBarButtonItem.mm +26 -0
- package/ios/StackControllerDelegate.mm +1 -0
- package/ios/TopBarPresenter.mm +26 -2
- package/ios/TransitionOptions.mm +2 -2
- package/ios/UIViewController+LayoutProtocol.h +2 -0
- package/lib/module/interfaces/Options.js.map +1 -1
- package/lib/typescript/interfaces/Options.d.ts +42 -0
- package/lib/typescript/interfaces/Options.d.ts.map +1 -1
- package/package.json +13 -12
- package/src/interfaces/Options.ts +54 -0
|
@@ -40,7 +40,6 @@ import static org.mockito.Mockito.spy;
|
|
|
40
40
|
import static org.mockito.Mockito.times;
|
|
41
41
|
import static org.mockito.Mockito.verify;
|
|
42
42
|
|
|
43
|
-
@Ignore("New architecture - WIP")
|
|
44
43
|
public class TopTabsViewControllerTest extends BaseTest {
|
|
45
44
|
private static final int SIZE = 2;
|
|
46
45
|
|
|
@@ -40,7 +40,6 @@ import static org.mockito.Mockito.times;
|
|
|
40
40
|
import static org.mockito.Mockito.verify;
|
|
41
41
|
import static org.mockito.Mockito.withSettings;
|
|
42
42
|
|
|
43
|
-
@Ignore("New architecture - WIP")
|
|
44
43
|
public class ViewControllerTest extends BaseTest {
|
|
45
44
|
|
|
46
45
|
private ViewController uut;
|
package/android/src/test/java/com/reactnativenavigation/views/TitleAndButtonsContainerTest.kt
CHANGED
|
@@ -30,7 +30,6 @@ import kotlin.test.assertFalse
|
|
|
30
30
|
private const val UUT_WIDTH = 1000
|
|
31
31
|
private const val UUT_HEIGHT = 100
|
|
32
32
|
|
|
33
|
-
@Ignore("New architecture - failed to fix")
|
|
34
33
|
class TitleAndButtonsContainerTest : BaseTest() {
|
|
35
34
|
lateinit var uut: TitleAndButtonsContainer
|
|
36
35
|
private lateinit var activity: Activity
|
package/ios/BottomTabPresenter.h
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
#import "RNNTabBarItemCreator.h"
|
|
3
3
|
@interface BottomTabPresenter : RNNBasePresenter
|
|
4
4
|
|
|
5
|
+
@property(nonatomic, strong, readonly) RNNTabBarItemCreator *tabCreator;
|
|
6
|
+
|
|
5
7
|
- (instancetype)initWithDefaultOptions:(RNNNavigationOptions *)defaultOptions
|
|
6
8
|
tabCreator:(RNNTabBarItemCreator *)tabCreator;
|
|
7
9
|
|
|
@@ -3,9 +3,7 @@
|
|
|
3
3
|
#import "UIViewController+LayoutProtocol.h"
|
|
4
4
|
#import "UIViewController+RNNOptions.h"
|
|
5
5
|
|
|
6
|
-
@implementation BottomTabPresenter
|
|
7
|
-
RNNTabBarItemCreator *_tabCreator;
|
|
8
|
-
}
|
|
6
|
+
@implementation BottomTabPresenter
|
|
9
7
|
|
|
10
8
|
- (instancetype)initWithDefaultOptions:(RNNNavigationOptions *)defaultOptions
|
|
11
9
|
tabCreator:(RNNTabBarItemCreator *)tabCreator {
|
|
@@ -9,6 +9,10 @@
|
|
|
9
9
|
for (UIViewController *viewController in bottomTabsController.deselectedViewControllers) {
|
|
10
10
|
dispatch_async(dispatch_get_main_queue(), ^{
|
|
11
11
|
UIWindow *preloadWindow = [[UIWindow alloc] initWithFrame:CGRectZero];
|
|
12
|
+
// Clip the preload window so the deselected tab's full-screen reactView,
|
|
13
|
+
// which is briefly hosted here while it renders, can't draw outside the
|
|
14
|
+
// zero-frame window and flicker over the already-visible selected tab.
|
|
15
|
+
preloadWindow.clipsToBounds = YES;
|
|
12
16
|
preloadWindow.hidden = NO;
|
|
13
17
|
|
|
14
18
|
dispatch_group_t ready = dispatch_group_create();
|
|
@@ -7,6 +7,10 @@
|
|
|
7
7
|
dispatch_group_t ready = dispatch_group_create();
|
|
8
8
|
|
|
9
9
|
UIWindow *preloadWindow = [[UIWindow alloc] initWithFrame:CGRectZero];
|
|
10
|
+
// Clip the preload window so the full-screen reactViews hosted here while
|
|
11
|
+
// they render can't draw outside the zero-frame window. Today the splash
|
|
12
|
+
// screen masks this, but without clipping it's a latent flicker.
|
|
13
|
+
preloadWindow.clipsToBounds = YES;
|
|
10
14
|
preloadWindow.hidden = NO;
|
|
11
15
|
|
|
12
16
|
NSMapTable *reactViewToParent = [NSMapTable strongToStrongObjectsMapTable];
|
package/ios/RNNAppDelegate.mm
CHANGED
|
@@ -4,7 +4,6 @@
|
|
|
4
4
|
#import <react/featureflags/ReactNativeFeatureFlagsDefaults.h>
|
|
5
5
|
|
|
6
6
|
#import "RCTAppSetupUtils.h"
|
|
7
|
-
#import <React/CoreModulesPlugins.h>
|
|
8
7
|
#if __has_include(<React/RCTCxxBridgeDelegate.h>)
|
|
9
8
|
#import <React/RCTCxxBridgeDelegate.h>
|
|
10
9
|
#endif
|
|
@@ -56,12 +55,17 @@ static NSString *const kRNConcurrentRoot = @"concurrentRoot";
|
|
|
56
55
|
- (BOOL)application:(UIApplication *)application
|
|
57
56
|
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
|
|
58
57
|
|
|
58
|
+
// RN 0.77 & RN0.78
|
|
59
59
|
#if !RNN_RN_VERSION_79_OR_NEWER
|
|
60
|
-
|
|
60
|
+
#if __has_include(<ReactAppDependencyProvider/RCTAppDependencyProvider.h>)
|
|
61
|
+
self.dependencyProvider = [RCTAppDependencyProvider new];
|
|
62
|
+
#endif
|
|
63
|
+
// RN 0.77
|
|
61
64
|
#if !RNN_RN_VERSION_78
|
|
65
|
+
[self _setUpFeatureFlags];
|
|
62
66
|
self.rootViewFactory = [self createRCTRootViewFactory];
|
|
63
|
-
#else
|
|
64
|
-
self.reactNativeFactory = [[RCTReactNativeFactory alloc]
|
|
67
|
+
#else // RN0.78
|
|
68
|
+
self.reactNativeFactory = [[RCTReactNativeFactory alloc] initWithDelegate:self];
|
|
65
69
|
self.reactNativeFactory.rootViewFactory = [self createRCTRootViewFactory];
|
|
66
70
|
#endif
|
|
67
71
|
|
|
@@ -119,6 +123,13 @@ didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
|
|
|
119
123
|
return [[RCTRootViewFactory alloc] initWithConfiguration:configuration andTurboModuleManagerDelegate:self];
|
|
120
124
|
}
|
|
121
125
|
|
|
126
|
+
#pragma mark - RCTTurboModuleManagerDelegate
|
|
127
|
+
|
|
128
|
+
- (id<RCTTurboModule>)getModuleInstanceFromClass:(Class)moduleClass
|
|
129
|
+
{
|
|
130
|
+
return RCTAppSetupDefaultModuleFromClass(moduleClass, self.dependencyProvider);
|
|
131
|
+
}
|
|
132
|
+
|
|
122
133
|
#pragma mark - Feature Flags
|
|
123
134
|
class RCTAppDelegateBridgelessFeatureFlags : public facebook::react::ReactNativeFeatureFlagsDefaults {
|
|
124
135
|
public:
|
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
@property(nonatomic, strong) Text *identifier;
|
|
19
19
|
@property(nonatomic, strong) Bool *popStackOnPress;
|
|
20
20
|
@property(nonatomic, strong) RNNIconBackgroundOptions *iconBackground;
|
|
21
|
+
@property(nonatomic, strong) Bool *hideSharedBackground;
|
|
21
22
|
|
|
22
23
|
- (BOOL)hasValue;
|
|
23
24
|
|
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
self.displayMode = [TextParser parse:dict key:@"displayMode"];
|
|
21
21
|
self.popStackOnPress = [BoolParser parse:dict key:@"popStackOnPress"];
|
|
22
22
|
self.iconBackground = [[RNNIconBackgroundOptions alloc] initWithDict:dict[@"iconBackground"] enabled:nil];
|
|
23
|
+
self.hideSharedBackground = [BoolParser parse:dict key:@"hideSharedBackground"];
|
|
23
24
|
|
|
24
25
|
return self;
|
|
25
26
|
}
|
|
@@ -54,6 +55,8 @@
|
|
|
54
55
|
self.popStackOnPress = options.popStackOnPress;
|
|
55
56
|
if (options.sfSymbol.hasValue)
|
|
56
57
|
self.sfSymbol = options.sfSymbol;
|
|
58
|
+
if (options.hideSharedBackground.hasValue)
|
|
59
|
+
self.hideSharedBackground = options.hideSharedBackground;
|
|
57
60
|
}
|
|
58
61
|
|
|
59
62
|
- (BOOL)hasValue {
|
|
@@ -31,6 +31,7 @@
|
|
|
31
31
|
self.selectTabOnPress = [BoolParser parse:dict key:@"selectTabOnPress"];
|
|
32
32
|
self.sfSymbol = [TextParser parse:dict key:@"sfSymbol"];
|
|
33
33
|
self.sfSelectedSymbol = [TextParser parse:dict key:@"sfSelectedSymbol"];
|
|
34
|
+
self.role = [TextParser parse:dict key:@"role"];
|
|
34
35
|
|
|
35
36
|
return self;
|
|
36
37
|
}
|
|
@@ -76,6 +77,8 @@
|
|
|
76
77
|
self.sfSymbol = options.sfSymbol;
|
|
77
78
|
if (options.sfSelectedSymbol.hasValue)
|
|
78
79
|
self.sfSelectedSymbol = options.sfSelectedSymbol;
|
|
80
|
+
if (options.role.hasValue)
|
|
81
|
+
self.role = options.role;
|
|
79
82
|
}
|
|
80
83
|
|
|
81
84
|
- (BOOL)hasValue {
|
|
@@ -85,7 +88,7 @@
|
|
|
85
88
|
self.iconColor.hasValue || self.selectedIconColor.hasValue ||
|
|
86
89
|
self.selectedTextColor.hasValue || self.iconInsets.hasValue || self.textColor.hasValue ||
|
|
87
90
|
self.visible.hasValue || self.selectTabOnPress.hasValue || self.sfSymbol.hasValue ||
|
|
88
|
-
self.sfSelectedSymbol.hasValue;
|
|
91
|
+
self.sfSelectedSymbol.hasValue || self.role.hasValue;
|
|
89
92
|
}
|
|
90
93
|
|
|
91
94
|
@end
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
#import "RNNBottomTabsController.h"
|
|
2
|
+
#import "RNNTabBarItemCreator.h"
|
|
2
3
|
#import "UITabBarController+RNNOptions.h"
|
|
3
4
|
#import "UITabBarController+RNNUtils.h"
|
|
4
5
|
|
|
@@ -7,6 +8,8 @@
|
|
|
7
8
|
@property(nonatomic, strong) RNNDotIndicatorPresenter *dotIndicatorPresenter;
|
|
8
9
|
@property(nonatomic, strong) UILongPressGestureRecognizer *longPressRecognizer;
|
|
9
10
|
|
|
11
|
+
- (void)rnn_cycleAllTabsThenRestoreInitialSelection;
|
|
12
|
+
|
|
10
13
|
@end
|
|
11
14
|
|
|
12
15
|
@implementation RNNBottomTabsController {
|
|
@@ -16,6 +19,8 @@
|
|
|
16
19
|
BOOL _tabBarNeedsRestore;
|
|
17
20
|
RNNNavigationOptions *_options;
|
|
18
21
|
BOOL _didFinishSetup;
|
|
22
|
+
BOOL _rnnDidApplyInitialTabBarSelectionFix;
|
|
23
|
+
BOOL _rnnSuppressTabSelectionEvents;
|
|
19
24
|
}
|
|
20
25
|
|
|
21
26
|
- (instancetype)initWithLayoutInfo:(RNNLayoutInfo *)layoutInfo
|
|
@@ -49,12 +54,7 @@
|
|
|
49
54
|
eventEmitter:eventEmitter
|
|
50
55
|
childViewControllers:childViewControllers];
|
|
51
56
|
|
|
52
|
-
if (@available(iOS
|
|
53
|
-
UITabBarAppearance *appearance = [UITabBarAppearance new];
|
|
54
|
-
[appearance configureWithDefaultBackground];
|
|
55
|
-
self.tabBar.standardAppearance = appearance;
|
|
56
|
-
self.tabBar.scrollEdgeAppearance = [appearance copy];
|
|
57
|
-
} else if (@available(iOS 13.0, *)) {
|
|
57
|
+
if (@available(iOS 13.0, *)) {
|
|
58
58
|
UITabBarAppearance *appearance = [UITabBarAppearance new];
|
|
59
59
|
[appearance configureWithOpaqueBackground];
|
|
60
60
|
appearance.backgroundEffect = nil;
|
|
@@ -90,11 +90,44 @@
|
|
|
90
90
|
}
|
|
91
91
|
}
|
|
92
92
|
|
|
93
|
-
- (void)
|
|
94
|
-
[super
|
|
93
|
+
- (void)viewDidAppear:(BOOL)animated {
|
|
94
|
+
[super viewDidAppear:animated];
|
|
95
|
+
// iOS 26: first layout can misplace tab item titles; cycling selection (then restoring) forces a
|
|
96
|
+
// correct layout without user interaction. Defer so all tab children are in the hierarchy.
|
|
97
|
+
if (@available(iOS 26.0, *)) {
|
|
98
|
+
if (!_rnnDidApplyInitialTabBarSelectionFix) {
|
|
99
|
+
_rnnDidApplyInitialTabBarSelectionFix = YES;
|
|
100
|
+
__weak RNNBottomTabsController *weakSelf = self;
|
|
101
|
+
dispatch_async(dispatch_get_main_queue(), ^{
|
|
102
|
+
[weakSelf rnn_cycleAllTabsThenRestoreInitialSelection];
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
- (void)rnn_cycleAllTabsThenRestoreInitialSelection {
|
|
109
|
+
NSUInteger count = self.childViewControllers.count;
|
|
110
|
+
if (count <= 1) {
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
NSUInteger initial = _currentTabIndex;
|
|
115
|
+
if (initial >= count) {
|
|
116
|
+
initial = 0;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
_rnnSuppressTabSelectionEvents = YES;
|
|
120
|
+
[UIView performWithoutAnimation:^{
|
|
121
|
+
for (NSUInteger i = 0; i < count; i++) {
|
|
122
|
+
[self setSelectedIndex:i];
|
|
123
|
+
}
|
|
124
|
+
[self setSelectedIndex:initial];
|
|
125
|
+
}];
|
|
126
|
+
_rnnSuppressTabSelectionEvents = NO;
|
|
95
127
|
}
|
|
96
128
|
|
|
97
129
|
- (void)createTabBarItems:(NSArray<UIViewController *> *)childViewControllers {
|
|
130
|
+
_bottomTabPresenter.tabCreator.searchRoleUsed = NO;
|
|
98
131
|
for (UIViewController *child in childViewControllers) {
|
|
99
132
|
[_bottomTabPresenter applyOptions:child.resolveOptions child:child];
|
|
100
133
|
}
|
|
@@ -200,6 +233,9 @@
|
|
|
200
233
|
|
|
201
234
|
- (void)tabBarController:(UITabBarController *)tabBarController
|
|
202
235
|
didSelectViewController:(UIViewController *)viewController {
|
|
236
|
+
if (_rnnSuppressTabSelectionEvents) {
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
203
239
|
[self.eventEmitter sendBottomTabSelected:@(tabBarController.selectedIndex)
|
|
204
240
|
unselected:@(_previousTabIndex)];
|
|
205
241
|
}
|
|
@@ -216,6 +252,10 @@
|
|
|
216
252
|
NSUInteger _index = [tabBarController.viewControllers indexOfObject:viewController];
|
|
217
253
|
BOOL isMoreTab = ![tabBarController.viewControllers containsObject:viewController];
|
|
218
254
|
|
|
255
|
+
if (_rnnSuppressTabSelectionEvents) {
|
|
256
|
+
return YES;
|
|
257
|
+
}
|
|
258
|
+
|
|
219
259
|
[self.eventEmitter sendBottomTabPressed:@(_index)];
|
|
220
260
|
|
|
221
261
|
if ([[viewController resolveOptions].bottomTab.selectTabOnPress withDefault:YES] || isMoreTab) {
|
package/ios/RNNButtonOptions.h
CHANGED
|
@@ -23,6 +23,7 @@
|
|
|
23
23
|
@property(nonatomic, strong) RNNComponentOptions *component;
|
|
24
24
|
@property(nonatomic, strong) RNNIconBackgroundOptions *iconBackground;
|
|
25
25
|
@property(nonatomic, strong) Bool *disableIconTint;
|
|
26
|
+
@property(nonatomic, strong) Bool *hideSharedBackground;
|
|
26
27
|
|
|
27
28
|
- (RNNButtonOptions *)withDefault:(RNNButtonOptions *)defaultOptions;
|
|
28
29
|
|
package/ios/RNNButtonOptions.mm
CHANGED
|
@@ -25,6 +25,7 @@
|
|
|
25
25
|
enabled:self.enabled];
|
|
26
26
|
self.systemItem = [TextParser parse:dict key:@"systemItem"];
|
|
27
27
|
self.disableIconTint = [BoolParser parse:dict key:@"disableIconTint"];
|
|
28
|
+
self.hideSharedBackground = [BoolParser parse:dict key:@"hideSharedBackground"];
|
|
28
29
|
|
|
29
30
|
return self;
|
|
30
31
|
}
|
|
@@ -49,6 +50,7 @@
|
|
|
49
50
|
newOptions.iconBackground = self.iconBackground.copy;
|
|
50
51
|
newOptions.systemItem = self.systemItem.copy;
|
|
51
52
|
newOptions.disableIconTint = self.disableIconTint.copy;
|
|
53
|
+
newOptions.hideSharedBackground = self.hideSharedBackground.copy;
|
|
52
54
|
return newOptions;
|
|
53
55
|
}
|
|
54
56
|
|
|
@@ -88,6 +90,8 @@
|
|
|
88
90
|
self.systemItem = options.systemItem;
|
|
89
91
|
if (options.disableIconTint.hasValue)
|
|
90
92
|
self.disableIconTint = options.disableIconTint;
|
|
93
|
+
if (options.hideSharedBackground.hasValue)
|
|
94
|
+
self.hideSharedBackground = options.hideSharedBackground;
|
|
91
95
|
}
|
|
92
96
|
|
|
93
97
|
- (BOOL)shouldCreateCustomView {
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
self.name = [TextParser parse:dict key:@"name"];
|
|
10
10
|
self.componentId = [TextParser parse:dict key:@"componentId"];
|
|
11
11
|
self.alignment = [TextParser parse:dict key:@"alignment"];
|
|
12
|
-
|
|
12
|
+
self.waitForRender = [BoolParser parse:dict key:@"waitForRender"];
|
|
13
13
|
|
|
14
14
|
return self;
|
|
15
15
|
}
|
|
@@ -155,6 +155,11 @@
|
|
|
155
155
|
isFocused:searchController.searchBar.isFirstResponder];
|
|
156
156
|
}
|
|
157
157
|
|
|
158
|
+
- (void)destroyReactView {
|
|
159
|
+
[self.reactView removeFromSuperview];
|
|
160
|
+
self.reactView = nil;
|
|
161
|
+
}
|
|
162
|
+
|
|
158
163
|
- (void)screenPopped {
|
|
159
164
|
[_eventEmitter sendScreenPoppedEvent:self.layoutInfo.componentId];
|
|
160
165
|
}
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
self.content = [[RNNEnterExitAnimation alloc] initWithDict:dict[@"content"]];
|
|
12
12
|
self.bottomTabs = [[ElementTransitionOptions alloc] initWithDict:dict[@"bottomTabs"]];
|
|
13
13
|
self.enable = [BoolParser parse:dict key:@"enabled"];
|
|
14
|
-
|
|
14
|
+
self.waitForRender = [BoolParser parse:dict key:@"waitForRender"];
|
|
15
15
|
self.duration = [TimeIntervalParser parse:dict key:@"duration"];
|
|
16
16
|
self.sharedElementTransitions = [OptionsArrayParser parse:dict
|
|
17
17
|
key:@"sharedElementTransitions"
|
|
@@ -42,7 +42,82 @@
|
|
|
42
42
|
|
|
43
43
|
- (UIViewController *)popViewControllerAnimated:(BOOL)animated {
|
|
44
44
|
[self prepareForPop];
|
|
45
|
-
|
|
45
|
+
UIViewController *previousTop = self.topViewController;
|
|
46
|
+
UIView *snapshot = [self snapshotTopView:animated];
|
|
47
|
+
|
|
48
|
+
UIViewController *poppedVC = [super popViewControllerAnimated:animated];
|
|
49
|
+
if (!poppedVC) {
|
|
50
|
+
[snapshot removeFromSuperview];
|
|
51
|
+
return nil;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
id<UIViewControllerTransitionCoordinator> coordinator = self.transitionCoordinator;
|
|
55
|
+
if (coordinator && coordinator.isInteractive) {
|
|
56
|
+
// Interactive pop (swipe-back): remove snapshot overlay — UIKit shows the live
|
|
57
|
+
// view during the gesture. Skip early teardown so the React view stays alive
|
|
58
|
+
// if the gesture is cancelled. The delegate's didShowViewController handles
|
|
59
|
+
// cleanup once the animation finishes.
|
|
60
|
+
[snapshot removeFromSuperview];
|
|
61
|
+
} else {
|
|
62
|
+
[self teardownPoppedControllers:@[ poppedVC ] previousTop:previousTop];
|
|
63
|
+
}
|
|
64
|
+
return poppedVC;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
- (NSArray<UIViewController *> *)popToViewController:(UIViewController *)viewController
|
|
68
|
+
animated:(BOOL)animated {
|
|
69
|
+
UIViewController *previousTop = self.topViewController;
|
|
70
|
+
UIView *snapshot = [self snapshotTopView:animated];
|
|
71
|
+
|
|
72
|
+
NSArray<UIViewController *> *poppedVCs =
|
|
73
|
+
[super popToViewController:viewController animated:animated];
|
|
74
|
+
if (poppedVCs.count > 0) {
|
|
75
|
+
[self teardownPoppedControllers:poppedVCs previousTop:previousTop];
|
|
76
|
+
} else {
|
|
77
|
+
[snapshot removeFromSuperview];
|
|
78
|
+
}
|
|
79
|
+
return poppedVCs;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
- (NSArray<UIViewController *> *)popToRootViewControllerAnimated:(BOOL)animated {
|
|
83
|
+
UIViewController *previousTop = self.topViewController;
|
|
84
|
+
UIView *snapshot = [self snapshotTopView:animated];
|
|
85
|
+
|
|
86
|
+
NSArray<UIViewController *> *poppedVCs =
|
|
87
|
+
[super popToRootViewControllerAnimated:animated];
|
|
88
|
+
if (poppedVCs.count > 0) {
|
|
89
|
+
[self teardownPoppedControllers:poppedVCs previousTop:previousTop];
|
|
90
|
+
} else {
|
|
91
|
+
[snapshot removeFromSuperview];
|
|
92
|
+
}
|
|
93
|
+
return poppedVCs;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
#pragma mark - React view teardown
|
|
97
|
+
|
|
98
|
+
- (UIView *)snapshotTopView:(BOOL)animated {
|
|
99
|
+
if (!animated) return nil;
|
|
100
|
+
UIViewController *topVC = self.topViewController;
|
|
101
|
+
if (!topVC.isViewLoaded || !topVC.view.window) return nil;
|
|
102
|
+
|
|
103
|
+
UIView *snapshot = [topVC.view snapshotViewAfterScreenUpdates:NO];
|
|
104
|
+
if (snapshot) {
|
|
105
|
+
snapshot.frame = topVC.view.bounds;
|
|
106
|
+
snapshot.autoresizingMask =
|
|
107
|
+
UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
|
|
108
|
+
[topVC.view addSubview:snapshot];
|
|
109
|
+
}
|
|
110
|
+
return snapshot;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
- (void)teardownPoppedControllers:(NSArray<UIViewController *> *)poppedVCs
|
|
114
|
+
previousTop:(UIViewController *)previousTop {
|
|
115
|
+
for (UIViewController *vc in poppedVCs) {
|
|
116
|
+
if (vc == previousTop && [vc isKindOfClass:[RNNComponentViewController class]]) {
|
|
117
|
+
[[(RNNComponentViewController *)vc reactView] componentDidDisappear];
|
|
118
|
+
}
|
|
119
|
+
[vc destroyReactView];
|
|
120
|
+
}
|
|
46
121
|
}
|
|
47
122
|
|
|
48
123
|
- (BOOL)navigationBar:(UINavigationBar *)navigationBar shouldPopItem:(UINavigationItem *)item {
|
|
@@ -1,15 +1,46 @@
|
|
|
1
1
|
#import "RNNTabBarItemCreator.h"
|
|
2
2
|
#import "RNNFontAttributesCreator.h"
|
|
3
3
|
#import "UIImage+utils.h"
|
|
4
|
+
#import <React/RCTLog.h>
|
|
4
5
|
|
|
5
6
|
@implementation RNNTabBarItemCreator
|
|
6
7
|
|
|
8
|
+
+ (NSNumber *)systemItemForRole:(NSString *)role {
|
|
9
|
+
static NSDictionary<NSString *, NSNumber *> *map = nil;
|
|
10
|
+
static dispatch_once_t onceToken;
|
|
11
|
+
dispatch_once(&onceToken, ^{
|
|
12
|
+
map = @{
|
|
13
|
+
@"search" : @(UITabBarSystemItemSearch),
|
|
14
|
+
@"bookmarks" : @(UITabBarSystemItemBookmarks),
|
|
15
|
+
@"contacts" : @(UITabBarSystemItemContacts),
|
|
16
|
+
@"downloads" : @(UITabBarSystemItemDownloads),
|
|
17
|
+
@"favorites" : @(UITabBarSystemItemFavorites),
|
|
18
|
+
@"featured" : @(UITabBarSystemItemFeatured),
|
|
19
|
+
@"history" : @(UITabBarSystemItemHistory),
|
|
20
|
+
@"more" : @(UITabBarSystemItemMore),
|
|
21
|
+
@"mostRecent" : @(UITabBarSystemItemMostRecent),
|
|
22
|
+
@"mostViewed" : @(UITabBarSystemItemMostViewed),
|
|
23
|
+
@"recents" : @(UITabBarSystemItemRecents),
|
|
24
|
+
@"topRated" : @(UITabBarSystemItemTopRated),
|
|
25
|
+
};
|
|
26
|
+
});
|
|
27
|
+
return map[role];
|
|
28
|
+
}
|
|
29
|
+
|
|
7
30
|
- (UITabBarItem *)createTabBarItem:(UITabBarItem *)mergeItem {
|
|
8
31
|
return mergeItem ?: [UITabBarItem new];
|
|
9
32
|
}
|
|
10
33
|
|
|
11
34
|
- (UITabBarItem *)createTabBarItem:(RNNBottomTabOptions *)bottomTabOptions
|
|
12
35
|
mergeItem:(UITabBarItem *)mergeItem {
|
|
36
|
+
|
|
37
|
+
if (bottomTabOptions.role.hasValue) {
|
|
38
|
+
UITabBarItem *roleItem = [self createSystemItemForRole:bottomTabOptions
|
|
39
|
+
mergeItem:mergeItem];
|
|
40
|
+
if (roleItem)
|
|
41
|
+
return roleItem;
|
|
42
|
+
}
|
|
43
|
+
|
|
13
44
|
UITabBarItem *tabItem = [self createTabBarItem:mergeItem];
|
|
14
45
|
UIImage *icon = [bottomTabOptions.icon withDefault:nil];
|
|
15
46
|
UIImage *selectedIcon = [bottomTabOptions.selectedIcon withDefault:icon];
|
|
@@ -55,6 +86,71 @@
|
|
|
55
86
|
return tabItem;
|
|
56
87
|
}
|
|
57
88
|
|
|
89
|
+
#pragma mark - Role (system item) creation
|
|
90
|
+
|
|
91
|
+
- (UITabBarItem *)createSystemItemForRole:(RNNBottomTabOptions *)bottomTabOptions
|
|
92
|
+
mergeItem:(UITabBarItem *)mergeItem {
|
|
93
|
+
NSString *role = [bottomTabOptions.role withDefault:nil];
|
|
94
|
+
NSNumber *systemItemNumber = [RNNTabBarItemCreator systemItemForRole:role];
|
|
95
|
+
|
|
96
|
+
if (!systemItemNumber) {
|
|
97
|
+
RCTLogWarn(@"[RNN] Unknown bottomTab role '%@' — falling back to normal tab.", role);
|
|
98
|
+
return nil;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if ([role isEqualToString:@"search"] && self.searchRoleUsed) {
|
|
102
|
+
RCTLogWarn(@"[RNN] Only one tab per bottomTabs layout may use role:'search'. "
|
|
103
|
+
@"Subsequent 'search' roles are ignored — creating a normal tab instead.");
|
|
104
|
+
return nil;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
if ([role isEqualToString:@"search"]) {
|
|
108
|
+
self.searchRoleUsed = YES;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
UITabBarItem *tabItem =
|
|
112
|
+
[[UITabBarItem alloc] initWithTabBarSystemItem:(UITabBarSystemItem)[systemItemNumber integerValue]
|
|
113
|
+
tag:bottomTabOptions.tag];
|
|
114
|
+
|
|
115
|
+
UITabBarItem *baseItem = [self createTabBarItem:mergeItem];
|
|
116
|
+
tabItem.standardAppearance = baseItem.standardAppearance;
|
|
117
|
+
#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 150000
|
|
118
|
+
if (@available(iOS 15.0, *)) {
|
|
119
|
+
tabItem.scrollEdgeAppearance = baseItem.scrollEdgeAppearance;
|
|
120
|
+
}
|
|
121
|
+
#endif
|
|
122
|
+
|
|
123
|
+
UIImage *icon = [bottomTabOptions.icon withDefault:nil];
|
|
124
|
+
UIImage *selectedIcon = [bottomTabOptions.selectedIcon withDefault:nil];
|
|
125
|
+
UIColor *iconColor = [bottomTabOptions.iconColor withDefault:nil];
|
|
126
|
+
UIColor *selectedIconColor = [bottomTabOptions.selectedIconColor withDefault:iconColor];
|
|
127
|
+
|
|
128
|
+
if (@available(iOS 13.0, *)) {
|
|
129
|
+
if (bottomTabOptions.sfSymbol.hasValue) {
|
|
130
|
+
icon = [UIImage systemImageNamed:[bottomTabOptions.sfSymbol withDefault:nil]];
|
|
131
|
+
}
|
|
132
|
+
if (bottomTabOptions.sfSelectedSymbol.hasValue) {
|
|
133
|
+
selectedIcon = [UIImage systemImageNamed:[bottomTabOptions.sfSelectedSymbol withDefault:nil]];
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
if (icon) {
|
|
138
|
+
tabItem.image = [self getIconImage:icon withTint:iconColor];
|
|
139
|
+
}
|
|
140
|
+
if (selectedIcon) {
|
|
141
|
+
tabItem.selectedImage = [self getSelectedIconImage:selectedIcon
|
|
142
|
+
selectedIconColor:selectedIconColor];
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
tabItem.accessibilityIdentifier = [bottomTabOptions.testID withDefault:nil];
|
|
146
|
+
tabItem.accessibilityLabel = [bottomTabOptions.accessibilityLabel withDefault:nil];
|
|
147
|
+
[self appendTitleAttributes:tabItem bottomTabOptions:bottomTabOptions];
|
|
148
|
+
|
|
149
|
+
return tabItem;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
#pragma mark - Icon helpers
|
|
153
|
+
|
|
58
154
|
- (UIImage *)getSelectedIconImage:(UIImage *)selectedIcon
|
|
59
155
|
selectedIconColor:(UIColor *)selectedIconColor {
|
|
60
156
|
if (selectedIcon) {
|
|
@@ -82,6 +178,8 @@
|
|
|
82
178
|
return nil;
|
|
83
179
|
}
|
|
84
180
|
|
|
181
|
+
#pragma mark - Title attributes
|
|
182
|
+
|
|
85
183
|
- (void)appendTitleAttributes:(UITabBarItem *)tabItem
|
|
86
184
|
bottomTabOptions:(RNNBottomTabOptions *)bottomTabOptions {
|
|
87
185
|
UIColor *textColor = [bottomTabOptions.textColor withDefault:[UIColor blackColor]];
|
|
@@ -71,6 +71,13 @@
|
|
|
71
71
|
[button.widthAnchor constraintEqualToConstant:icon.size.width].active = YES;
|
|
72
72
|
[button.heightAnchor constraintEqualToConstant:icon.size.height].active = YES;
|
|
73
73
|
self = [super initWithCustomView:button];
|
|
74
|
+
if (@available(iOS 26.0, *)) {
|
|
75
|
+
// The UIButton already paints its own iconBackground chrome, so the
|
|
76
|
+
// iOS 26 shared Platter would double-decorate it. Defaults to YES;
|
|
77
|
+
// callers can opt back in via the hideSharedBackground option.
|
|
78
|
+
self.hidesSharedBackground =
|
|
79
|
+
[buttonOptions.hideSharedBackground withDefault:YES];
|
|
80
|
+
}
|
|
74
81
|
[self applyOptions:buttonOptions];
|
|
75
82
|
self.onPress = onPress;
|
|
76
83
|
return self;
|
|
@@ -91,6 +98,25 @@
|
|
|
91
98
|
buttonOptions:(RNNButtonOptions *)buttonOptions
|
|
92
99
|
onPress:(RNNButtonPressCallback)onPress {
|
|
93
100
|
self = [super initWithCustomView:reactView];
|
|
101
|
+
if (@available(iOS 26.0, *)) {
|
|
102
|
+
// iOS 26 wraps every bar button item in a shared Platter/Liquid-Glass
|
|
103
|
+
// background. React-rendered custom views supply their own chrome, and
|
|
104
|
+
// the Platter both double-decorates and misaligns custom views taller
|
|
105
|
+
// than the standard pill (~30pt). Defaults to YES; callers can opt
|
|
106
|
+
// back in via the hideSharedBackground option.
|
|
107
|
+
self.hidesSharedBackground =
|
|
108
|
+
[buttonOptions.hideSharedBackground withDefault:YES];
|
|
109
|
+
// Pin the custom view to a stable 44pt bar-item size so the navbar
|
|
110
|
+
// reserves the final slot up-front (incl. during push-transition
|
|
111
|
+
// snapshots) and doesn't relayout once React mounts. Without this,
|
|
112
|
+
// React's post-mount sizeToFit changes bounds, the navbar relayouts,
|
|
113
|
+
// and the customView visibly snaps from a 0x0 / wrong-position state
|
|
114
|
+
// to its centered final frame. React content centers within via its
|
|
115
|
+
// own flex layout.
|
|
116
|
+
reactView.translatesAutoresizingMaskIntoConstraints = NO;
|
|
117
|
+
[reactView.widthAnchor constraintEqualToConstant:44].active = YES;
|
|
118
|
+
[reactView.heightAnchor constraintEqualToConstant:44].active = YES;
|
|
119
|
+
}
|
|
94
120
|
[self applyOptions:buttonOptions];
|
|
95
121
|
self.onPress = onPress;
|
|
96
122
|
return self;
|