Opening multiple WindowGroups(id:for:) with same for:-value does not work

100 Views Asked by At

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 ids but the same for-value. This should be possible according to the docs. In short, my case is the following:

  • I am using SwiftData to store models.
  • I have a nested view hierarchy, where the user starts at Screen1, from which they can open Screen2, and from which they can open Screen3.
  • I want to pass a data model created in Screen1 all the way through Screen2 and Screen3.

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?

1

There are 1 best solutions below

2
On BEST ANSWER

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))
}