Does the new SwiftUI App Lifecycle support new windows on macOS/iPadOS?

In the AppKit lifecycle it was possible to open new windows by calling showWindow(_ sender: Any?) on an NSWindowController instance, whether it was initialized via a storyboard or via code alone.

I see that on macOS in the new SwiftUI Lifecycle you can create a new window by default using ⌘N, but it's a new copy of the existing window. Can we declare a completely unique scene and open it in a new window?

I saw that it was possible to declare custom scenes, but how do we invoke them if they aren't the default scene at startup?

Thanks!

Accepted Reply

Here is how to open a new window in SwiftUI on macOS.
In your ContentView create a button and open a URL for your app and another View e.g. Viewer to be shown in the window we will open:
Code Block
struct ContentView: View {
@Environment(\.openURL) var openURL
var body: some View {
VStack {
Button("Open Viewer") {
if let url = URL(string: "myappname://viewer") {
openURL(url)
}
}
Text("Hello, world!")
}
.padding()
}
}
struct Viewer: View {
var body: some View {
Text("Viewer")
}
}

In your App add another WindowGroup for your viewer and set it to enable handling of external launch events (an internal event in our case).
Code Block
@main
struct GroupDefaultsTestApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
WindowGroup("Viewer") { // other scene
Viewer()
}
.handlesExternalEvents(matching: Set(arrayLiteral: "*"))
}
}

Now in Project->Info->URL Types type in myappname in the URL Schemes field (and the identifier field too) to register our app with the system.
Now run your app and click the button and it should open a new window!

Replies

A temporary workaround is to still rely on the AppKit Lifecycle and to manage multiple NSWindow instances manually. You can use NSHostingControllers to load SwiftUI views into the window.

For example:
Code Block swift
import Cocoa
import SwiftUI
@NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {
var primaryWindow: NSWindow?
func applicationDidFinishLaunching(_ aNotification: Notification) {
let hostingController = NSHostingController(rootView: SwiftUIView())
window = NSWindow(contentViewController: hostingController)
window?.toolbar = NSToolbar()
window?.title = "SwiftUI Test Window"
window?.makeKeyAndOrderFront(nil)
}
}
struct SwiftUIView: View {
var body: some View {
Text("Hello, World!")
.frame(minWidth: 640, maxWidth: .infinity, minHeight: 480, maxWidth: .infinity)
}
}

Just make sure the Swift UI view has some kind of declared or implicit minimum size or the window will be extremely small/practically invisible. More windows can be opened the same way, which is what I was trying to achieve.

I'd still like to figure out some kind of window management solution for SwiftUI projects. If anyone knows how to instantiate new windows directly through the SwiftUI lifecycle I'd love to know!
Its mind-blowing that something like this can be missing still.
@lupinglade Agreed. It's been really frustrating to try to emulate these changes without anything documented. I know it's all in beta, but a lot of the working sessions were showing them off as if these features already exist. It would be nice to get some sample code from those sessions.
I don’t understand why there are primitive scenes for WindowGroup and DocumentGroup but nothing for just Window


It would make complete sense to treat a Window similarly to a Rectangle View and be allows to pass multiple Window scenes in a Group.

Based on what I’ve read about iPad OS these Windows should work on iPadOS as well. Of course they wouldn’t work on iPhone which is why I think there is still this strange limitation.

I hope Apple adds multiple window support to iPhone in the near future. It could work by taking the tabs view from safari and putting it into the multitasking view. WebOS had a super nice design for it years ago. Since iOS has already learnt so many lessons, a few more would be nice.
Here is how to open a new window in SwiftUI on macOS.
In your ContentView create a button and open a URL for your app and another View e.g. Viewer to be shown in the window we will open:
Code Block
struct ContentView: View {
@Environment(\.openURL) var openURL
var body: some View {
VStack {
Button("Open Viewer") {
if let url = URL(string: "myappname://viewer") {
openURL(url)
}
}
Text("Hello, world!")
}
.padding()
}
}
struct Viewer: View {
var body: some View {
Text("Viewer")
}
}

In your App add another WindowGroup for your viewer and set it to enable handling of external launch events (an internal event in our case).
Code Block
@main
struct GroupDefaultsTestApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
WindowGroup("Viewer") { // other scene
Viewer()
}
.handlesExternalEvents(matching: Set(arrayLiteral: "*"))
}
}

Now in Project->Info->URL Types type in myappname in the URL Schemes field (and the identifier field too) to register our app with the system.
Now run your app and click the button and it should open a new window!
Fantastic workaround from @malc that doesn't require importing AppKit. Kudos!