.onChange with ScenePhase not working

Hi, I created a new project with Xcode 12 beta on Big Sur beta. I changed the code in the @main-wrapper to this:

Code Block
@Environment(\.scenePhase) private var scenePhase
var body: some Scene {
WindowGroup {
ContentView()
}
.onChange(of: scenePhase) { (newScenePhase) in
print(newScenePhase)
}
}


If I start the application (either macOS or iOS), nothing happens, nothing will be printed in the onChange. Do I miss something?

Replies

I had the same problem. Couldn't get this to work in the Simulator or on an iPhone running the iOS 14 beta. The ScenePhase doc's sample code didn't work for me when adding it to an App or Scene.

Finally worked by adding it into the View instead (the first example in the ScenePhase doc). Seems more limited, but works.


Code Block Swift
struct ContentView: View {
    @Environment(\.scenePhase) var scenePhase
    var body: some View {
        Text("Hello, world!").padding()
            .onChange(of: scenePhase) { phase in
                print("scenePhase change \(phase)")
            }
    }
}


Thanks for the workaround, mjcotr!
Has anybody filed a bug in radar?
I will file this bug today.
I had the same issue and filed a bug: FB7923639
I started trying to implement this today and found the same problem. One thing to note is that on beta 3 I see differences between behavior on an iPod Touch (no multiple window support), and iPad or the simulator. The App and Scene versions just never fire under any conditions I can find. I don't see the initial launch of an app on an iPad or on any simulator, but I will see subsequent background/foreground events. (On the iPad when I create a second scene I do receive a .active phase change.) On an actual iPod Touch (7th generation) device I see the View .active case on every launch.
In Xcode 12 beta 4, running my app on the iPhone simulator, I don't get an .active change for initial launch in either the App or the ContentView.
I'm having the same problem with phone simulator in Xcode 12 Beta 4.
It looks as though a separate function, onAppear, should be used for the initial activation.

This issue persists on watchOS 8 Beta. It's not just that the onChange is not called. It is also not updating the variable at all (in some cases). As this seems to be unreliable I went for this solution, which is not recommended, but 100% reliable:

1. Create an ExtensionDelegate / AppDelegate

It just serves to send notifications when the state of the app changes. For watchOS this would be:

class ExtensionDelegate: NSObject, WKExtensionDelegate {

    func applicationDidBecomeActive() {

        NotificationCenter.default.post(name: Notification.Name.App.scenePhaseChanged, object: nil, userInfo: ["scenePhase": ScenePhase.active])

    }

    func applicationWillResignActive() {

        NotificationCenter.default.post(name: Notification.Name.App.scenePhaseChanged, object: nil, userInfo: ["scenePhase": ScenePhase.inactive])

    }

    

    func applicationDidEnterBackground() {

        NotificationCenter.default.post(name: Notification.Name.App.scenePhaseChanged, object: nil, userInfo: ["scenePhase": ScenePhase.background])

    }

}

2. Create a custom ScenePhase environment key

This serves as a replacement for the actual key, which is not updated reliably.

extension EnvironmentValues {

    var customScenePhase: ScenePhase {

        get {self[CustomScenePhaseKey.self]}

        set {self[CustomScenePhaseKey.self] = newValue}

    }

}



private struct CustomScenePhaseKey: EnvironmentKey {

    static let defaultValue = ScenePhase.inactive

}

3. Process updating notifications with the new scenePhase

var appStateChangeNotification = NotificationCenter.default.publisher(for: Notification.Name.App.scenePhaseChanged)

//....
.onReceive(self.appStateChangeNotification, perform: { notification in

                    let newScenePhase: ScenePhase = notification.userInfo?["scenePhase"] as? ScenePhase ?? .active

                    guard newScenePhase != self.scenePhase else {return}

                    self.scenePhase = newScenePhase

                })

4. Forward the new scenePhase

ContentView()
   .environment(\.customScenePhase, self.scenePhase)

I hope this sample code helps anyone with similar issues