In my app, I have two tabs. The second tab is shown or hidden based on some condition. I find if there is a sheet being presented in the second tab when the tab is to be hidden, the sheet can't be dismissed.
The issue can be consistently reproduced with the code below. To reproduce it, click tab 2, then click "Present Sheet", then click "Hide Tab 2". You will see the sheet isn't removed, though the tab containing it (that is, tab 2) has been removed (you can drag the sheet down to verify it).
It seems a SwiftUI bug to me. Does anyone know how to work around it? I'm close to finish my app but hit this unexpected issue :( Any help will be much appreciated.
struct ContentView: View {
@State var showTab2: Bool = true
var body: some View {
TabView {
// tab 1
NavigationView {
Text("Tab 1")
}
.tabItem {
Label("Tab 1", systemImage: "1.circle")
}
// tab 2
if showTab2 {
NavigationView {
Tab2(showTab2: $showTab2)
}
.tabItem {
Label("Tab 2", systemImage: "2.circle")
}
}
}
}
}
struct Tab2: View {
@State var showSheet: Bool = false
@Binding var showTab2: Bool
var body: some View {
VStack(spacing: 12) {
Text("Tab 2")
Button("Click to present sheet") {
showSheet = true
}
}
.sheet(isPresented: $showSheet, onDismiss: nil) {
NavigationView {
MySheet(showTab2: $showTab2)
}
}
}
}
struct MySheet: View {
@Environment(\.dismiss) var dismiss
@Binding var showTab2: Bool
var body: some View {
Button("Click to hide tab 2") {
// dismiss() works fine if I comment out this line.
showTab2 = false
dismiss()
}
}
}
I have submitted feedback on this to Apple, but I'm not optimistic for any reply (I have never received one).
Update:
The issue can be reproduced in many other scenarios where no sheet is involved. So, the second approach @Asperi gave is not a general solution.
Well, here we see conflict of actions (due to racing): async sheet closing (due to animation) and sync tab removing.
Here are possible approaches:
Button("Click to hide tab 2") {
dismiss()
DispatchQueue.main.asyncAfter(deadline: .now() + 0.25) { // << here !!
showTab2 = false
}
}
.sheet(isPresented: $showSheet, onDismiss: { showTab2 = false }) { // << here !!
NavigationView {
MySheet(showTab2: $showTab2)
}
}
Note: Actually when view knows/manages something for parent of parent is not very good design, so option 2 (maybe with some additional conditions/callbacks) are more preferable.