Toggling tabBarHidden destroys the tab's SwiftUI identity, dismissing presented sheets
Chưa có ai nhận issue này.
Đánh giá
- Độ khó
- 2/5
- Thời gian dự kiến
- 1-3 giờ
- Mức phù hợp với người mới
- 75/100
- Loại issue
- Lỗi
- Độ rõ ràng
- Đặc tả rõ ràng
- Mức độ hoạt động
- Sôi nổi
- Công nghệ
- react-native, swift, typescript
Hướng nghiên cứu
Vấn đề nằm trong mã Swift cho iOS. Hãy xem xét hàm hideTabBar trong ios/TabViewImpl.swift xung quanh dòng 537-553. Cách sửa là thay đổi logic điều kiện để sử dụng self.toolbar(flag ? .hidden : .automatic, for: .tabBar) cho iOS 16+ nhằm bảo toàn danh tính SwiftUI. Kiểm tra thay đổi bằng cách sử dụng gist tái tạo SwiftUI được cung cấp để xác minh rằng việc chuyển đổi cờ không còn đặt lại trạng thái hoặc đóng sheet nữa. Thay đổi này nên được áp dụng trong cả NewTabView.swift và LegacyTabView.swift nơi hàm được gọi.
Do mô hình lập chỉ mục viết ra từ nội dung của issue.
Mô tả
Before submitting a new issue
- I tested using the latest version of the library (1.4.0 is both
latestand what we ship) - I tested using a supported version of react native (0.86.3)
- I checked for possible duplicate issues
Bug summary
Toggling tabBarHidden at runtime tears down the tab's content and rebuilds it. A sheet presented from inside that tab is dismissed, and any @State in the subtree is lost.
We hit this on the common pattern of hiding the tab bar while the keyboard is up: a user typing in a sheet raises the keyboard, the app sets tabBarHidden, and their sheet disappears mid-edit.
Cause. hideTabBar is a @ViewBuilder whose branch depends on flag:
// ios/TabViewImpl.swift:537-553 @ 1.4.0
@ViewBuilder
func hideTabBar(_ flag: Bool) -> some View {
#if !os(macOS)
if flag {
if #available(iOS 16.0, tvOS 16.0, *) {
self.toolbar(.hidden, for: .tabBar)
} else {
// We fallback to isHidden on UITabBar
self
}
} else {
self
}
#else
self
#endif
}
A @ViewBuilder if/else compiles to _ConditionalContent<A, B>. Flipping flag swaps which branch is taken, which changes the view's type, which changes its structural identity. SwiftUI answers an identity change by discarding the subtree and building a new one — taking @State and any anchored presentation with it.
flag is a JS-driven prop, so this happens at runtime rather than once at setup:
ios/TabView/NewTabView.swift:45 .hideTabBar(props.tabBarHidden)
ios/TabView/LegacyTabView.swift:33 .hideTabBar(props.tabBarHidden)
Suggested fix
Take one branch, decided by availability — which is constant at runtime — and let only the toolbar value depend on flag:
@ViewBuilder
func hideTabBar(_ flag: Bool) -> some View {
#if !os(macOS)
if #available(iOS 16.0, tvOS 16.0, *) {
// Keep the tab content's SwiftUI identity when tab-bar visibility changes.
// Switching between a modified view and `self` tears down presentations
// anchored in that tab.
self.toolbar(flag ? .hidden : .automatic, for: .tabBar)
} else {
// We fallback to isHidden on UITabBar.
self
}
#else
self
#endif
}
.automatic is the documented default for a toolbar's visibility, so the flag == false case keeps its current behaviour while the view keeps its identity. The pre-iOS-16 fallback is unchanged and still cannot hide the bar this way; that branch is now chosen by availability alone, so it no longer participates in identity changes either.
This is the diff we currently carry as a patch:
@ViewBuilder
func hideTabBar(_ flag: Bool) -> some View {
#if !os(macOS)
- if flag {
- if #available(iOS 16.0, tvOS 16.0, *) {
- self.toolbar(.hidden, for: .tabBar)
- } else {
- // We fallback to isHidden on UITabBar
- self
- }
+ if #available(iOS 16.0, tvOS 16.0, *) {
+ // Keep the tab content's SwiftUI identity when keyboard visibility
+ // changes. Switching between a modified view and `self` tears down
+ // presentations anchored in that tab, including Expo UI sheets.
+ self.toolbar(flag ? .hidden : .automatic, for: .tabBar)
} else {
+ // We fallback to isHidden on UITabBar.
self
}
#else
Happy to open a PR.
Library version
1.4.0
Environment info
react-native: 0.86.3
expo: 57.0.20
react: 19.2.3
react-native-bottom-tabs: 1.4.0
platform: iOS (real device and simulator)
architecture: Fabric (New Architecture)
Steps to reproduce
The reproduction is pure SwiftUI — no React Native and no dependency on this library, which is what shows the cause is the @ViewBuilder branch rather than anything about the bridge, the props, or the app.
- Download
IdentityRepro.swiftfrom the gist below - Drop it into an iOS app target, or open it in an Xcode Preview
- Increment both counters, and/or tap "present sheet" on either side
- Flip the
tabBarHiddentoggle
Expected: both columns keep their state.
Actual: the UPSTREAM column resets its counter to 0 and dismisses its sheet; the FIXED column keeps both.
The file defines hideTabBar_upstream (verbatim from TabViewImpl.swift) and hideTabBar_fixed (the suggestion above) side by side over identical subtrees.
iOS/tvOS only — .toolbar(_:for: .tabBar) is unavailable on macOS, which is why the library's own function is wrapped in #if !os(macOS).
Reproducible sample code
https://gist.github.com/pawarren/56cbb014c18150e67995bf6f11ff8168
- Ngôn ngữ chính
- TypeScript
- Star
- 1.5k
- Fork
- 108
- Merge trung bình
- 12 giờ 42 phút
- Pull request đã merge (30 ngày)
- 9
Hướng dẫn đóng góp
Bắt đầu từ đâu
- Đọc hết issue, rồi đọc hướng dẫn đóng góp của dự án.
- Bình luận trên issue rằng bạn sẽ nhận — tránh hai người làm cùng một việc.
- Fork repository và làm thay đổi trên một nhánh.
- Mở pull request có tham chiếu số hiệu của issue.
Issue khác của callstack/react-native-bottom-tabs
-
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 75/100
-
Độ khó 4/5 3-5 ngày Mức phù hợp với người mới 48/100
-
Độ khó 4/5 3-5 ngày Mức phù hợp với người mới 68/100
callstack/react-native-bottom-tabs#591 · 1 bình luận ·
-
Độ khó 4/5 3-5 ngày Mức phù hợp với người mới 58/100
-
bug
Độ khó 4/5 3-5 ngày Mức phù hợp với người mới 48/100
Tất cả issue của callstack/react-native-bottom-tabs
Issue tương tự
-
VerificationGate: ATTRIBUTION quote guard never matches a normal quotation (\b around the quote) Đang mở
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 75/100
danielmiessler/LifeOS#2234 ·
-
T: Bug
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 75/100
-
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 65/100
-
Độ khó 1/5 Dưới một giờ Mức phù hợp với người mới 85/100
-
Mend: dependency security vulnerability untriaged
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 70/100