I've got a Mac Document App using SwiftUI and SwiftData.
All is working well with the models editing, etc.
There's a feature I need to implement, and can't seem to make it work.
From the main window of the app, I need to be able to launch an auxilliary window containing a view-only representation of the model being edited. The required workflow is something like this:
- Open a document (SwiftData)
- Select a sub-model of the document
- Launch the aux window to display the view of the model data (must be in a separate window, because it will be on a different physical display)
- Continue making edits to the sub-model, as they are reflected in the other window
So, below is the closest I've been able to come, and it's still not working at all. What happens with this code:
Click on the "Present" button, the encounter-presentation
Window opens, but never loads the data model or the view. It's just an empty window.
This is the spot in the main view where the auxiliary window will be launched:
@State
var presenting: Presentation? = nil
var presentingThisEncounter: Bool {
presenting?.encounter.id == encounter.id
}
@Environment(\.openWindow) var openWindow
...
if presentingThisEncounter {
Button(action: { presenting = nil }) {
Label("Stop", systemImage: "stop.fill")
.padding(.horizontal, 4)
}
.preference(key: PresentationPreferenceKey.self, value: presenting)
} else {
Button(action: {
presenting = Presentation(encounter: encounter, display: activeDisplay)
openWindow(id: "encounter-presentation")
}) {
Label("Present", systemImage: "play.fill")
.padding(.horizontal, 4)
}
.preference(key: PresentationPreferenceKey.self, value: nil)
}
Presentation
is declared as:
class Presentation: Observable, Equatable {
Here's the contents of the App, where the DocumentGroup & model is instantiated, and the aux window is managed:
@State
var presentation: Presentation?
var body: some Scene {
DocumentGroup(editing: .encounterList, migrationPlan: EncounterListMigrationPlan.self) {
ContentView()
.onPreferenceChange(PresentationPreferenceKey.self) { self.presentation = $0 }
}
Window("Presentation", id: "encounter-presentation") {
VStack {
if let presentation = presentation {
PresentingView(presentation: presentation)
}
}
}
}
And the definition of PresentationPreferenceKey
:
struct PresentationPreferenceKey: PreferenceKey {
static var defaultValue: Presentation?
static func reduce(value: inout Presentation?, nextValue: () -> Presentation?) {
value = nextValue()
}
}
Hello @karim. Consider using a WindowGroup
scene instead of a Window
scene, specifically the one that lets you define a particular type of data to be displayed.
Check out the example under the Present data in a window section of https://developer.apple.com/documentation/swiftui/windowgroup#Present-data-in-a-window
Once you've defined a WindowGroup
for a specific type, you can open it using openWindow
from the environment, passing in a value.
The types represented by WindowGroup
s should be small, as they may be stored so the windows may be restored when your app is quit and relaunched.
An ID
type of an Identifiable
type is a good choice and typically used instead of the model type, as seen in the example. Then in your WindowGroup
's view you'd query for the rest of the model using that identifier.
The WindowGroup
gives you a Binding
, so you can change the value that the window represents at runtime. (Imagine you have buttons to navigate to displaying another model, but within the same window.)