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: