SwiftUI: dismissing `ActionSheet` dismisses its parent view

I have a view FooView in a navigation stack presented via a NavigationLink.

And FooView has a button that triggers a ActionSheet.

The problem is: when the ActionSheet is dismissed (either cancelled or selected option), its parent view, i.e. FooView is also dismissed, i.e. pops up from the navigation stack.

Is there a way to prevent it doing that?

More importantly, why is this happening? What dismissed FooView?

iOS version: iOS 15 on iPhone

XCode: 14.2

Here is a minimum reproducible sample code.

I noticed that if I moved NavigationView from outside of TabView into ContentView, then the issue is gone. But then the Tabs will show up even in FooView, hence I kept NavigationView outside TabView. (And why does that matter?)

import SwiftUI

@main
struct ViewDismissApp: App {
    var body: some Scene {
        WindowGroup {
            NavigationView {
                TabView {
                    ContentView(title: "Tab 1")
                        .tabItem {
                            Label("Tab1", systemImage: "wifi")
                        }
                    ContentView(title: "Tab 2")
                        .tabItem {
                            Label("Tab2", systemImage: "wrench.adjustable")
                        }
                }
            }
            .navigationViewStyle(.stack)
        }
    }
}


struct ContentView: View {
    let title: String

    var body: some View {
        VStack {
            NavigationLink {
                FooView()
            } label: {
                Text(title)
            }
        }
    }
}

struct FooView: View {
    @State private var showActions = false

    var body: some View {
        VStack {
            Text("Foo View")
            
            actionButton()
        }
    }
    
    private func actionButton() -> some View {
        Button(action: {
            showActions = true
        }) {
            Image(systemName: "arrow.up.circle")
        }
        .actionSheet(isPresented: $showActions) {
            ActionSheet(title: Text("Actions"),
                        message: nil,
                        buttons: [
                            .default(Text("Option 1")) {
                                // TODO
                            },
                            .default(Text("Option 2")) {
                                // TODO
                            },
                            .cancel()
                        ])
        }
    }
}

Thanks!

Replies

Hi @keepsimple ,

A few things here -

If you're using TabView and NavigationView, each tab must have it's own NavigationView, not the other way around. Otherwise, undefined behavior may occur, which is the issue you're describing above. So the structure of your app should look like this instead:

@main
struct ViewDismissApp: App {
    var body: some Scene {
        WindowGroup {
      
         TabView {
               NavigationView {
                    ContentView(title: "Tab 1")
                     }
                        .navigationViewStyle(.stack)
                        .tabItem {
                            Label("Tab1", systemImage: "wifi")
                        }
              NavigationView {
                    ContentView(title: "Tab 2")
               }
                      .navigationViewStyle(.stack)
                      .tabItem {
                            Label("Tab2", systemImage: "wrench.adjustable")
                        }
                }
            }
       
        }
    }
}

Alternatively, move NavigationView into ContentView as you mentioned above.

Also, ActionSheet is deprecated, please use confirmationDialog instead - https://developer.apple.com/documentation/swiftui/view/confirmationdialog(_:ispresented:titlevisibility:presenting:actions:message:)-8y541

Try this and please update if the issue still persists!