Home > Blockchain >  Interacting with a confirmationDialog or alert is causing the parent view to pop
Interacting with a confirmationDialog or alert is causing the parent view to pop

Time:10-23

When you navigate and open the confirmation dialog. When you select Yes, No or Cancel the page that the app was on is dismissed and it takes you back to the form on the previous page.

We also found this happens with alerts too.

It's a simple enough app structure, top level tabs then a menu which links to sub pages.

Here is a quick demo of the bug:

Demo of the bug

We put together an example app that demonstrates this. How can we prevent this from happening while also maintaining the app structure?

import SwiftUI

@main
struct testApp: App {
    var body: some Scene {
        WindowGroup {
            NavigationView {
                TabView() {
                    Form {
                        NavigationLink(destination: SubPage()) {
                            Image(systemName: "clock")
                            Text("Sub Page")
                        }
                        // This is where more menu options would be
                    }
                    .tag(1)
                    .tabItem {
                        Image(systemName: "square.grid.2x2")
                        Text("Tab 1")
                    }
                    // This is where more tab pages would be
                }
            }
        }
    }
}

struct SubPage: View {
    @State private var confirmDialogVisible = false
    
    var body: some View {
        VStack {
            Button{
                confirmDialogVisible = true
            } label: {
                Text("popup")
            }
        }
        .confirmationDialog("Confirm?", isPresented: $confirmDialogVisible) {
            Button("Yes") {
                print("yes")
            }
            Button("No", role: .destructive) {
                print("no")
            }
        }
    }
}

We are using XCode 14.1 And running on iOS 16.1

CodePudding user response:

I usually use ViewModifer to keep consistency between tabs. One modifier for the root of the tab and one for the children.

///Modifier that uses `ToolbarViewModifier` and includes a `NavigationView`
struct NavigationViewModifier: ViewModifier{
    func body(content: Content) -> some View {
        NavigationView{
            content
                .modifier(ToolbarViewModifier())
        }
    }
}
///`toolbar` that can be used by the root view of the navigation
///and the children of the navigation
struct ToolbarViewModifier: ViewModifier{
    let title: String = "Company Name"
    func body(content: Content) -> some View {
        content
            .toolbar {
                ToolbarItem(placement: .principal) {
                    VStack{
                        Image(systemName: "sparkles")
                        Text(title)
                    }
                }
            }
    }
}

Then the Views use it something like this.

import SwiftUI

struct CustomTabView: View {
    var body: some View {
        TabView{
            ForEach(0..<4){ n in
                CustomChildView(title: n.description)
                    //Each tab gets a `NavigationView` and the shared toolbar
                    .modifier(NavigationViewModifier())
                    .tabItem {
                        Text(n.description)
                    }
            }
        }
    }
}
struct CustomChildView: View {
    let title: String
    @State private var showConfirmation: Bool = false
    var body: some View {
        VStack{
            Text(title)
            
            NavigationLink {
                CustomChildView(title: "\(title) :: \(UUID().uuidString)")
            } label: {
                Text("open child")
            }
            Button("show confirmation") {
                showConfirmation.toggle()
            }
            .confirmationDialog("Confirm?", isPresented: $showConfirmation) {
                Button("Yes") {
                    print("yes")
                }
                Button("No", role: .destructive) {
                    print("no")
                }
            }
        }
        //Each child uses the shared toolbar
        .modifier(ToolbarViewModifier())
        
    }
}

I stuck with NavigationView since that is what you have in your code but if we take into consideration the new NavigationStack the possibilities of these two modifiers become exponentially better.

You can include custom back buttons that only appear if the path is not empty, return to the root from anywhere, etc.

Apple says that

Tab bars use bar items to navigate between mutually exclusive panes of content in the same view

Make sure the tab bar is visible when people navigate to different areas in your app

https://developer.apple.com/design/human-interface-guidelines/components/navigation-and-search/tab-bars/

Having the NavigationView or NavigationStack on top goes against these guidelines and therefore are the source of endless bugs, Especially when you take into consideration iPadOS.

CodePudding user response:

Simple solution would be to use navigationDestination.

struct testApp: App {
    
    @State var goToSubPage = false
    
    var body: some Scene {
        WindowGroup {
            NavigationStack {
                TabView() {
                    Form {
                        VStack {
                            Image(systemName: "clock")
                            Text("Sub Page")
                        }
                        .onTapGesture {
                            goToSubPage = true
                        }
                        // This is where more menu options would be
                    }
                    .navigationDestination(isPresented: $goToSubPage, destination: {
                        SubPage()
                    })
                    .tag(1)
                    .tabItem {
                        Image(systemName: "square.grid.2x2")
                        Text("Tab 1")
                    }
                    // This is where more tab pages would be
                }
            }
        }
    }
}

I tested it and it won't popped off itself anymore.

  • Related