Skip to content

Commit d16f85b

Browse files
authored
Fix iOS 26 bottom tab test IDs (#8254)
* Fix iOS 26 bottom tab test IDs * Address tab testID review feedback * Preserve system tab accessibility identifiers * Harden bottom tab testID syncing
1 parent d2b9e7f commit d16f85b

3 files changed

Lines changed: 116 additions & 0 deletions

File tree

ios/RNNBottomTabsController.mm

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
#import "RNNBottomTabsController.h"
2+
#import "UITabBarController+RNNOptions.h"
23
#import "UITabBarController+RNNUtils.h"
34

45
@interface RNNBottomTabsController ()
@@ -100,6 +101,8 @@ - (void)createTabBarItems:(NSArray<UIViewController *> *)childViewControllers {
100101
for (UIViewController *child in childViewControllers) {
101102
[_bottomTabPresenter applyOptions:child.resolveOptions child:child];
102103
}
104+
105+
[self syncTabBarItemTestIDs];
103106
}
104107

105108
- (void)mergeChildOptions:(RNNNavigationOptions *)options child:(UIViewController *)child {
@@ -111,6 +114,8 @@ - (void)mergeChildOptions:(RNNNavigationOptions *)options child:(UIViewControlle
111114
[_dotIndicatorPresenter mergeOptions:options
112115
resolvedOptions:childViewController.resolveOptions
113116
child:childViewController];
117+
118+
[self syncTabBarItemTestIDs];
114119
}
115120

116121
- (id<UITabBarControllerDelegate>)delegate {
@@ -123,6 +128,7 @@ - (void)render {
123128

124129
- (void)viewDidLayoutSubviews {
125130
[super viewDidLayoutSubviews];
131+
[self syncTabBarItemTestIDs];
126132
[self.presenter viewDidLayoutSubviews];
127133
[_dotIndicatorPresenter bottomTabsDidLayoutSubviews:self];
128134
}

ios/UITabBarController+RNNOptions.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,4 +20,6 @@
2020

2121
- (void)hideTabBar:(BOOL)animated;
2222

23+
- (void)syncTabBarItemTestIDs;
24+
2325
@end

ios/UITabBarController+RNNOptions.mm

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,85 @@
11
#import "RNNBottomTabsController.h"
22
#import "UITabBar+utils.h"
33
#import "UITabBarController+RNNOptions.h"
4+
#import <objc/runtime.h>
5+
6+
static const NSTimeInterval RNNTabBarTestIDRetryDelay = 0.15;
7+
static const NSUInteger RNNTabBarTestIDMaxRetryAttempts = 5;
8+
static const void *RNNTabBarTestIDRetryScheduledKey = &RNNTabBarTestIDRetryScheduledKey;
9+
static const void *RNNTabBarTestIDRetryAttemptsKey = &RNNTabBarTestIDRetryAttemptsKey;
10+
static const void *RNNOriginalTabBarViewAccessibilityIdentifierKey =
11+
&RNNOriginalTabBarViewAccessibilityIdentifierKey;
412

513
@implementation UITabBarController (RNNOptions)
614

15+
- (void)rnn_storeOriginalAccessibilityIdentifierIfNeededForTabView:(UIView *)tabView {
16+
if (objc_getAssociatedObject(tabView, RNNOriginalTabBarViewAccessibilityIdentifierKey))
17+
return;
18+
19+
id originalAccessibilityIdentifier = tabView.accessibilityIdentifier ?: [NSNull null];
20+
objc_setAssociatedObject(tabView, RNNOriginalTabBarViewAccessibilityIdentifierKey,
21+
originalAccessibilityIdentifier,
22+
OBJC_ASSOCIATION_RETAIN_NONATOMIC);
23+
}
24+
25+
- (void)rnn_applyTestID:(NSString *)testID toTabView:(UIView *)tabView {
26+
[self rnn_storeOriginalAccessibilityIdentifierIfNeededForTabView:tabView];
27+
tabView.accessibilityIdentifier = testID;
28+
}
29+
30+
- (void)rnn_restoreOriginalAccessibilityIdentifierForTabView:(UIView *)tabView {
31+
id originalAccessibilityIdentifier =
32+
objc_getAssociatedObject(tabView, RNNOriginalTabBarViewAccessibilityIdentifierKey);
33+
if (!originalAccessibilityIdentifier)
34+
return;
35+
36+
tabView.accessibilityIdentifier =
37+
[originalAccessibilityIdentifier isKindOfClass:NSNull.class]
38+
? nil
39+
: originalAccessibilityIdentifier;
40+
objc_setAssociatedObject(tabView, RNNOriginalTabBarViewAccessibilityIdentifierKey, nil,
41+
OBJC_ASSOCIATION_RETAIN_NONATOMIC);
42+
}
43+
44+
- (BOOL)rnn_isTabBarTestIDRetryScheduled {
45+
return [objc_getAssociatedObject(self, RNNTabBarTestIDRetryScheduledKey) boolValue];
46+
}
47+
48+
- (void)rnn_setTabBarTestIDRetryScheduled:(BOOL)scheduled {
49+
objc_setAssociatedObject(self, RNNTabBarTestIDRetryScheduledKey, @(scheduled),
50+
OBJC_ASSOCIATION_RETAIN_NONATOMIC);
51+
}
52+
53+
- (NSUInteger)rnn_tabBarTestIDRetryAttempts {
54+
NSNumber *retryAttempts = objc_getAssociatedObject(self, RNNTabBarTestIDRetryAttemptsKey);
55+
return retryAttempts.unsignedIntegerValue;
56+
}
57+
58+
- (void)rnn_setTabBarTestIDRetryAttempts:(NSUInteger)retryAttempts {
59+
objc_setAssociatedObject(self, RNNTabBarTestIDRetryAttemptsKey, @(retryAttempts),
60+
OBJC_ASSOCIATION_RETAIN_NONATOMIC);
61+
}
62+
63+
- (BOOL)rnn_applyTabBarItemTestIDs {
64+
NSArray<UITabBarItem *> *items = self.tabBar.items ?: @[];
65+
BOOL appliedAllKnownTestIDs = YES;
66+
67+
for (NSUInteger tabIndex = 0; tabIndex < items.count; tabIndex++) {
68+
UITabBarItem *item = items[tabIndex];
69+
NSString *testID = item.accessibilityIdentifier;
70+
UIView *tabView = [self.tabBar tabBarItemViewAtIndex:tabIndex];
71+
if (testID.length > 0 && tabView) {
72+
[self rnn_applyTestID:testID toTabView:tabView];
73+
} else if (tabView) {
74+
[self rnn_restoreOriginalAccessibilityIdentifierForTabView:tabView];
75+
} else if (testID.length > 0) {
76+
appliedAllKnownTestIDs = NO;
77+
}
78+
}
79+
80+
return appliedAllKnownTestIDs;
81+
}
82+
783
- (void)setCurrentTabIndex:(NSUInteger)currentTabIndex {
884
[self setSelectedIndex:currentTabIndex];
985
}
@@ -16,6 +92,38 @@ - (void)setTabBarTestID:(NSString *)testID {
1692
self.tabBar.accessibilityIdentifier = testID;
1793
}
1894

95+
- (void)syncTabBarItemTestIDs {
96+
if ([self rnn_applyTabBarItemTestIDs]) {
97+
[self rnn_setTabBarTestIDRetryScheduled:NO];
98+
[self rnn_setTabBarTestIDRetryAttempts:0];
99+
return;
100+
}
101+
102+
if ([self rnn_isTabBarTestIDRetryScheduled])
103+
return;
104+
105+
NSUInteger retryAttempts = [self rnn_tabBarTestIDRetryAttempts];
106+
if (retryAttempts >= RNNTabBarTestIDMaxRetryAttempts) {
107+
[self rnn_setTabBarTestIDRetryAttempts:0];
108+
return;
109+
}
110+
111+
[self rnn_setTabBarTestIDRetryScheduled:YES];
112+
[self rnn_setTabBarTestIDRetryAttempts:retryAttempts + 1];
113+
114+
__weak UITabBarController *weakSelf = self;
115+
dispatch_after(dispatch_time(DISPATCH_TIME_NOW,
116+
(int64_t)(RNNTabBarTestIDRetryDelay * NSEC_PER_SEC)),
117+
dispatch_get_main_queue(), ^{
118+
UITabBarController *controller = weakSelf;
119+
if (!controller)
120+
return;
121+
122+
[controller rnn_setTabBarTestIDRetryScheduled:NO];
123+
[controller syncTabBarItemTestIDs];
124+
});
125+
}
126+
19127
- (void)setTabBarStyle:(UIBarStyle)barStyle {
20128
self.tabBar.barStyle = barStyle;
21129
}

0 commit comments

Comments
 (0)