0

I'm writing a cross-platform SwiftUI-based app that lets users create different types of "screens". It's a word puzzle helper. You can create screens to help solve Wordle puzzles, Anagrams, or crossword puzzle like puzzles.

Right now it uses a TabView on each window, and users can switch between tabs. However, that causes some problems, and I've concluded that for the macOS version of the app I instead want to let the user create new Wordle, Anagram, or Crossword windows.

Adding a WindowGroup to my app lets me create windows of that type from the File menu's "New Window" command.

The Window(_:, id:) struct defines a single instance of a window. If you use the openWindow(id:) action, it opens a "singleton" of that window. Users can't open multiple instances of a Window() with a specific ID.

How would I go about adding "New Wordle Window", "New Anagrams Window" and "New Crosswords Window" menu items to the file menu, so that each time the user selects that menu item it creates a new instance of that type of Window?

(For the iOS version of my app I may keep the tabbed approach where users can switch between single instances of a Wordle, Anagram, and Crosswords screen via a TabView.)

1 Answer 1

2

You can add menu items to the main menu using commands. This takes a CommandsBuilder closure that builds a Commands.

Using a CommandGroup, you can replace the "New Window" menu item added by WindowGroups by default.

struct OpenWindowCommands: Commands {
    @Environment(\.openWindow) var open
    var body: some Commands {
        CommandGroup(replacing: .newItem) { // .newItem is where the New Window/Open menu items are placed
            Button("New Anagrams Window") {
                open(id: "Anagrams")
            }
            Button("New Wordle Window") {
                open(id: "Wordle")
            }
            Button("New Crosswords Window") {
                open(id: "Crosswords")
            }
        }
    }
}

Then you can have 3 WindowGroups:

WindowGroup(id: "Anagrams") {
    Text("Anagrams Window")
}.commands { OpenWindowCommands() }

WindowGroup(id: "Wordle") {
    Text("Wordle")
}
.commands { OpenWindowCommands() }

WindowGroup(id: "Crosswords") {
    Text("Crosswords")
}
.commands { OpenWindowCommands() }

Output:

enter image description here


If anagrams, wordles, and crosswords are represented by custom structs you wrote, you can use the [init(for:content:)]3 initialiser instead.

/*
Assuming you have

struct Anagram: Codable, Hashable {
    ...
}
*/

WindowGroup(for: Anagram.self) { $anagram in
    // ...
}

// the "open" calls should be changed to use the value: parameter
open(value: Anagram(...))

If the anagrams, wordles and crosswords are files (documents) that your app supports opening and writing, consider using a DocumentGroup instead.

Sign up to request clarification or add additional context in comments.

3 Comments

Cool. That's a step in the right direction. It introduces a new problem however. Each of my types of windows has an ObservableObject to hold state data and provide functions that mutate that state. With the default single WindowGroup and 3 tabs, each window gets a unique ObservableObject as desired. When I create a separate WindowGroup for each type of window, each type ends up sharing a single ObservableObject so they no longer have unique state. Why is that, and how do I fix it?
@DuncanC You would need to show a minimal reproducible example, probably in a new question. How are you creating the ObservableObjects? If you are just doing .environmentObject(SomeObservableObject()), then everything will share the same observable object. If you use @StateObject in the views of each window, I don't think that should happen.
I was declaring my object as an @ObservedObject. Simply switching to @StateObject seems to fix it. Thanks for that!

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.