Thanks to apple support for helping me out with this one.
The correct answer is in the WWDC22 The SwiftUI cookbook for navigation video, right at the end.
For completeness, the following fixes the example given in the original question:
import SwiftUI
import Combine
@main
struct SceneStorageTest: App {
var body: some Scene {
WindowGroup("Port Statistics", id: "statsView") {
ContentView()
}
}
}
class StreamStats: ObservableObject, Encodable {
@Published public var port: Int = 60000
public var jsonData: Data? {
get {
try? JSONEncoder().encode(self)
}
set {
guard let data = newValue,
let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any]
else { return }
port = json["port"] as! Int
}
}
enum CodingKeys: String, CodingKey {
case port
}
init() {
startListening()
}
public func startListening() {
print("Gathering stats for port \(port)")
}
var objectWillChangeSequence: AsyncPublisher<Publishers.Buffer<ObservableObjectPublisher>> {
objectWillChange
.buffer(size: 1, prefetch: .byRequest, whenFull: .dropOldest)
.values
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(port, forKey: .port)
}
}
struct ContentView: View {
@StateObject var streamStats = StreamStats()
@SceneStorage("streamStatsData") var streamStatsData: Data?
@Environment(\.openWindow) private var openWindow
var body: some View {
VStack {
TextField("port", value: $streamStats.port, format: .number)
Button("Open") {
streamStats.startListening()
}
Divider()
Button("New Port Statistics Window") {
openWindow(id: "statsView")
}
}
.padding()
.task {
if let data = streamStatsData {
print("Loading data.")
streamStats.jsonData = data
}
for await _ in streamStats.objectWillChangeSequence {
streamStatsData = streamStats.jsonData
}
}
}
}