How to present new sheet view from navigationBarItems button in SwiftUI

In this tutorial we’re going to learn how use NavigationView’s navigationBarItems button to present a new sheet view modally.

In order to accomplish this, we’re going to follow these steps:

  1. Create a NavigationView
  2. Add a button to the NavigationView
  3. Create a new sheet view which we wish to present
  4. Add action to the button to present the new sheet view
  5. For bonus points: dismiss the new sheet view with a Done button

Create a NavigationView

Add a NavigationView with some content (in this case, a simple text view) and give it a title using the navigationBarTitle() modifier:

struct ContentView: View {
    var body: some View {
        NavigationView {
            Text("Content")
            .navigationBarTitle("SwiftUI Tutorials")
        }
    }
}

Add a button to the NavigationView

Add a trailing button to to the NavigationView using navigationBarItems() modifier. We’re using an SF Symbol in place of the button. Update the NavigationView like this:

NavigationView {
    Text("Content")
    .navigationBarTitle("SwiftUI Tutorials")
    .navigationBarItems(trailing:
        Button(action: {
            print("Navigation bar item action")
        }) {
            Image(systemName: "bell.circle.fill")
                .font(Font.system(.title))
        }
    )
}

At this point, the result should look like this:

NavigationView with a navigationBarItems button in SwiftUI
NavigationView with a navigationBarItems button in SwiftUI

Create new sheet view

Now, let’s add a brand new view to our project which is going to be presented when user taps the bell button. We’re going to call it a SheetView and it’s also going to be a NavigationView with a title. In this case, the title of the navigation bar is styled to be displayed inline (in the middle of the navigation bar):

struct SheetView: View {
    var body: some View {
        NavigationView {
            Text("List of notifications")
            .navigationBarTitle(Text("Notifications"), displayMode: .inline)
        }
    }
}

This view doesn’t do much right now but we’re going to use it in the next section.

Present a new sheet view as a modal

In order to present the newly created SheetView when user taps the bell button, two things need to happen in the ContentView:

  1. Add new boolean @State variable called showSheetView to the ContentView which will control whether to show SheetView
  2. Use sheet(isPresented:) modifier which presents a sheet based on the isPresented binding (which will point at our showSheetView state variable)

Take a look at updated code:

struct ContentView: View {
    @State var showSheetView = false
    
    var body: some View {
        NavigationView {
            Text("Content")
            .navigationBarTitle("SwiftUI Tutorials")
            .navigationBarItems(trailing:
                Button(action: {
                    self.showSheetView.toggle()
                }) {
                    Image(systemName: "bell.circle.fill")
                        .font(Font.system(.title))
                }
            )
        }.sheet(isPresented: $showSheetView) {
            SheetView()
        }
    }
}

Tapping the bell button presents the SheetView!

Sheet view presented modally in SwiftUI
Sheet view presented modally in SwiftUI

In order to dismiss the SheetView, user needs to pull down on it (swipe down). However, what if we wanted to add a “Done” button to the SheetView which will dismiss it when tapped? Let’s explore how to achieve it.

Dismiss the sheet view with a Done button

The new sheet view with a Done button will look like this:

Sheet view with a Done button presented modally in SwiftUI
Sheet view with a Done button presented modally in SwiftUI

First of all, we need to add a trailing Done button to the navigation bar of the SheetView. We’re going to do it by using the navigationBarItems(trailing:) modifier just like we did for the ContentView earlier.

Then, we need to declare a @Binding variable within the SheetView which will actually reference the showSheetView variable from ContentView (because it controls whether the SheetView is presented or not).

Finally, we need to pass the showSheetView’s binding reference to the SheetView.

The final code for SheetView should look like this:

struct SheetView: View {
    @Binding var showSheetView: Bool

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

And the ContentView should look like this:

struct ContentView: View {
    @State var showSheetView = false
    var body: some View {
        NavigationView {
            Text("Content")
            .navigationBarTitle("SwiftUI Tutorials")
            .navigationBarItems(trailing:
                Button(action: {
                    self.showSheetView.toggle()
                }) {
                    Image(systemName: "bell.circle.fill")
                        .font(Font.system(.title))
                }
            )
        }.sheet(isPresented: $showSheetView) {
            SheetView(showSheetView: self.$showSheetView)
        }
    }
}

Take a look at final result of this tutorial:

Present new sheet view from NavigationView's button
Present new sheet view from NavigationView’s button

Related tutorials:

Take a look at Apple’s official NavigationView and navigationBarItems(trailing:) documentations.