RXP
RXP

Reputation: 677

Presenting Sheet modally in SwiftUI

I am trying to present a modal sheet upon selecting the menu item in the navigation bar. But, the sheet is not displayed. Upon debugging I noticed that the state variable showSheet is not getting updated and I am sort of lost as to why it is not updating.

Any help is very much appreciated. Thank you!

There is another post (@State not updating in SwiftUI 2) that has a similar issue. Is this a bug in SwiftUI?

Below is a full sample

I have a fileprivate enum that defines two cases for the views - add and edit

fileprivate enum SheetView {
    case add, edit
}

Below is the ContentView. The ContentView declares two @State variables that are set based on the menu item selected The menu items (var actionItems) are on the NavigationView and has menu with 2 buttons - Add and Edit. Each button has an action set to toggle the showSheetView and the showSheet variables. The content is presented based on which item is selected. Content is built using @ViewBuilder

struct ContentView: View {
    @State private var showSheetView = false
    @State private var showSheet: SheetView? = nil
    
    var body: some View {
        GeometryReader { g in
            NavigationView {
                Text("Main Page")
                    .padding()
                    .navigationBarTitle("Main Page")
                    .navigationBarItems(trailing: actionItems)
            }
        }.sheet(isPresented: $showSheetView) {
            content
        }
    }
    
    var actionItems: some View {
        Menu {
            Button(action: {
                showSheet = .add
                showSheetView.toggle()
            }) {
                Label("Add Asset", systemImage: "plus")
            }
            Button(action: {
                showSheet = .edit
                showSheetView.toggle()
            }) {
                Label("Edit Asset", systemImage: "minus")
            }
        } label: {
            Image(systemName: "dot.circle.and.cursorarrow").resizable()
        }
    }
    
    @ViewBuilder
    var content: some View {
        if let currentView = showSheet {
            switch currentView {
            case .add:
                AddAsset(showSheetView: $showSheetView)
            case .edit:
                EditAsset(showSheetView: $showSheetView)
            }
        }
    }
    
}

Below are the two Views - AddAsset and EditAsset

struct AddAsset: View {
    @Binding var showSheetView: Bool

    var body: some View {
        NavigationView {
            Text("Add Asset")
                .navigationBarTitle(Text("Add"), displayMode: .inline)
                .navigationBarItems(trailing: Button(action: {
                    print("Dismissing sheet view...")
                    self.showSheetView = false
                }) {
                    Text("Done").bold()
                })
        }
    }
}

struct EditAsset: View {
    @Binding var showSheetView: Bool
    var body: some View {
        NavigationView {
            Text("Edit Asset")
                .navigationBarTitle(Text("Edit"), displayMode: .inline)
                .navigationBarItems(trailing: Button(action: {
                    print("Dismissing sheet view...")
                    self.showSheetView = false
                }) {
                    Text("Done").bold()
                })
        }
    }
}

Upvotes: 1

Views: 1674

Answers (1)

Asperi
Asperi

Reputation: 257711

The solution is to use sheet(item: variant.

Here is fixed code (there are many changes so all components included). Tested with Xcode 12.1 / iOS 14.1

demo

enum SheetView: Identifiable {
    var id: Self { self }
    case add, edit
}

struct ContentView: View {
    @State private var showSheet: SheetView? = nil
    
    var body: some View {
        GeometryReader { g in
            NavigationView {
                Text("Main Page")
                    .padding()
                    .navigationBarTitle("Main Page")
                    .navigationBarItems(trailing: actionItems)
            }
        }.sheet(item: $showSheet) { mode in
            content(for: mode)
        }
    }
    
    var actionItems: some View {
        Menu {
            Button(action: {
                showSheet = .add
            }) {
                Label("Add Asset", systemImage: "plus")
            }
            Button(action: {
                showSheet = .edit
            }) {
                Label("Edit Asset", systemImage: "minus")
            }
        } label: {
            Image(systemName: "dot.circle.and.cursorarrow").resizable()
        }
    }
    
    @ViewBuilder
    func content(for mode: SheetView) -> some View {
        switch mode {
        case .add:
            AddAsset(showSheet: $showSheet)
        case .edit:
            EditAsset(showSheet: $showSheet)
        }
    }
    
}

struct AddAsset: View {
    @Binding var showSheet: SheetView?
    
    var body: some View {
        NavigationView {
            Text("Add Asset")
                .navigationBarTitle(Text("Add"), displayMode: .inline)
                .navigationBarItems(trailing: Button(action: {
                    print("Dismissing sheet view...")
                    self.showSheet = nil
                }) {
                    Text("Done").bold()
                })
        }
    }
}

struct EditAsset: View {
    @Binding var showSheet: SheetView?
    var body: some View {
        NavigationView {
            Text("Edit Asset")
                .navigationBarTitle(Text("Edit"), displayMode: .inline)
                .navigationBarItems(trailing: Button(action: {
                    print("Dismissing sheet view...")
                    self.showSheet = nil
                }) {
                    Text("Done").bold()
                })
        }
    }
}

Upvotes: 3

Related Questions