diff --git a/.changeset/tidy-unicode-tabs.md b/.changeset/tidy-unicode-tabs.md
new file mode 100644
index 00000000..ddb17323
--- /dev/null
+++ b/.changeset/tidy-unicode-tabs.md
@@ -0,0 +1,5 @@
+---
+'react-native-bottom-tabs': patch
+---
+
+Fix an iOS Fabric crash when selecting or long-pressing a tab whose key contains non-ASCII characters.
diff --git a/packages/example-shared/e2e/unicode-tab-key.yaml b/packages/example-shared/e2e/unicode-tab-key.yaml
new file mode 100644
index 00000000..2771d0d4
--- /dev/null
+++ b/packages/example-shared/e2e/unicode-tab-key.yaml
@@ -0,0 +1,19 @@
+appId: ${APP_ID}
+name: Press and long-press tabs with Unicode keys
+---
+- launchApp
+- scrollUntilVisible:
+ element:
+ text: ^Native Bottom Tabs with Unicode keys$
+ direction: DOWN
+ timeout: 15000
+- tapOn:
+ text: ^Native Bottom Tabs with Unicode keys$
+- assertVisible: Press me
+- tapOn:
+ text: ^アルバム$
+- longPressOn:
+ text: ^기사$
+- tapOn:
+ text: ^Чат$
+- assertVisible: Press me
diff --git a/packages/example-shared/src/App.tsx b/packages/example-shared/src/App.tsx
index 420d3427..0611d023 100644
--- a/packages/example-shared/src/App.tsx
+++ b/packages/example-shared/src/App.tsx
@@ -31,6 +31,7 @@ import SFSymbols from './Examples/SFSymbols';
import Roles from './Examples/Roles';
import LabeledTabs from './Examples/Labeled';
import NativeBottomTabs from './Examples/NativeBottomTabs';
+import NativeBottomTabsUnicode from './Examples/NativeBottomTabsUnicode';
import TintColorsExample from './Examples/TintColors';
import NativeBottomTabsEmbeddedStacks from './Examples/NativeBottomTabsEmbeddedStacks';
import NativeBottomTabsSVGs from './Examples/NativeBottomTabsSVGs';
@@ -199,6 +200,10 @@ const examples = [
name: 'Native Bottom Tabs with original icons',
},
{ component: NativeBottomTabs, name: 'Native Bottom Tabs' },
+ {
+ component: NativeBottomTabsUnicode,
+ name: 'Native Bottom Tabs with Unicode keys',
+ },
{ component: JSBottomTabs, name: 'JS Bottom Tabs' },
{
component: JSBottomTabs,
diff --git a/packages/example-shared/src/Examples/NativeBottomTabsUnicode.tsx b/packages/example-shared/src/Examples/NativeBottomTabsUnicode.tsx
new file mode 100644
index 00000000..76f599d0
--- /dev/null
+++ b/packages/example-shared/src/Examples/NativeBottomTabsUnicode.tsx
@@ -0,0 +1,100 @@
+import { Article } from '../Screens/Article';
+import { Albums } from '../Screens/Albums';
+import { Contacts } from '../Screens/Contacts';
+import { Chat } from '../Screens/Chat';
+import { createNativeBottomTabNavigator } from '@bottom-tabs/react-navigation';
+import { Platform } from 'react-native';
+import { useState } from 'react';
+
+const Tab = createNativeBottomTabNavigator();
+
+function NativeBottomTabsUnicode() {
+ const [label, setLabel] = useState('기사');
+ return (
+ {
+ console.log(
+ `${Platform.OS}: Long press detected on tab with key ${data.target} at the navigator level.`
+ );
+ },
+ }}
+ >
+ {
+ console.log(
+ `${Platform.OS}: Long press detected on tab with key ${data.target} at the screen level.`
+ );
+ setLabel('새 기사');
+ },
+ }}
+ options={{
+ tabBarButtonTestID: 'articleTestID',
+ tabBarBadge: '10',
+ tabBarLabel: label,
+ tabBarIcon: ({ focused }) =>
+ focused
+ ? require('../../assets/icons/person_dark.png')
+ : require('../../assets/icons/article_dark.png'),
+ }}
+ />
+ require('../../assets/icons/grid_dark.png'),
+ }}
+ />
+ {
+ console.log('Contacts tab press prevented');
+ },
+ }}
+ options={{
+ tabBarButtonTestID: 'contactsTestID',
+ tabBarIcon: () => require('../../assets/icons/person_dark.png'),
+ tabBarActiveTintColor: 'yellow',
+ preventsDefault: true,
+ }}
+ />
+ {
+ console.log('Chat tab pressed');
+ },
+ }}
+ options={{
+ tabBarButtonTestID: 'chatTestID',
+ tabBarIcon: () => require('../../assets/icons/chat_dark.png'),
+ tabBarActiveTintColor: 'white',
+ }}
+ />
+
+ );
+}
+
+export default NativeBottomTabsUnicode;
diff --git a/packages/react-native-bottom-tabs/ios/RCTTabViewComponentView.mm b/packages/react-native-bottom-tabs/ios/RCTTabViewComponentView.mm
index 8e9cae58..ccb09ea6 100644
--- a/packages/react-native-bottom-tabs/ios/RCTTabViewComponentView.mm
+++ b/packages/react-native-bottom-tabs/ios/RCTTabViewComponentView.mm
@@ -265,7 +265,7 @@ - (void)onPageSelectedWithKey:(NSString *)key reactTag:(NSNumber *)reactTag {
auto eventEmitter = std::static_pointer_cast(_eventEmitter);
if (eventEmitter) {
eventEmitter->onPageSelected(RNCTabViewEventEmitter::OnPageSelected{
- .key = [key cStringUsingEncoding:kCFStringEncodingUTF8]
+ .key = std::string([key UTF8String] ?: "")
});
}
}
@@ -274,7 +274,7 @@ - (void)onLongPressWithKey:(NSString *)key reactTag:(NSNumber *)reactTag {
auto eventEmitter = std::static_pointer_cast(_eventEmitter);
if (eventEmitter) {
eventEmitter->onTabLongPress(RNCTabViewEventEmitter::OnTabLongPress {
- .key = [key cStringUsingEncoding:kCFStringEncodingUTF8]
+ .key = std::string([key UTF8String] ?: "")
});
}
}