When you target and environment that support opening multiple windows, it’s great opportunity to enable opening new window for multitasking purpose. So, recently when developing Spatial Counter for visionOS, I want to show multiple window of counter.
This will be useful for user when they want to count multiple things at once. Also a great opportunity for me to learn how multiple window works outside iOS platform which I usually develop an app.
Open window using its identifier
In typical SwiftUI application, we will have this code as entry point to our application.
import SwiftUI
@main
struct Spatial_CounterApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
On a platform where multiple window is allowed, we can create multiple WindowGroup inside the scene body.
For example if we want a main window, we can give an identifier to our window group
import SwiftUI
@main
struct Spatial_CounterApp: App {
var body: some Scene {
WindowGroup(id: "main-view") {
ContentView()
}
WindowGroup(id: "settings-view") {
SettingsView()
}
}
}
Then in other view, we can use @Environment(\.openWindow)
to open specific window
import SwiftUI
@main
struct SomeOtherView: View {
@Environment(\.openWindow) var openWindow
var body: some View {
Button("Open Settings") {
self.openWindow(id: "settings-view") // open SettingsView() as new window
}
}
}
Open window using custom value
We can also open a new window by passing binding of a value. This value should be conform to Hashable and Decodable. This can be useful for case where you want show more detailed information in a new window. You need to pass the value into openWindow(value:)
environment
struct Contact: Identifiable {
var name: String
var phoneNumber: String
var address: String
var profilePictureURL: URL?
}
extension Contact: Codable, Hashable {}
struct ContactDetail: View {
@Environment(\.openWindow) var openWindow
@Bindable var contact: Contact
var body: some View {
// show contact cell
Menu {
Button {
self.openWindow(value: self.contact)
} label: {
Label("Open in New Window", systemImage: "rectangle.fill.on.rectangle.fill")
}
} label: {
Text("Options")
}
}
}
Then in your app entrypoint, create a WindowGroup that accept the value
import SwiftUI
@main
struct Spatial_CounterApp: App {
var body: some Scene {
WindowGroup(id: "main-view") {
ContentView()
}
WindowGroup(id: "settings-view") {
SettingsView()
}
WindowGroup(for: Contact.self) { contact in
ContactWindowView(contact: contact)
}
}
}
References: