Xcode 11 Backward Compatibility: "Uiwindowscene Is Only Available in iOS 13 or Newer"

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.

Trying to make iOS 13 project compatible with iOS 12


I'm using Xcode 10 for Swift 3 compatibility.

That’s the problem. You cannot mention iOS 13 classes using Xcode 10. It knows nothing about them. You must work entirely within Xcode 11 if you want to link against the iOS 13 SDK.

NSPersistentCloudKitContainer' is only available in iOS 13.0 or newer

You have to make sure that all targets are set to ios13.

Select the target, then build settings, then basic. top section

Xcode keeps giving .signUp is only available in iOS 13.2 . Is this some Xcode bug?

Use a function instead of a computed property to work around this issue.

@available(iOS 13.0, *)
func getAppleSignInButton() -> ASAuthorizationAppleIDButton {
let button: ASAuthorizationAppleIDButton
if #available(iOS 13.2, *) {
button = ASAuthorizationAppleIDButton(type: .signUp, style: .black)
}else{
button = ASAuthorizationAppleIDButton(type: .signIn, style: .black)
}
button.addTarget(self, action: #selector(appleSignUP), for: .touchDown)
return button
}

You could also separate it out and keep the lazy var if you wanted.

@available(iOS 13.0, *)
lazy var appleSignInButton: ASAuthorizationAppleIDButton = {
let button = getSignUpOrInButton()
button.addTarget(self, action: #selector(appleSignUP), for: .touchDown)
return button
}()

@available(iOS 13.0, *)
private func getSignUpOrInButton() -> ASAuthorizationAppleIDButton {
if #available(iOS 13.2, *) {
return ASAuthorizationAppleIDButton(type: .signUp, style: .black)
} else {
return ASAuthorizationAppleIDButton(type: .signIn, style: .black)
}
}

Is it possible to run WidgetKit code in iOS 13?

WidgetKit

The WidgetKit framework is only supported in iOS 14+, so no you cannot change the deployment version of your widget to iOS 13 because this is below the minimum iOS version. To learn more about WidgetKit refer to Apple's documentation here.

Notification Center

This is the depreciated api for creating Today View widgets in iOS versions earlier than iOS 14. If you want a widget that can be used in the Today View menu on devices running iOS 13 or earlier, then you need to use the Notification Center framework. However, this framework does not work the same as WidgetKit. You will need to create entirely different widgets for them to work with Notification Center. To learn more about Notification Center refer to Apple's documentation here.

Overall, it is my recommendation that if you are trying to create a widget for your app it is not worth the effort in creating widgets that support iOS 13 and earlier. For one, these widgets are only available in the today view not on the main home screen. Also, very few users still run iOS 13 compared to iOS 14, so it simply would not be worth supporting the small additional user base.

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
}
}

iOS: Concurrency is only available in iOS 15.0.0 or newer in protocol

The short answer is "there is currently no solution." If you want your apps to run on iOS 12 and earlier, you can't use the async/await calls, unless you want to write 2 versions of all your async code, one that runs on iOS < 15, and the other that runs on iOS ≥ 15.

As George mentions in his comment, Apple is trying to figure out how to "back-depoloy" async/await support. If they are able to do that, you will be able to use the modern approach with older versions, but I would bet Apple will not go back as far as iOS 12.

Edit:

See Bradley's comment below. The best you will get is async/await support in iOS 13, if Apple is able to pull that off. From the link Bradley posted, iOS 12 definitely won't be supported.

How to resolve: 'keyWindow' was deprecated in iOS 13.0

This is my solution:

let keyWindow = UIApplication.shared.connectedScenes
.filter({$0.activationState == .foregroundActive})
.compactMap({$0 as? UIWindowScene})
.first?.windows
.filter({$0.isKeyWindow}).first

Usage e.g.:

keyWindow?.endEditing(true)


Related Topics



Leave a reply



Submit