Showing a SwiftUI sheet from a Mac Menu

Most articles on showing sheets in SwiftUI apps show an example of a button with a .sheet modifier that shows the sheet. But if you try to apply the example to a menu in a Mac app,

struct PreviewMenu: View {
  @State private var isPreviewing = false
            
  var body: some View {
    Button(action: {
      isPreviewing = true
    }, label: {
      Text("Preview")
    })          
    .sheet(isPresented: $isPreviewing) {
      PreviewView()
    }
  }
}

The sheet does not appear when you choose the menu item. How do you show the sheet when someone chooses the menu item?

  • Add a focused value for showing the sheet.
  • Set the focused scene value in your main view.
  • Add the menu item to show the sheet.
  • Add the menu item to the app’s menu bar.

Read the Accessing the Document in a SwiftUI Menu article to learn more about focused values and working with SwiftUI menus.

Add a Focused Value

Add a focused value that your app uses to control whether or not to show the sheet. In this article I’m going to use the example of a menu item that previews some content in a sheet.

struct ShowPreviewKey: FocusedValueKey {
  typealias Value = Binding<Bool>
}
    
extension FocusedValues {
  var showPreview: Binding<Bool>? {
    get { self[ShowPreviewKey.self] }
    set { self[ShowPreviewKey.self] = newValue }
  }
}  

Set the Focused Scene Value

After creating the focused value to show the sheet, you must make that value a focused scene value to show the sheet from a menu. Start by adding a property to the main SwiftUI view.

@State private var showingPreview: Bool = false

The next step is to add a .focusedSceneValue modifier to the view. The first argument is the name of the variable you created for the focused value. The second argument is a binding using the property you created in the view.

.focusedSceneValue(\.showPreview, $showingPreview)
.sheet(isPresented: $showingPreview) {
  PreviewView()
}

Apple added the .focusedSceneValue modifier in macOS 12.

Add the Menu Item

Menus in SwiftUI apps are SwiftUI views. To show a sheet from the menu, start by creating a property with the @FocusedValue property wrapper that contains the focused value to show the sheet.

Create a button for the menu item. The action for the button is to set the focused value’s wrapped value. The focused value for showing a sheet is a Boolean value so you give it the value of true, which shows the sheet when someone chooses the menu item.

struct PreviewMenu: View {
  @FocusedValue(\.showPreview) private var showPreview
            
  var body: some View {
    Button(action: {
      showPreview?.wrappedValue = true
    }, label: {
      Text("Preview")
    })
    .disabled(showPreview == nil)    
  }
}

Add the Menu to the App

After creating the menu you must add it to your app’s menu bar. Add a .commands modifier to the app’s window group or document group to add the menu to the menu bar.

.commands {
  // Replace .newItem with wherever you want to insert your menu item.
  CommandGroup(after: .newItem) {
    PreviewMenu()
  }
}

Credits

Thanks to Jason Armstrong for his answer to the following Stack Overflow question:

Calling a sheet from a menu item

Get the Swift Dev Journal Newsletter

Subscribe and get exclusive articles, a free guide on moving from tutorials to making your first app, notices of sales on books, and anything I decide to add in the future.

    We won't send you spam. Unsubscribe at any time.