swiftuidismissios15

Dismissing sheet with button in SwiftUI


I am currently trying to figure out how to dismiss a sheet in SwiftUI. I created a view for the Dismiss-Button because I want to use that button multiple times at different locations in the app. When the sheet pops up, pressing the button is not doing anything, I can only dismiss the sheet via swiping down on the screen.

I first tried to follow a YouTube tutorial, but it uses the deprecated .presentationMode Therefore I am currently trying to make the .dismiss variable working, but I can't figure out what I am doing wrong. I already tried declaring .dismiss in the Button-View and Portfolio-View. I also used dismiss() and dismiss.callAsFunction().

Here is my Button-View (XMarkButton), the presented Sheet-View (PortfolioView) and my Home-View, where the sheet will be displayed:

XMarkButton

struct XMarkButton: View {
    @Environment(\.dismiss) private var dismiss
    
    var body: some View {
        Button(action: {
            dismiss.callAsFunction()
        }, label: {
            Image(systemName: "xmark")
                .font(.headline)
        })
    }
}

PortfolioView

struct PortfolioView: View {    
    var body: some View {
        NavigationView {
            ScrollView {
                VStack(alignment: .leading, spacing: 0) {
                    Text("Portfolio")
                }
            }
            .navigationTitle("Edit Portfolio")
            .toolbar(content: {
                ToolbarItem(placement: .navigationBarLeading) {
                    XMarkButton()
                }
            })
        }
    }
}

HomeView

struct HomeView: View {
    
    @EnvironmentObject private var vm: HomeViewModel
    @State private var showPortfolio: Bool = false
    @State private var showPortfolioView: Bool = false
    
    var body: some View {
        ZStack {
            // Background layer
            Color.theme.background
                .ignoresSafeArea()
                .sheet(isPresented: $showPortfolioView) {
                    PortfolioView()
                        .environmentObject(vm)
                }
            
            VStack {
                homeHeader
                HomeStatsView(showPortfolio: $showPortfolio)
                
                SearchBarView(searchText: $vm.searchText)
                
                columnTitles
                
                if !showPortfolio {
                    allCoinsList
                    .transition(.move(edge: .leading))
                }
                if showPortfolio {
                    portfolioCoinsList
                        .transition(.move(edge: .trailing))
                }
                
                Spacer(minLength: 0)
            }
        }
    }
}

Solution

  • The easiest way to get this up and running would be to move the dismiss functionality into the view that needs to be dismissed.

    The problem with the current approach is you are attempting to dismiss the X button, not the actual view.

    struct XMarkButton: View {
        
        var body: some View {
            Image(systemName: "xmark") //changed to image, can change color here if needed
                .font(.headline)
        }
    }
    
    
    
    struct PortfolioView: View {
        @Environment(\.dismiss) private var dismiss // moved dismiss functionality here
        
        var body: some View {
            NavigationView {
                ScrollView {
                    VStack(alignment: .leading, spacing: 0) {
                        Text("Portfolio")
                    }
                }
                .navigationTitle("Edit Portfolio")
                .toolbar(content: {
                    ToolbarItem(placement: .navigationBarLeading) {
                        XMarkButton().onTapGesture { // on tap gesture calls dismissal
                            dismiss()
                        }
                    }
                })
            }
        }
    }
    

    An alternate approach that keeps your same structure would be to use a completion handler - see below:

    struct XMarkButton: View {
        var action: () -> Void
        
        var body: some View {
            Button(action: {
                action()
            }, label: {
                Image(systemName: "xmark")
                    .font(.headline)
            })
        }
    }
    
    
    
    struct PortfolioView: View {
        @Environment(\.dismiss) private var dismiss
        
        var body: some View {
            NavigationView {
                ScrollView {
                    VStack(alignment: .leading, spacing: 0) {
                        Text("Portfolio")
                    }
                }
                .navigationTitle("Edit Portfolio")
                .toolbar(content: {
                    ToolbarItem(placement: .navigationBarLeading) {
                        XMarkButton {
                            dismiss()
                        }
                    }
                })
            }
        }
    }