Appdelegate and Scenedelegate When Supporting iOS 12 and 13

AppDelegate and SceneDelegate when supporting iOS 12 and 13

You do need to duplicate the code but you need to make sure it runs only on the correct system. In iOS 13 you don’t want that application delegate didFinishLaunching body code to run, so use an availability check to prevent it.
In the same way, use availability to hide the window scene stuff from iOS 12.

Here's the basic sketch of a solution that runs correctly on both iOS 12 and iOS 13:

AppDelegate.Swift

import UIKit
@UIApplicationMain
class AppDelegate : UIResponder, UIApplicationDelegate {
var window : UIWindow?
func application(_ application: UIApplication,
didFinishLaunchingWithOptions
launchOptions: [UIApplication.LaunchOptionsKey : Any]?)
-> Bool {
if #available(iOS 13, *) {
// do only pure app launch stuff, not interface stuff
} else {
self.window = UIWindow()
let vc = ViewController()
self.window!.rootViewController = vc
self.window!.makeKeyAndVisible()
self.window!.backgroundColor = .red
}
return true
}
}

SceneDelegate.swift

import UIKit
@available(iOS 13.0, *)
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
var window : UIWindow?
func scene(_ scene: UIScene,
willConnectTo session: UISceneSession,
options connectionOptions: UIScene.ConnectionOptions) {
if let windowScene = scene as? UIWindowScene {
self.window = UIWindow(windowScene: windowScene)
let vc = ViewController()
self.window!.rootViewController = vc
self.window!.makeKeyAndVisible()
self.window!.backgroundColor = .red
}
}
}

ViewController.swift

import UIKit
class ViewController : UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
print("view did load")
self.view.backgroundColor = .green
}
}

Note that dealing with other duplicates, such as the application activating, is much simpler because if you support window scenes the application delegate method won't be called on iOS 12. So the problem is confined to this one situation, namely where you have window / root view controller manipulations to perform at launch (e.g. no storyboard).

Xcode 11 backward compatibility: UIWindowScene is only available in iOS 13 or newer

The template in Xcode 11 uses a scene delegate. Scene delegates and the related classes are new in iOS 13; they don't exist in iOS 12 and before, and the launch process is different.

To make a project generated from an Xcode 11 app template backward compatible, you need to mark the entire SceneDelegate class, and any methods in the AppDelegate class that refer to UISceneSession, as @available(iOS 13.0, *).

You also need to declare a window property in the AppDelegate class (if you don't do that, the app will run and launch but the screen will be black):

var window : UIWindow?

The result is that when this app runs in iOS 13, the scene delegate has the window, but when it runs in iOS 12 or before, the app delegate has the window — and your other code may then need to take account of that in order to be backward compatible.

SceneDelegate in iOS 13, Is it necessary?

its not necessary.Since you
Read this beautiful article

UI state restoration for a scene in iOS 13 while still supporting iOS 12. No storyboards

To support state restoration in iOS 13 you will need to encode enough state into the NSUserActivity:

Use this method to return an NSUserActivity object with information about your scene's data. Save enough information to be able to retrieve that data again after UIKit disconnects and then reconnects the scene. User activity objects are meant for recording what the user was doing, so you don't need to save the state of your scene's UI

The advantage of this approach is that it can make it easier to support handoff, since you are creating the code necessary to persist and restore state via user activities.

Unlike the previous state restoration approach where iOS recreated the view controller hierarchy for you, you are responsible for creating the view hierarchy for your scene in the scene delegate.

If you have multiple active scenes then your delegate will be called multiple times to save the state and multiple times to restore state; Nothing special is needed.

The changes I made to your code are:

AppDelegate.swift

Disable "legacy" state restoration on iOS 13 & later:

func application(_ application: UIApplication, viewControllerWithRestorationIdentifierPath identifierComponents: [String], coder: NSCoder) -> UIViewController? {
if #available(iOS 13, *) {

} else {
print("AppDelegate viewControllerWithRestorationIdentifierPath")

// If this is for the nav controller, restore it and set it as the window's root
if identifierComponents.first == "RootNC" {
let nc = UINavigationController()
nc.restorationIdentifier = "RootNC"
self.window?.rootViewController = nc

return nc
}
}
return nil
}

func application(_ application: UIApplication, willEncodeRestorableStateWith coder: NSCoder) {
print("AppDelegate willEncodeRestorableStateWith")
if #available(iOS 13, *) {

} else {
// Trigger saving of the root view controller
coder.encode(self.window?.rootViewController, forKey: "root")
}
}

func application(_ application: UIApplication, didDecodeRestorableStateWith coder: NSCoder) {
print("AppDelegate didDecodeRestorableStateWith")
}

func application(_ application: UIApplication, shouldSaveApplicationState coder: NSCoder) -> Bool {
print("AppDelegate shouldSaveApplicationState")
if #available(iOS 13, *) {
return false
} else {
return true
}
}

func application(_ application: UIApplication, shouldRestoreApplicationState coder: NSCoder) -> Bool {
print("AppDelegate shouldRestoreApplicationState")
if #available(iOS 13, *) {
return false
} else {
return true
}
}

SceneDelegate.swift

Create a user activity when required and use it to recreate the view controller. Note that you are responsible for creating the view hierarchy in both normal and restore cases.

func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
print("SceneDelegate willConnectTo")

guard let winScene = (scene as? UIWindowScene) else { return }

// Got some of this from WWDC2109 video 258
window = UIWindow(windowScene: winScene)

let vc = ViewController()

if let activity = connectionOptions.userActivities.first ?? session.stateRestorationActivity {
vc.continueFrom(activity: activity)
}

let nc = UINavigationController(rootViewController: vc)
nc.restorationIdentifier = "RootNC"

self.window?.rootViewController = nc
window?.makeKeyAndVisible()

}

func stateRestorationActivity(for scene: UIScene) -> NSUserActivity? {
print("SceneDelegate stateRestorationActivity")

if let nc = self.window?.rootViewController as? UINavigationController, let vc = nc.viewControllers.first as? ViewController {
return vc.continuationActivity
} else {
return nil
}

}

ViewController.swift

Add support for saving and loading from an NSUserActivity.

var continuationActivity: NSUserActivity {
let activity = NSUserActivity(activityType: "restoration")
activity.persistentIdentifier = UUID().uuidString
activity.addUserInfoEntries(from: ["Count":self.count])
return activity
}

func continueFrom(activity: NSUserActivity) {
let count = activity.userInfo?["Count"] as? Int ?? 0
self.count = count
}

Opt out of UISceneDelegate/SwiftUI on iOS

While you should embrace using scenes when your app is run under iOS 13 and later, you can fully opt out while you still support iOS 12 or earlier.

  • Completely remove the “Application Scene Manifest” entry from Info.plist.

  • If there is a scene delegate class, remove it.

  • If there are any scene related methods in your app delegate, remove those methods.

  • If missing, add the property var window: UIWindow? to your app delegate.

Your app should now only use the app delegate and under iOS 13 it should have the same life cycle as iOS 12.

Note: None of this is specific to Swift or SwiftUI.

Black screen after adding SceneDelegate and updating Info.plist

You have several issues here. It's important to read the documentation related to the app lifecycle which states what is called under iOS 13 and what is called under iOS 12.

You may also want to see my Single View App template that supports iOS 12 and 13.

Looking at your code, here is a summary of the problems:

AppDelegate:

  • You should only setup the main window and the root view controller if the app is being run under iOS 12 or earlier. You need to check this at runtime.
  • The func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) method should not be in the app delegate.
  • Not directly related but never sleep on app startup. Remove the Thread.sleep(forTimeInterval: 3.0) line. Users want to use your app, not stare at the launch screen longer than necessary. And blocking the main thread on app launch can cause your app to be killed.

SceneDelegate:

  • This is mostly fine but there is no reason for the guard let _ = (scene as? UIWindowScene) else { return } line, especially since it is inside an if let that already does that check.
  • You don't appear to be using SwiftUI so remove that import.

I would update your app delegate to be more like this:

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?

func application(_ application: UIApplication, willFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil) -> Bool {
UINavigationBar.appearance().barTintColor = UIColor(red:0.08, green:0.23, blue:0.62, alpha:1.0)

if #available(iOS 13.0, *) {
// In iOS 13 setup is done in SceneDelegate
} else {
let window = UIWindow(frame: UIScreen.main.bounds)
self.window = window

if (user != nil && userSelfIdent != nil){
let mainstoryboard:UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
let newViewcontroller:UIViewController = mainstoryboard.instantiateViewController(withIdentifier: "swrevealviewcontroller") as! SWRevealViewController
window.rootViewController = newViewcontroller
}
}

return true
}

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
if #available(iOS 13.0, *) {
// In iOS 13 setup is done in SceneDelegate
} else {
self.window?.makeKeyAndVisible()
}

return true
}

func applicationWillResignActive(_ application: UIApplication) {
// Not called under iOS 13 - See SceneDelegate sceneWillResignActive
}

func applicationDidEnterBackground(_ application: UIApplication) {
// Not called under iOS 13 - See SceneDelegate sceneDidEnterBackground
}

func applicationWillEnterForeground(_ application: UIApplication) {
// Not called under iOS 13 - See SceneDelegate sceneWillEnterForeground
}

func applicationDidBecomeActive(_ application: UIApplication) {
// Not called under iOS 13 - See SceneDelegate sceneDidBecomeActive
}

// MARK: UISceneSession Lifecycle

@available(iOS 13.0, *)
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)
}

@available(iOS 13.0, *)
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.
}
}

Your scene delegate could be like:

@available(iOS 13.0, *)
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
var window: UIWindow?

func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
guard let windowScene = (scene as? UIWindowScene) else { return }

let window = UIWindow(windowScene: windowScene)
self.window = window

if (user != nil && userSelfIdent != nil){
let mainstoryboard:UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
let newViewcontroller:UIViewController = mainstoryboard.instantiateViewController(withIdentifier: "swrevealviewcontroller") as! SWRevealViewController
window.rootViewController = newViewcontroller
window.makeKeyAndVisible()
}
}

func sceneDidDisconnect(_ scene: UIScene) {
// Called as the scene is being released by the system.
}

func sceneDidBecomeActive(_ scene: UIScene) {
// Not called under iOS 12 - See AppDelegate applicationDidBecomeActive
}

func sceneWillResignActive(_ scene: UIScene) {
// Not called under iOS 12 - See AppDelegate applicationWillResignActive
}

func sceneWillEnterForeground(_ scene: UIScene) {
// Not called under iOS 12 - See AppDelegate applicationWillEnterForeground
}

func sceneDidEnterBackground(_ scene: UIScene) {
// Not called under iOS 12 - See AppDelegate applicationDidEnterBackground
}
}


Related Topics



Leave a reply



Submit