Home > Blockchain >  Dismissing a SwiftUI sheet with a lot of navigation views
Dismissing a SwiftUI sheet with a lot of navigation views

Time:02-06

I have a button that opens up a Profile & Settings view in a sheet that has additional navigation views in it.

I am aware how to dismiss the sheet, however this method seems to not work with additional navigation views, as when I'm deeper into the navigation and I tap "Done" to dismiss the sheet, it only returns me back to the previous navigation view until I go back to the main Profile & Settings view.

The view with the button:

import SwiftUI

struct TodayView: View {
    @State private var showSheet = false
    
    var body: some View {
        NavigationView {
            ScrollView {
                VStack(alignment: .leading) {
                    
                    TodayTabDateComponent()
                        .padding(.top, -10)
                    
                    ForEach(0 ..< 32) { item in
                        VStack(alignment: .leading) {
                            Text("Title")
                            Text("Description")
                        }
                        .padding(.vertical)
                    }
                }
                .frame(maxWidth: .infinity, alignment: .leading)
                .padding(.horizontal)
            }
            .navigationTitle("Today")
            
            .toolbar {
                ToolbarItem(placement: .navigationBarLeading) {
                    Button(action: {
                        showSheet = true
                    }, label: {
                        Image(systemName: "person.circle.fill")
                            .foregroundColor(.primary)
                    })
                    .sheet(isPresented: $showSheet) {
                        ProfileAndSettingsView()
                    }
                }
            }
        }
    }
}

struct TodayView_Previews: PreviewProvider {
    static var previews: some View {
        TodayView()
    }
}

The Profile & Settings view:

import SwiftUI

struct ProfileAndSettingsView: View {
    @Environment(\.presentationMode) var presentationMode
    
    var body: some View {
        NavigationView {
            VStack {
                List {
                    Section {
                        NavigationLink {
                            UserProfileView()
                        } label: {
                            HStack(alignment: .center) {
                                Image("avatar")
                                    .resizable()
                                    .aspectRatio(contentMode: .fit)
                                    .clipShape(Circle())
                                    .frame(width: 60, height: 60)
                                
                                VStack(alignment: .leading) {
                                    Text("Name Surname")
                                        .font(.title2)
                                        .fontWeight(.bold)
                                    
                                    Text("Profile Settings, Feed Preferences\n& Linked Accounts")
                                        .font(.caption)
                                }
                            }
                        }
                        .padding(.vertical, 6)
                    }            }
                .listStyle(.insetGrouped)
                
                .navigationTitle("Profile & Settings")
                .navigationBarTitleDisplayMode(.inline)
                
                .toolbar {
                    ToolbarItem(placement: .navigationBarTrailing) {
                        Button {
                            presentationMode.wrappedValue.dismiss()
                        } label: {
                            Text("Done")
                        }
                    }
                }
            }
        }
    }
}


struct ProfileAndSettingsView_Previews: PreviewProvider {
    
    static var previews: some View {
        ProfileAndSettingsView()
    }
}

I have looked into the issue but couldn't find any working solutions.

CodePudding user response:

Is your issue here that you're applying the .sheet to the Button inside the Toolbar? I think you need to apply it to the NavigationView itself?

import SwiftUI

struct TodayView: View {
    @State private var showSheet = false
    
    var body: some View {
        NavigationView {
            ....
            }
            .navigationTitle("Today")
            .toolbar {
                ToolbarItem(placement: .navigationBarLeading) {
                    Button(action: {
                        showSheet = true
                    }, label: {
                        Image(systemName: "person.circle.fill")
                            .foregroundColor(.primary)
                    })
                }
            }
            .sheet(isPresented: $showSheet) {
                ProfileAndSettingsView()
            }
        }
    }
}

CodePudding user response:

If you're targeting iOS15 or higher don't use presentationMode use @Environment(\.isPresented) private var isPresented instead this will perform the action that you want.

presentationMode was deprecated and replaced by isPresented and dismiss

I believe that presentationMode performs a similar action as dismiss does which according to Apple Docs (on the dismiss)

If you do this, the sheet fails to dismiss because the action applies to the environment where you declared it, which is that of the detail view, rather than the sheet. In fact, if you’ve presented the detail view in a NavigationView, the dismissal pops the detail view the navigation stack. The dismiss action has no effect on a view that isn’t currently presented. If you need to query whether SwiftUI is currently presenting a view, read the isPresented environment value.

If you're targeting a lower iOS version you can create your own key like so

struct SheetOpen: EnvironmentKey {
    static var defaultValue: Binding<Bool> = .constant(false)
}

extension EnvironmentValues {
    var sheetOpen: Binding<Bool> {
        get { self[SheetOpen.self] }
        set { self[SheetOpen.self] = newValue }
    }
}

Where you have your sheet defined you do this

.sheet(isPresented: $showSheet) {
    ProfileAndSettingsView()
        .environment(\.sheetOpen, $showSheet)
}

Then you can use it like any other environment variable

@Environment(\.sheetOpen) var sheetOpen

To dismiss it you simply do this sheetOpen.wrappedValue.toggle()

  • Related