How to Handle Multiple Sheets in a Single View Using Enums

Something that ought to be simple in SwiftUI but really isn’t is presenting different sheets from the same View. You’d think that by chaining multiple .sheet() and having each one hooked up to its own individual @State boolean would suffice, but the way SwiftUI works would mean that only the last .sheet() would be used.

After a lot of wrangling, I found a solution that works really well for me. Instead of using .sheet(isPresented: ...), I used .sheet(item: ...), which will present the sheet if the item binding is not nil. You can then access that item - which can be of any type, as long as it’s optional - inside the .sheet() closure, using it however you like, such as passing it as an argument to the View you are presenting inside the sheet.

I figured that I could create an enum called ActiveSheet with a case for each of the different sheets that I wanted to present. However, as I had data held in an array and I used a ForEach to generate the views for it, I had no way of getting each value into the .sheet() closure. That’s when I remembered the true power of enum - I could use associated values to pass in the data for the row that I had tapped on. Using a switch statement inside .sheet(), I can then initialise that associated value and pass it in to my View!

Below is a working example of what I just explained. You can toggle between SheetA and SheetB, and tapping on any row in the List will present the toggled sheet with the string you tapped on passed in.

This example will also work with no associated values - just remove them from the ActiveSheet cases, and remove the (let arg) inside the .sheet() closure.

struct Arg: Identifiable {
    let id = UUID().uuidString
    let string: String
}

struct ContentView: View {
    
    enum ActiveSheet: Identifiable {
        case sheetA(_ arg: Arg), sheetB(_ arg: Arg)
        
        var id: String {
            UUID().uuidString
        }
    }
    
    @State private var toggleOn: Bool = true
    @State private var activeSheet: ActiveSheet?
    
    let args = [Arg(string: "A"), Arg(string: "B"), Arg(string: "C"), Arg(string: "D")]
    
    var body: some View {
        List {
            Toggle(isOn: $toggleOn, label: {
                Text("Use sheet A?")
            })
            ForEach(args) { arg in
                Button(action: { activeSheet = toggleOn ? .sheetA(arg) : .sheetB(arg) }, label: {
                    Text(arg.string)
                })
            }
        }
        .sheet(item: $activeSheet) { sheet in
            switch sheet {
                case .sheetA(let arg):
                    SheetA(arg: arg)
                case .sheetB(let arg):
                    SheetB(arg: arg)
            }
        }
    }
}

struct SheetA: View {
    
    let arg: Arg
    
    var body: some View {
        Text("This is Sheet 'A', and my arg is: \(arg.string)")
    }
}

struct SheetB: View {
    
    let arg: Arg
    
    var body: some View {
        Text("This is Sheet 'B', and my arg is: \(arg.string)")
    }
}

Conclusion#

If this tutorial helped you in any way, I’d really appreciate a share on Twitter. You can reach out to me via Twitter if you have any questions, or to show me what you’ve been up to!