How to make a SwiftUI .sheet() size match the width/height of window on MacOS?

Viewed 2198

Background

I am working on a MacOS application in SwiftUI in Xcode 12.4 on Catalina. Right now my app has a landing page with tiles and each tile contains a feature that is displayed using sheets.

ForEach(MenuItem.allCases) { menuItem in
    Button(action: {
        detailView = menuItem
    }) {
        Tile(labelText: menuItem.menuText, icon: menuItem.menuIcon)
            .sheet(item: $detailView) { item in
                FeatureView(dismissFeatureView: { self.detailView = nil }, menuItem: item)
                    .environmentObject(container)
                    .background(Color("backgroundMain"))
            }
    }
}

The window has a minimum width and height, but can be expanded given it has no max height or width. I'm looking for a way to make the sheet take up the entire screen. On iOS you can use .fullScreenCover() but this isn't available on MacOS. So now, when I make the screen large and click on a tile the sheet view doesn't take up the entire space.

Additionally, the user is able to resize the sheet beyond the size of the window, as seen here.

The Question

How can I get the sheet views to match the size of the window (which does not have a max width or height) so the user can still resize the window, but ensure that the sheet always takes up the entirety of the window.

2 Answers

One solution is to use GeometryReader to obtain the size of the parent view, and apply that to the frame of the sheet. See code below:

GeometryReader { geometry in
    VStack(alignment: .center) {
        HStack(alignment: .center, spacing: 20) {
            ForEach(MenuItem.allCases) { menuItem in
                Button(action: {
                    detailView = menuItem
                }) {
                    Tile(labelText: menuItem.menuText, icon: menuItem.menuIcon)
                        .sheet(item: $detailView) { item in
                            FeatureView(dismissFeatureView: { self.detailView = nil }, menuItem: item)
                                .environmentObject(container)
                                .background(Color("backgroundMain"))
                                .frame(width: geometry.size.width, height: geometry.size.height)
                        }

                }
            }
        }
    }
}

If you're displaying a sheet after a user interaction, you can set the sheet content's frame like this:

.frame(idealWidth: NSApp.keyWindow?.contentView?.bounds.width ?? 500, idealHeight: NSApp.keyWindow?.contentView?.bounds.height ?? 500)

The neat part is that this can be used by nested controls that don't know anything about their superviews.

500 is used as a fallback width/height if a keyWindow can't be found.

Keep in mind: The sheet may be taller than the height of its content's frame, because it may contain additional controls (e.g. a toolbar).

Related