Programmatically open a new window in SwiftUI on macOS
In macOS 13 we finally have a way to programmatically present a window in SwiftUI. We can call the new openWindow action from the environment and pass it a scene id or a value.
In this post we are going to see how we could open a note from a list of notes in a new window like, for example, in the Notes app on the Mac.
First, we'll add a context menu to each item in the list, so that the user can right-click and choose to open a note in a new window. Inside the button action we'll call the openWindow
action from the environment and pass it the note id.
struct ContentView: View {
...
@Environment(\.openWindow) var openWindow
var body: some View {
NavigationSplitView {
List(
dataStore.notes, selection: $selectedNote
) { note in
NavigationLink(note.name, value: note.id)
.contextMenu {
Button("Open Note in New Window") {
openWindow(value: note.id)
}
}
}
} detail: {
NoteView(noteId: selectedNote)
}
}
}
Notice that we are passing the note id, rather than the note value. Our detail view will get a binding to the note from the shared data store based on that id. This way it will allow users to edit the same note, rather than its copy.
Users can now right-click on a note in the list and see that they can open it in a new window for editing. Clicking on the button, however, won't open a window just yet.
To actually present a new window, we'll need to add another WindowGroup
scene to the app's body
property. The new scene will indicate that it expects to receive a value of type Note.ID
. We'll also make sure, that the main scene and the detail scene get passed the shared data store as an environment object.
@main
struct OpenWindowApp: App {
@StateObject private var dataStore = DataStore()
var body: some Scene {
WindowGroup {
ContentView()
.environmentObject(dataStore)
}
WindowGroup("Note", for: Note.ID.self) { $noteId in
NoteView(noteId: noteId)
.environmentObject(dataStore)
}
}
}
With this setup our users can now click on the action in the context menu and open a note in a new window.
If the user has already opened a new window for the same note, SwiftUI will reuse the existing window and bring it to the front instead.
The value that we pass to openWindow
action should conform to Hashable
and Codable
. Hashable
conformance lets SwiftUI match the presented value to the right scene. Codable
conformance allows SwiftUI to implement state restoration for the open windows on our behalf. When the user reopens the app, all the windows that were open before closing the app will be restored.
You can get the sample project for this post from our GitHub repository. You can play around with it and test opening multiple windows, opening a new window for the note that already has a separate window, and check state restoration of the windows. I didn't implement data persistence in the sample, so if you test state restoration, windows will be restored but the note text won't be there.
If you have a document-based app, you can also look into the new APIs to programmatically open documents such as newDocument and openDocument actions in the environment.
If you're an experienced Swift developer looking to learn advanced techniques, check out my latest book Swift Gems. It’s packed with tips and tricks focused solely on the Swift language and Swift Standard Library. From optimizing collections and handling strings to mastering asynchronous programming and debugging, "Swift Gems" provides practical advice that will elevate your Swift development skills to the next level. Grab your copy and let's explore these advanced techniques together.