A SwiftUI package for managing windows and immersive spaces in visionOS applications. ScenesManager provides centralized control over scene lifecycle, visibility tracking, and event handling.
Created by Tom Krikorian and Tina Debove Nigro
Inspired by VisibilityTrackerModifier from Florent Morin
- Track window and immersive space visibility
- Manage scene lifecycle events
- Suppress window opening and dismissal events
- Simple SwiftUI modifiers for scene tracking
- Immersive space state management
Add ScenesManager to your project using Swift Package Manager:
dependencies: [
.package(url: "https://github.com/Tab-To-Tap/ScenesManager.git", from: "1.0.0")
]- Set up the ScenesManager in your main app scene:
@main
struct MyApp: App {
@Environment(\.openWindow) var openWindow
@Environment(\.dismissWindow) var dismissWindow
@Environment(\.openImmersiveSpace) var openImmersiveSpace
@Environment(\.dismissImmersiveSpace) var dismissImmersiveSpace
@State private var scenesManager = ScenesManager()
var body: some Scene {
WindowGroup(id: SceneId.mainWindow.rawValue) {
ContentView()
.environment(\.scenesManager, scenesManager)
.onAppear {
scenesManager.setActions(
openWindow: openWindow,
dismissWindow: dismissWindow,
openImmersiveSpace: openImmersiveSpace,
dismissImmersiveSpace: dismissImmersiveSpace
)
}
}
}
}- Track window lifecycle using the scene tracker modifier:
struct MyView: View {
var body: some View {
ContentView()
.sceneTracker(for: .mainWindow,
onOpen: { print("Window opened") },
onDismiss: { print("Window dismissed") }
)
}
}- Track immersive spaces:
struct ImmersiveView: View {
var body: some View {
RealityView { content in
// Your RealityKit content
}
.immersiveSpaceTracker()
}
}Before using ScenesManager, you need to define your scene identifiers. Create an extension of SceneId in your app:
import ScenesManager
public extension SceneId {
static var mainWindow: Self { .init(rawValue: "MainWindow") }
static var settings: Self { .init(rawValue: "Settings") }
static var immersiveMenu: Self { .init(rawValue: "ImmersiveMenu") }
}The package already includes the immersiveSpace identifier that's used internally:
public extension SceneId {
/// The identifier for the immersive space scene
static var immersiveSpace: Self { .init(rawValue: "ImmersiveSpace") }
}Once defined, you can use these identifiers throughout your app:
// In your WindowGroup declarations
WindowGroup(id: SceneId.mainWindow.rawValue) {
ContentView()
}
// When opening windows
scenesManager.openWindow(.settings)
// When tracking scenes
.sceneTracker(for: .mainWindow)
// For immersive spaces
ImmersiveSpace(id: SceneId.immersiveSpace.rawValue) {
ImmersiveView()
}A full project is available at ScenesManager-Example
@Environment(\.scenesManager) var scenesManager
// Open a window
scenesManager.openWindow(.settings)
// Dismiss a window
scenesManager.dismissWindow(.settings)// Suppress both open and dismiss events
scenesManager.suppressEvents(.all, for: .settings)
// Suppress only dismiss events
scenesManager.suppressEvents(.dismissEvent, for: .settings)
// Remove suppression
scenesManager.unsuppressEvents(for: .settings)
// Check if events are suppressed
if scenesManager.areEventsSuppressed(.dismissEvent, for: .settings) {
print("Cannot dismiss settings window")
}// Toggle immersive space
await scenesManager.toggleImmersiveSpace()
// Check immersive space state
switch scenesManager.immersiveSpaceState {
case .open:
print("Immersive space is visible")
case .closed:
print("Immersive space is closed")
case .inTransition:
print("Immersive space is transitioning")
}Scenes can be in one of four states:
.opening: Scene is in the process of opening.opened: Scene is fully open and active.closing: Scene is in the process of closing.closed: Scene is fully closed
// Check scene state
let state = scenesManager.getSceneState(for: .settings)
if state == .opened {
print("Settings scene is fully open")
}- visionOS 2.0+
- Swift 5.9+