How to Present Multiple Sheets in SwiftUI

DevTechie Inc
Dec 7, 2022


Photo by Andy Hall on Unsplash

Presenting more than one Sheet in SwiftUI can sometimes be tricky, as it turns out that with the help of an enum and a bit of code organization technique, we can manage multiple sheet launch in SwiftUI easily.

In this article, we will work on a solution to present multiple sheets from a single view. Here is our final product in action:

Let’s start with a data structure to store pages we want to launch in a sheet.

enum Page: Identifiable {
    case home, profile, settings
    var id: Int {
        hashValue
    }
}
Next, we will create three views. One for each home, profile and settings. These views will also take a Binding parameter so we can pass data between views.

Home

struct DTHomeView: View {
    @Binding var passedValue: Int
    var body: some View {
        NavigationStack {
            VStack {
                Text("Home")
                    .font(.largeTitle)
                Image(systemName: "house")
                    .font(.largeTitle)
                    .foregroundColor(.orange)
                Button("Increase") {
                    passedValue += 10
                }
            }
            .navigationTitle("DevTechie.com")
        }
    }
}
Profile

struct DTProfileView: View {
    @Binding var passedValue: Int
    
    var body: some View {
        NavigationStack {
            VStack {
                Text("Profile")
                    .font(.largeTitle)
                Image(systemName: "person.circle")
                    .font(.largeTitle)
                    .foregroundColor(.mint)
                Button("Increase") {
                    passedValue += 10
                }
            }
                .navigationTitle("DevTechie.com")
        }
    }
}
Settings

struct DTSettingsView: View {
    @Binding var passedValue: Int
    
    var body: some View {
        NavigationStack {
            VStack {
                Text("Settings")
                    .font(.largeTitle)
                Image(systemName: "gear")
                    .font(.largeTitle)
                    .foregroundColor(.indigo)
                Button("Increase") {
                    passedValue += 10
                }
            }
            .navigationTitle("DevTechie.com")
        }
    }
}
For our main view, we will create two state variables. One to pass a value to other views, other is to hold the currentPage. We will have three buttons, each will set currentPage variable’s value to the view it wants to launch.

struct ContentView: View {
    @State var currentPage: Page?
    @State var valueToPass = 10
    
    var body: some View {
        VStack {
            Text("Current Value: \(valueToPass)")
            
            Button {
                currentPage = .home
            } label: {
                Label("Home", systemImage: "house.fill")
                    .frame(width: 200)
            }
            .buttonStyle(.borderedProminent)
            
            Button {
                currentPage = .profile
            } label: {
                Label("Profile", systemImage: "person.circle.fill")
                    .frame(width: 200)
            }
            .buttonStyle(.borderedProminent)
            
            Button {
                currentPage = .settings
            } label: {
                Label("Settings", systemImage: "gear.circle.fill")
                    .frame(width: 200)
            }
            .buttonStyle(.borderedProminent)
        }
    }
}
Now, we will add code to launch sheet. We will pass currentPage as item parameter for sheet and apply a switch statement which will determine which view to display based on the value in currentPage.

.sheet(item: $currentPage) { item in
    switch item {
    case .home:
        DTHomeView(passedValue: $valueToPass)case .profile:
        DTProfileView(passedValue: $valueToPass)case .settings:
        DTSettingsView(passedValue: $valueToPass)
    }
}
Complete code will look like this:

struct ContentView: View {
    @State var currentPage: Page?
    @State var valueToPass = 10
    
    var body: some View {
        VStack {
            Text("Current Value: \(valueToPass)")
            
            Button {
                currentPage = .home
            } label: {
                Label("Home", systemImage: "house.fill")
                    .frame(width: 200)
            }
            .buttonStyle(.borderedProminent)
            
            Button {
                currentPage = .profile
            } label: {
                Label("Profile", systemImage: "person.circle.fill")
                    .frame(width: 200)
            }
            .buttonStyle(.borderedProminent)
            
            Button {
                currentPage = .settings
            } label: {
                Label("Settings", systemImage: "gear.circle.fill")
                    .frame(width: 200)
            }
            .buttonStyle(.borderedProminent)
        }
        .sheet(item: $currentPage) { item in
            switch item {
            case .home:
                DTHomeView(passedValue: $valueToPass)
                
            case .profile:
                DTProfileView(passedValue: $valueToPass)
                
            case .settings:
                DTSettingsView(passedValue: $valueToPass)
            }
        }
    }
}
Build and run:

We can present fullScreenCover as well by simply changing sheet to fullScreenCover:

struct ContentView: View {
    @State var currentPage: Page?
    @State var valueToPass = 10
    
    var body: some View {
        VStack {
            Text("Current Value: \(valueToPass)")
            
            Button {
                currentPage = .home
            } label: {
                Label("Home", systemImage: "house.fill")
                    .frame(width: 200)
            }
            .buttonStyle(.borderedProminent)
            
            Button {
                currentPage = .profile
            } label: {
                Label("Profile", systemImage: "person.circle.fill")
                    .frame(width: 200)
            }
            .buttonStyle(.borderedProminent)
            
            Button {
                currentPage = .settings
            } label: {
                Label("Settings", systemImage: "gear.circle.fill")
                    .frame(width: 200)
            }
            .buttonStyle(.borderedProminent)
        }
        .fullScreenCover(item: $currentPage) { item in
            switch item {
            case .home:
                DTHomeView(passedValue: $valueToPass)
                
            case .profile:
                DTProfileView(passedValue: $valueToPass)
                
            case .settings:
                DTSettingsView(passedValue: $valueToPass)
            }
        }
    }
}
But now, we can’t dismiss the sheet. Well there is a simple solution to that too. We will take help from Environment property wrapper.

We use the Environment property wrapper to read a value stored in a view’s environment.
Let’s add Environment property wrapper at the top of our pages, as shown below:

struct DTHomeView: View {
    @Environment(\.dismiss) var dismiss
    @Binding var passedValue: Int
    var body: some View {
        NavigationStack {
            VStack {
                Text("Home")
                    .font(.largeTitle)
                Image(systemName: "house")
                    .font(.largeTitle)
                    .foregroundColor(.orange)
                Button("Increase") {
                    passedValue += 10
                }
            }
            .navigationTitle("DevTechie.com")
        }
    }
}
Next, add a button to dismiss current view.

struct DTHomeView: View {
    @Environment(\.dismiss) var dismiss
    @Binding var passedValue: Int
    var body: some View {
        NavigationStack {
            VStack {
                Text("Home")
                    .font(.largeTitle)
                Image(systemName: "house")
                    .font(.largeTitle)
                    .foregroundColor(.orange)
                Button("Increase") {
                    passedValue += 10
                }
                Button("Dismiss") {
                    dismiss()
                }
                .buttonStyle(.bordered)
            }
            .navigationTitle("DevTechie.com")
        }
    }
}
Build and run:

Now, we are launching full screen sheet, modifying the state and dismissing view without any issues.

Let’s make this change in all views, build and run for one more time.

Complete code below:

struct ContentView: View {
    @State var currentPage: Page?
    @State var valueToPass = 10
    
    var body: some View {
        VStack {
            Text("Current Value: \(valueToPass)")
            
            Button {
                currentPage = .home
            } label: {
                Label("Home", systemImage: "house.fill")
                    .frame(width: 200)
            }
            .buttonStyle(.borderedProminent)
            
            Button {
                currentPage = .profile
            } label: {
                Label("Profile", systemImage: "person.circle.fill")
                    .frame(width: 200)
            }
            .buttonStyle(.borderedProminent)
            
            Button {
                currentPage = .settings
            } label: {
                Label("Settings", systemImage: "gear.circle.fill")
                    .frame(width: 200)
            }
            .buttonStyle(.borderedProminent)
        }
        .fullScreenCover(item: $currentPage) { item in
            switch item {
            case .home:
                DTHomeView(passedValue: $valueToPass)
                
            case .profile:
                DTProfileView(passedValue: $valueToPass)
                
            case .settings:
                DTSettingsView(passedValue: $valueToPass)
            }
        }
    }
}

struct DTHomeView: View { @Environment(\.dismiss) var dismiss @Binding var passedValue: Int var body: some View { NavigationStack { VStack { Text("Home") .font(.largeTitle) Image(systemName: "house") .font(.largeTitle) .foregroundColor(.orange) Button("Increase") { passedValue += 10 } Button("Dismiss") { dismiss() } .buttonStyle(.bordered) } .navigationTitle("DevTechie.com") } } }
struct DTProfileView: View { @Environment(\.dismiss) var dismiss @Binding var passedValue: Int var body: some View { NavigationStack { VStack { Text("Profile") .font(.largeTitle) Image(systemName: "person.circle") .font(.largeTitle) .foregroundColor(.mint) Button("Increase") { passedValue += 10 } Button("Dismiss") { dismiss() } .buttonStyle(.bordered) } .navigationTitle("DevTechie.com") } } }
struct DTSettingsView: View { @Environment(\.dismiss) var dismiss @Binding var passedValue: Int var body: some View { NavigationStack { VStack { Text("Settings") .font(.largeTitle) Image(systemName: "gear") .font(.largeTitle) .foregroundColor(.indigo) Button("Increase") { passedValue += 10 } Button("Dismiss") { dismiss() } .buttonStyle(.bordered) } .navigationTitle("DevTechie.com") } } }
With that we have reached the end of this article. Thank you once again for reading. Don’t forget to follow 😍. Also subscribe our newsletter at https://www.devtechie.com