App Window Closure Sequence Impacts Main Interface Reload Behavior

My VisionOS App (Travel Immersive) has two interface windows: a main 2D interface window and a 3D Earth window. If the user first closes the main interface window and then the Earth window, clicking the app icon again will only launch the Earth window while failing to display the main interface window. However, if the user closes the Earth window first and then the main interface window, the app restarts normally‌.

Below is the code of

import SwiftUI

@main struct Travel_ImmersiveApp: App { @StateObject private var appModel = AppModel()

var body: some Scene {
    WindowGroup(id: "MainWindow") { 
        ContentView()
            .environmentObject(appModel)
            .onDisappear {
                appModel.closeEarthWindow = true
            }
    }
    .windowStyle(.automatic)
    .defaultSize(width: 1280, height: 825)
    
    
    WindowGroup(id: "Earth") {
        if !appModel.closeEarthWindow {
            Globe3DView()
                .environmentObject(appModel)
                .onDisappear {
                    appModel.isGlobeWindowOpen = false
                }
        } else {
            EmptyView() // 关闭时渲染空视图
        }
    }
    .windowStyle(.volumetric)
    .defaultSize(width: 0.8, height: 0.8, depth: 0.8, in: .meters)
    
    
    
    ImmersiveSpace(id: "ImmersiveView") {
        ImmersiveView()
            .environmentObject(appModel)
    }

    
}

}

I had a similar issue in my app (Project Graveyard) which has a main volume and utility window to edit content.

I solved this by using some shared state (AppModel) and ScenePhase. What I ended up with was the ability to reopen the main window from the utility window OR open the utility window from the main window.

The first thing to keep in mind is that ScenePhase works differently when used at the app level (some Scene) vs. when using it I a view inside a window, volume, or space. visionOS has a lot of bugs (reported) about the app level uses. I was able to create by solution by using ScenePhase in my views and sharing some state in the AppModel.

Here is a breakdown

Add to AppModel

var mainWindowOpen: Bool = true
var yellowFlowerOpen: Bool = false

The root view of the main window (ContentView in this case)

@Environment(\.scenePhase) private var scenePhase

Then listen for scenePhase using onChange, writing to to the mainWindowOpen bool from appModel.

.onChange(of: scenePhase, initial: true) {
            switch scenePhase {
            case .inactive, .background:
                appModel.mainWindowOpen = false
            case .active:
                appModel.mainWindowOpen = true
            @unknown default:
                appModel.mainWindowOpen = false
            }
        }

We do the same thing in the root view for the other window (or volume)

@Environment(\.scenePhase) private var scenePhase

Then listen to scene phase

.onChange(of: scenePhase, initial: true) {
            switch scenePhase {
            case .inactive, .background:
                appModel.yellowFlowerOpen = false
            case .active:
                appModel.yellowFlowerOpen = true
            @unknown default:
                appModel.yellowFlowerOpen = false
            }
        }

You can download this as an Xcode project if you want to try it our before trying to reproduce it.

https://github.com/radicalappdev/Step-Into-Example-Projects/tree/main/Garden06

There is a also a video available on my website (I really wish we could upload short videos here)

https://stepinto.vision/example-code/how-to-use-scene-phase-to-track-and-manage-window-state/

App Window Closure Sequence Impacts Main Interface Reload Behavior
 
 
Q