SwiftUI WindowGroup: How to limit the number of windows?

3.1k views Asked by At

I am building a single window application and want to use the new Swift App Lifecycle.

import SwiftUI

@main
struct SingleWindowApp: App {
    var body: some Scene {
        WindowGroup {
            ContentView()
        }
    }
}

The default implementation of WindowGroup allows multiple instances of the window (i.e. if you hit ⌘N). I wasn’t able to find a modifier that changes that behaviour.

How would I limit the number of windows within a WindowGroup to just 1?

5

There are 5 answers

2
Mihai Fratu On BEST ANSWER

This should do it:

import SwiftUI

@main
struct SingleWindowApp: App {
    var body: some Scene {
        WindowGroup {
            ContentView()
        }.commands {
            CommandGroup(replacing: .newItem, addition: { })
        }
    }
}
0
warly On

As i was facing the same problem, but on iPad where the command modifier has no effect, I found this: There's an "Application Scene Manifest" UIApplicationSceneManifest property in your Info.plist that is a dictionary and as a child "Enable Multiple Windows" UIApplicationSupportsMultipleScenes which is set to YES by default. Settings this option to NO gives the desired effect :)

Info.plist for disabling multiple window support

0
kdubb On

In Xcode 14 it's as simple as creating a Scene with only a Window with id main.

This opens the window and does not include a File -> New menu item.

var body: some Scene {
  Window("My Single Window", id: "main") {
    ContentView()
  }
}

Relevant documentation here: https://developer.apple.com/documentation/swiftui/window#Use-a-window-as-the-main-scene

0
user1397892 On

Each time you open a WindowGroup object, you can use NSViewControllerRepresentable to get the NSWindow instance of the view.

Then consider defining an external object to store the collection of NSWindows. The next time you open the WindowGroup object, if the NSWindow collection is full, find a corresponding NSWindow to display. for example

if windowList.isfull {
let window = windowList.getWindow()
window.makeKey()
window.orderFront(nil)
} else {
  NSWorkspace.shared.open(url)
}

On how to obtain an instance of NSWindow from WindowGroup, you can consider an open source implementation: https://github.com/happycodelucky/SwiftUIWindowBinder

0
danneu On

Here's how you can use normal SwiftUI code to disable "File -> New Window" when the main window is open, and enable "File -> New Window" when the main window is closed.

This code probably has some edge cases that could be polished, but it does work.

import SwiftUI

@main
struct MyApp: App {
    @State var isMainWindowOpen = false
    
    var body: some Scene {
        WindowGroup {
            ContentView()
                .onAppear {
                    print("Main window appeared")
                    self.isMainWindowOpen = true
                }
                .onDisappear {
                    print("Main window disappeared")
                    self.isMainWindowOpen = false
                }

        }.commands {
            if isMainWindowOpen {
                CommandGroup(replacing: .newItem) {
                    Button("New Window", action: {})
                        .disabled(true)
                        // This is the same keyboard shortcut as the default New Window option.
                        // We're just doing this so that our disabled dummy option shows
                        // the same shortcut visually.
                        .keyboardShortcut(KeyboardShortcut("n", modifiers: [.command]))
                }
            } else {
                // By doing nothing here, we let the default 
                // "File -> New Window" item display and handle input.
                EmptyCommands()
            }
        }
    }
}