Question about ScenePhase implementation in SwiftUI with SceneDelegate

Hello,

I hope this message finds you well. I am currently working on a SwiftUI application that utilizes SceneDelegate in a project targeting iOS 17.

I am looking to incorporate the functionality of ScenePhase in a project where SceneDelegate is being used. However, I am unsure whether ScenePhase is supported in this context, and if it is, I am having difficulty finding information on its implementation.

Specifically, I would like to use ScenePhase within the onChange of a View to branch my processing based on changes in the state. However, it seems that the changes are not being detected.

In simpler projects where SceneDelegate is not utilized, I have been able to detect state changes successfully.

Could you please guide me on whether ScenePhase is available within SceneDelegate and, if so, provide specific implementation methods or best practices?

Any assistance you can provide would be greatly appreciated.

Thank you for your cooperation.

Hi @Ryo_Ohta ,

I've attached code for a sample test project that has a SceneDelegate and ScenePhase. Try comparing this project to yours and see what may differ and is causing your issue. When you run the project and go home/open the app, you'll see print statements from both the SceneDelegate and scenePhase in the console.

Test.swift

@main
struct TestApp: App {
    @UIApplicationDelegateAdaptor private var appDelegate: AppDelegate
    @Environment(\.scenePhase) private var scenePhase

    var body: some Scene {
        WindowGroup {
            ContentView()
        }
        .onChange(of: scenePhase){ _, phase in
            switch phase {
            case .active:
                print("scenePhase - active")
            case .inactive:
                print("scenePhase - inactive")
            case .background:
                print("scenePhase - background")
            @unknown default:
                fatalError()
            }
        }
        
        
    }
}

SceneDelegate.swift

import UIKit

class SceneDelegate: UIResponder, UIWindowSceneDelegate {
    var window: UIWindow?

    func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
        // Use this method to optionally configure and attach the UlWindow 'window' to the provided UIWindowScene 'scene'.
        // If using a storyboard, the 'window' property will automatically be initialized and attached to the scene.
        guard let _ = (scene as? UIWindowScene) else { return }
    }
    
    func sceneDidBecomeActive(_ scene: UIScene) {
        // Called when the scene has moved from an inactive state to an active state.
        print("SceneDelegate - active")
    }
    func sceneDidEnterBackground(_ scene: UIScene) {
        print("SceneDelegate - background")
    }
    
    func sceneDidDisconnect(_ scene: UIScene) {
        print("SceneDelegate - inactive")
    }
}

AppDelegate.swift

import UIKit

class AppDelegate: NSObject, UIApplicationDelegate {
    func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration {
            
            let sceneConfig = UISceneConfiguration(name: nil, sessionRole: connectingSceneSession.role)
               sceneConfig.delegateClass = SceneDelegate.self
               return sceneConfig
        }
}

I appreciate your help. I am using UIHostingController.

SceneDelegate.swift

import UIKit
import SwiftUI
import AuthenticationServices

var globalPresentationAnchor: ASPresentationAnchor? = nil

class SceneDelegate: UIResponder, UIWindowSceneDelegate {
  @ObservedObject(initialValue: OrientationModel()) var orientationModel: OrientationModel
  var window: UIWindow?

  func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
    // Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`.
    // If using a storyboard, the `window` property will automatically be initialized and attached to the scene.
    // This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead).

    // Get the managed object context from the shared persistent container.
    let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext

    // Create the SwiftUI view and set the context as the value for the managedObjectContext environment keyPath.
    // Add `@Environment(\.managedObjectContext)` in the views that will need the context.
    let contentView = ContentView().environment(\.managedObjectContext, context)

    // Use a UIHostingController as window root view controller.
    if let windowScene = scene as? UIWindowScene {
        let window = UIWindow(windowScene: windowScene)
        window.rootViewController = UIHostingController(rootView: contentView.environmentObject(orientationModel))
        self.window = window
        window.makeKeyAndVisible()
    }
    globalPresentationAnchor = window
  }

  func sceneDidBecomeActive(_ scene: UIScene) {
        // Called when the scene has moved from an inactive state to an active state.
        print("SceneDelegate - active")
    }
    func sceneDidEnterBackground(_ scene: UIScene) {
        print("SceneDelegate - background")
    }
    
    func sceneDidDisconnect(_ scene: UIScene) {
        print("SceneDelegate - inactive")
    }
  
  func windowScene(_ windowScene: UIWindowScene, didUpdate previousCoordinateSpace: UICoordinateSpace, interfaceOrientation previousInterfaceOrientation: UIInterfaceOrientation, traitCollection previousTraitCollection: UITraitCollection) {
      orientationModel.orientation = windowScene.interfaceOrientation
  }
}



final class OrientationModel: ObservableObject {
    @Published var orientation: UIInterfaceOrientation = .unknown
}

AppDelegate.swift

import UIKit
import CoreData

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {



  func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
    // Override point for customization after application launch.
    return true
  }

  // MARK: UISceneSession Lifecycle

  func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration {
    // Called when a new scene session is being created.
    // Use this method to select a configuration to create the new scene with.
    return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role)
  }

  func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set<UISceneSession>) {
    // Called when the user discards a scene session.
    // If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions.
    // Use this method to release any resources that were specific to the discarded scenes, as they will not return.
  }

  // MARK: - Core Data stack

  lazy var persistentContainer: NSPersistentContainer = {
      /*
       The persistent container for the application. This implementation
       creates and returns a container, having loaded the store for the
       application to it. This property is optional since there are legitimate
       error conditions that could cause the creation of the store to fail.
      */
      let container = NSPersistentContainer(name: "FacilityInspector")
      container.loadPersistentStores(completionHandler: { (storeDescription, error) in
          if let error = error as NSError? {
              // Replace this implementation with code to handle the error appropriately.
              // fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
               
              /*
               Typical reasons for an error here include:
               * The parent directory does not exist, cannot be created, or disallows writing.
               * The persistent store is not accessible, due to permissions or data protection when the device is locked.
               * The device is out of space.
               * The store could not be migrated to the current model version.
               Check the error message to determine what the actual problem was.
               */
              fatalError("Unresolved error \(error), \(error.userInfo)")
          }
      })
      return container
  }()
}

ContentView.swift

import SwiftUI

struct ContentView: View {
    @Environment(\.scenePhase) private var scenePhase
  
    var body: some View {
      NavigationView{
        SampleView()
      }.onChange(of: scenePhase){ _, phase in
                    switch phase {
                    case .active:
                        print("scenePhase - active")
                    case .inactive:
                        print("scenePhase - inactive")
                    case .background:
                        print("scenePhase - background")
                    @unknown default:
                        fatalError()
                    }
                }
  }
}

Thank you in advance.

Question about ScenePhase implementation in SwiftUI with SceneDelegate
 
 
Q