Programmatically open a new window in SwiftUI on macOS

0
21

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, will not 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.

Screenshot of the main app window and a new window on top of it that has the text of a note

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 did not implement data persistence in the sample, so if you test state restoration, windows will be restored but the note text will not 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 are enjoying our blog and would like to support us, you can now sponsor us on GitHub.

For updates about the blog and development tips, follow us on Twitter @nilcoalescing.

Source

LEAVE A REPLY

Please enter your comment!
Please enter your name here