Custom TabBar on iPad still renders duplicate with Top Navigation (iPadOS 26+)

Aperta
#463 3 commenti 0 reazioni 0 assegnatari Vedi su GitHub

Nessuno ha ancora preso questa issue.

Valutazione

Difficoltà
4/5
Tempo stimato
3-5 giorni
Idoneità per principianti
45/100
Tipo di issue
Bug
Chiarezza
Abbastanza chiara
Stato di attività
Tranquilla
Stack tecnologico
ios, react-native, swift, typescript
Ambito
mobile-dev

Direzione di ricerca

Inizia con la riproduzione in app/(tabs)/_layout.tsx e segui le modifiche ai figli di Tab descritte in PR #420, incluso .hideTabBar(props.tabBarHidden). Confronta il comportamento su iPadOS 18.5 e 26+ verificando al contempo la barra delle schede personalizzata e le opzioni dell’intestazione; il lavoro è completo quando la barra inferiore personalizzata rimane visibile senza la barra degli strumenti superiore su iPadOS 26+.

Scritto dal modello di indicizzazione a partire dal testo della issue.

Descrizione

bug
Before submitting a new issue
  • I tested using the latest version of the library, as the bug might be already fixed.
  • I tested using a supported version of react native.
  • I checked for possible duplicate issues, with possible answers.
Bug summary

Top Navigation/Toolbar appears on iPad with iPadOS 26+ even with headerShown: false and custom bottom tab bar configured. This worked correctly on iPadOS 18.5, but broke on iPadOS 26.

Expected behavior

Only the custom bottom tab bar should be visible. No top navigation toolbar should appear (as it did on iPadOS 18.5).

Actual behavior

On iPad with iPadOS 26+, a new top navigation/toolbar appears despite:

  • Setting headerShown: false
  • Using custom bottom tab bar with tabBarStyle={{ display: "none" }}
  • Custom tab bar positioned at bottom

On iPadOS 18.5, this setup worked perfectly without the top toolbar.

This appears to be a breaking change in the iPadOS 26 iPad UI pattern with a new segmented control.

Question about PR #420 and iPadOS 26 compatibility

I'm using the latest @bottom-tabs/monorepo from GitHub (main branch) which includes the changes from PR #420. However, the top toolbar still appears on iPadOS 26.

PR #420 moves .hideTabBar(props.tabBarHidden) INSIDE each Tab's child view:

Tab(value: tabData.key, role: tabData.role?.convert()) {
  child
    .ignoresSafeArea(.container, edges: .all)
    .tabAppear(using: context)
    .hideTabBar(props.tabBarHidden)  // Moved here in PR #420
} label: {
  TabItem(title: tabData.title, icon: icon, sfSymbol: tabData.sfSymbol, labeled: props.labeled)
}

Is PR #420 sufficient for iPadOS 26, or do we need an additional fix like .toolbar(.hidden, for: .tabBar) for iPadOS 26+ compatibility?

I tested adding .toolbar(.hidden, for: .tabBar) after .hideTabBar() and it seems to hide the toolbar, but I want to confirm if this is the correct approach or if there's a better solution.

Library version
  • @bottom-tabs/monorepo: main branch (from GitHub) - includes PR #420
  • @bottom-tabs/react-navigation: ^1.0.2
Environment info
- react-native: 0.81.4
- expo: ~54.0.13
- iPadOS: 18.5 (works) → 26.0+ (broken - iPad Simulator)
- Platform: iPadOS 
- Device: iPad Simulator
Steps to reproduce
  1. Create app with @bottom-tabs/react-navigation on Expo Router
  2. Test on iPad with iPadOS 18.5 → Works
  3. Test on iPad with iPadOS 26+ → Top toolbar appears
  4. Expected: Toolbar should not appear on iPadOS 26 either
Reproducible sample code
# app/(tabs)/\_layout.tsx

import { withLayoutContext } from "expo-router";
import { createNativeBottomTabNavigator } from "@bottom-tabs/react-navigation";
import { useSafeAreaInsets } from "react-native-safe-area-context";
import { Platform, View, Pressable, StyleSheet, Image, Dimensions } from "react-native";

const BottomTabNavigator = createNativeBottomTabNavigator().Navigator;

const Tabs = withLayoutContext(BottomTabNavigator);

function CustomBottomTabBar({ state, descriptors, navigation }: any) {
  const insets = useSafeAreaInsets();
  return (
    <View style={[styles.tabBar, { paddingBottom: Math.max(insets.bottom, 8) }]}>
      {state.routes.map((route: any, index: number) => {
        const { options } = descriptors[route.key];
        const isFocused = state.index === index;

        const onPress = () => {
          const event = navigation.emit({
            type: "tabPress",
            target: route.key,
            canPreventDefault: true,
          });

          if (!isFocused && !event.defaultPrevented) {
            navigation.navigate(route.name, route.params);
          }
        };

        const iconSource = options.tabBarIcon
          ? options.tabBarIcon({
              focused: isFocused,
              color: isFocused ? "#007AFF" : "#999999",
              size: 24,
            })
          : null;

        return (
          <Pressable
            key={route.key}
            onPress={onPress}
            style={[styles.tabItem, isFocused && styles.tabItemActive]}
          >
            {iconSource && (
              <Image
                source={iconSource}
                style={{ width: 24, height: 24 }}
                resizeMode="contain"
              />
            )}
          </Pressable>
        );
      })}
    </View>
  );
}

const styles = StyleSheet.create({
  tabBar: {
    position: "absolute",
    left: 0,
    right: 0,
    bottom: 0,
    flexDirection: "row",
    backgroundColor: "#fff",
    borderTopWidth: 1,
    borderTopColor: "#e5e5e5",
    height: 60,
  },
  tabItem: {
    flex: 1,
    justifyContent: "center",
    alignItems: "center",
    paddingVertical: 8,
  },
  tabItemActive: {
    borderTopWidth: 2,
    borderTopColor: "#007AFF",
  },
});

export default function TabLayout() {
  const isIPad = Platform.OS === "ios" && Dimensions.get("window").width >= 768;

  return (
    <Tabs
      screenOptions={{
        headerShown: false,
        sceneStyle: { paddingBottom: isIPad ? 68 : 0 },
      }}
      tabBar={isIPad ? (props) => <CustomBottomTabBar {...props} /> : undefined}
      tabBarStyle={isIPad ? { display: "none" } : undefined}
    >
      <Tabs.Screen
        name="discover"
        options={{
          title: "Entdecken",
          tabBarIcon: ({ focused }) => (focused ? icons.compass : icons.compassOutline),
        }}
      />
      <Tabs.Screen
        name="browse"
        options={{
          title: "Stöbern",
          tabBarIcon: ({ focused }) => (focused ? icons.search : icons.searchOutline),
        }}
      />
      <Tabs.Screen
        name="add"
        options={{
          title: "Hinzufügen",
          tabBarIcon: ({ focused }) => (focused ? icons.addCircle : icons.addCircleOutline),
        }}
      />
      <Tabs.Screen
        name="reports"
        options={{
          title: "Meldungen",
          tabBarIcon: ({ focused }) => (focused ? icons.flag : icons.flagOutline),
        }}
      />
      <Tabs.Screen
        name="profile"
        options={{
          title: "Profil",
          tabBarIcon: ({ focused }) => (focused ? icons.person : icons.personOutline),
        }}
      />
    </Tabs>
  );
}
Lingua principale
TypeScript
Stelle
1.5k
Fork
108
Merge medio
12h 42m
PR unite (30g)
9

Guida per i contributori

Apri la guida per i contributori

Come iniziare

  1. Leggi tutta la issue e poi la guida ai contributi del progetto.
  2. Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
  3. Fai un fork del repository e lavora su un branch.
  4. Apri una pull request che faccia riferimento al numero della issue.

Altre issue di callstack/react-native-bottom-tabs

Tutte le issue di callstack/react-native-bottom-tabs

Issue simili

Altre issue su TypeScript

Ricevi le nuove issue nella tua casella

Un breve riepilogo di issue GitHub adatte ai principianti.