I have been trying the seemingly extremely simple task of defining some WindowGroups in a VisionOS app with the signature WindowGroup(id:for:)
, with unique id
s but the same for
-value. This should be possible according to the docs. In short, my case is the following:
Very frustratingly, I simply cannot get this very easy setup to work: Screen1 opens just fine, the user gets to create an instance of the model there, and this model passes to Screen2 using openWindow(id:value:)
; but Screen3 will not even open if I again use openWindow(id:value:)
from Screen2.
The problem is reproduced with the minimal setup below. I am using Xcode 15.2 and am targeting VisionOS 1.0. MultipleScenes is enabled.
MainApp.swift
import SwiftUI
import SwiftData
@main
struct MainApp: App {
private var modelContainer: ModelContainer
init() {
do {
modelContainer = try ModelContainer(for: TurtleModel.self)
} catch {
fatalError("Could not initialize ModelContainer")
}
}
var body: some Scene {
WindowGroup {
ContentView()
.modelContainer(modelContainer)
}
WindowGroup(id: "content-view-2", for: TurtleModel.ID.self) { $turtleModelId in
ContentView2(turtleModelId: turtleModelId)
.modelContainer(modelContainer)
}
WindowGroup(id: "content-view-3", for: TurtleModel.ID.self) { $turtleModelId in
ContentView3(turtleModelId: turtleModelId)
.modelContainer(modelContainer)
}
}
}
TurtleModel.swift; the data model
import SwiftUI
import SwiftData
@Model
class TurtleModel {
@Attribute(.unique) var name: String
init(name: String) {
self.name = name
}
}
extension Array where Element: TurtleModel {
subscript(id: TurtleModel.ID?) -> TurtleModel? {
first { $0.id == id }
}
}
ContentView.swift
import SwiftUI
struct ContentView: View {
@Environment(\.modelContext) private var context
@Environment(\.openWindow) var openWindow
var body: some View {
Text("ContentView")
Button("Open ContentView2") {
let newData = TurtleModel(name: "I am a turtle")
context.insert(newData)
try? context.save()
openWindow(id: "content-view-2", value: newData.id)
}
}
}
ContentView2.swift
import SwiftUI
import SwiftData
struct ContentView2: View {
@Environment(\.openWindow) private var openWindow
@Query private var turtleModels: [TurtleModel]
var turtleModelId: TurtleModel.ID?
var body: some View {
VStack {
Text("ContentView2")
if turtleModelId != nil {
Text("Id: " + (turtleModels[turtleModelId]?.name ?? "none"))
.font(.subheadline)
Button("Open ContentView3") {
openWindow(id: "content-view-3", value: turtleModelId!)
}
}
}
}
}
ContentView3.swift
import SwiftUI
import SwiftData
struct ContentView3: View {
@Environment(\.openWindow) private var openWindow
@Query private var turtleModels: [TurtleModel]
var turtleModelId: TurtleModel.ID?
var body: some View {
VStack {
Text("ContentView3")
if turtleModelId != nil {
Text("Id: " + (turtleModels[turtleModelId]?.name ?? "none"))
.font(.subheadline)
}
}
}
}
Whenever I remove the turtleModelId
attributes from the ContentViews, change the WindowGroup signature to WindowGroup(id:)
, and navigate to them with openWindow(id:)
, ContentView3 opens as expected; but of course, this won't give me access to the created TurtleModel. I have also tried calling openWindow
asynchronously, to no avail.
There are no errors or even warnings in the debug output.
What is going on here? Am I missing something?
I had a similar issue on macOS and it seems that my fix there also works on visionOS.
What you need to do is to make the type used in the for:
argument unique and I solved this by creating a wrapper type to hold the original id.
extension TurtleModel {
struct Window3Value: Codable, Hashable {
let id: TurtleModel.ID
}
}
and then use this type in both the definition and the call for the window in SwiftUI
Declaration:
WindowGroup(id: "content-view-3", for: TurtleModel.Window3Value.self) { $window3Value in
ContentView3(turtleModelId: $window3Value.wrappedValue?.id)
.modelContainer(modelContainer)
}
Call:
Button("Open ContentView3") {
openWindow(id: "content-view-3", value: TurtleModel.Window3Value(id: turtleModelId))
}