Not Receiving scenePhase Changes

I acknowledge this question is specifically about schenePhase changes, however, on macOS I am not able to receive any .background notifications when a user switches to a different app. The older NotificationCenter strategy works as I expected, on both platforms. I'll add this to the mix for anyone who is just trying to execute some code, onForeground / onBackground on iOS and macOS.

On any view, you can attach:

.onReceive(NotificationCenter.default.publisher(for: .willResignActiveNotification)) { _ in
    doBackgroundThing()
}

The events you may care about are:

  • iOS: willResignActiveNotification & willEnterForegroundNotification
  • macOS: willResignActiveNotification & willBecomeActiveNotification

You can find all NotificationCenter Names here.

I use will* variants for background because I assume they'll be called early in the process, and I use did* variants for foreground, because they are called regardless of whether the app is launched for the first time, or it's coming out of background.

I use this extension so I don't have to think about the platform differences:

extension View {
    #if os(iOS)
    func onBackground(_ f: @escaping () -> Void) -> some View {
        self.onReceive(
            NotificationCenter.default.publisher(for: UIApplication.willResignActiveNotification),
            perform: { _ in f() }
        )
    }
    
    func onForeground(_ f: @escaping () -> Void) -> some View {
        self.onReceive(
            NotificationCenter.default.publisher(for: UIApplication.didBecomeActiveNotification),
            perform: { _ in f() }
        )
    }
    #else
    func onBackground(_ f: @escaping () -> Void) -> some View {
        self.onReceive(
            NotificationCenter.default.publisher(for: NSApplication.willResignActiveNotification),
            perform: { _ in f() }
        )
    }
    
    func onForeground(_ f: @escaping () -> Void) -> some View {
        self.onReceive(
            NotificationCenter.default.publisher(for: NSApplication.didBecomeActiveNotification),
            perform: { _ in f() }
        )
    }
    #endif
}

As expected, I use it as such:

AppView()
   .onBackground {
       print("my background")
   }
   .onForeground {
       print("my foreground")
   }

Use inside scene root view (usually ContentView)

Tested with Xcode 12 / iOS 14 as worked.

struct ContentView: View {
    @Environment(\.scenePhase) private var scenePhase
    var body: some View {
        TestView()
            .onChange(of: scenePhase) { phase in
                switch phase {
                    case .active:
                        print(">> your code is here on scene become active")
                    case .inactive:
                        print(">> your code is here on scene become inactive")
                    case .background:
                        print(">> your code is here on scene go background")
                    default:
                        print(">> do something else in future")
                }
            }
    }
}

Tags:

Swiftui