I have a new feature on my iOS app that is disabled by default. To manage this, I use Firebase RemoteConfig to do this, and added new_feature_enabled = false to a config_debug.plist file. I then set this as the default using remoteConfig.setDefaults(fromPlist: "config_debug").
My app has a background task, which when upon receiving a silent push notification, it would wake up the iOS app in the background to perform some tasks.
The issue I am facing is, after I enable the new feature remotely on Firebase console (ie setting new_feature_enabled = true on the console), and trigger the silent push notification, the app wakes up but the feature is reverted to disabled mode. I then kill the app, and launched the app again, the feature then got enabled.
In other words, every time the device receives the silent push notification and wakes up in the background, the remoteConfig resets to the local config.plistfile instead of using the remote version set on Firebase console.
Code:
//In AppDelegate
func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable : Any], fetchCompletionHandler completionHandler: #escaping (UIBackgroundFetchResult) -> Void) {
//handle silent PN and do background tasks...
handleSilentPN(userInfo: userInfo)
completionHandler(.newData)
}
func handleSilentPN(userInfo: [AnyHashable: Any]) {
guard let scheduler = userInfo["scheduler"] as? [String: AnyObject] else {return}
guard let rootViewController = (UIApplication.shared.connectedScenes.first?.delegate as? SceneDelegate)?.window?.rootViewController as? MainTabController else {
return
}
let vc = SomeController()
let controller = UINavigationController(rootViewController: vc)
rootViewController.present(controller, animated: false)
}
//At SceneDelegate
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
//Setup remote config
setupRemoteConfig()
let vc = MainTabController()
setupWindow(viewController: vc, scene: scene)
}
fileprivate func setupWindow(viewController: UIViewController, scene: UIScene) {
guard let windowScene = (scene as? UIWindowScene) else { return }
window = UIWindow(frame: UIScreen.main.bounds)
window?.makeKeyAndVisible()
window?.windowScene = windowScene
window?.rootViewController = viewController
}
func setupRemoteConfig() {
let remoteConfig = RemoteConfig.remoteConfig()
//Throttles for development to see changes frequently
#if DEBUG
let settings = RemoteConfigSettings()
settings.minimumFetchInterval = 0
remoteConfig.configSettings = settings
#endif
//Define a default setting
#if DEBUG
remoteConfig.setDefaults(fromPlist: "config_debug")
#else
remoteConfig.setDefaults(fromPlist: "config_release")
#endif
remoteConfig.fetchAndActivate { (status, error) in
if let error = error {
Log("Err setingup remote config: \(error.localizedDescription)")
}
//0: .successFetchedFromRemote
//1: .successUsingPreFetchedData
//2: .error
Log("Remote config status: \(status.rawValue)")
}
}
My code syncs RemoteConfig at SceneDelegate and ensures they are the remote version that is fetched from Firebase console. I suspected that setupRemoteConfig was called before didReceiveRemoteNotification and therefore did not sync the remote version.
I reattempted by moving setupRemoteConfig to didFinishLaunchingWithOptions and didReceiveRemoteNotification but both seems to give the same results.
I then tested which functions get called when the app wakes up in the background, and noticed that the following do not get called:
didFinishLaunchingWithOptions
application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions)
scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions)
sceneDidBecomeActive(_ scene: UIScene)
Where should I call setupRemoteConfig() so that I can maintain the remote version of the remoteConfig after receiving silent PN, and ensure that the new feature is enabled?
Related
My app supports older versions of iOS 10.0 and above. When I try to launch my app in iOS 13, it shows a black window. It does configure initial ViewController and shows labels when I remove the scene delegate. Everything works in iOS 12 and under with just App delegate.
In my App delegate:
#main
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
configureInitialViewController()
return true
}
func configureInitialViewController()
{
var initialVC: UIViewController
let mainsb = UIStoryboard(name: "Main", bundle: nil)
if Auth.auth().currentUser != nil {
//print("successfully signed in")
initialVC = mainsb.instantiateViewController(withIdentifier: IDENTIFIER_MAIN_TABBAR)
} else {
//print("successfully signed out")
initialVC = mainsb.instantiateViewController(withIdentifier: IDENTIFIER_WELCOME_NAV)
}
window?.rootViewController = initialVC
window?.makeKeyAndVisible()
}
func applicationWillResignActive(_ application: UIApplication) {
Api.User.isOnline(bool: false)
}
// 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.
}
}
In my Scene delegate:
#available(iOS 13.0, *)
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 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).
guard let _ = (scene as? UIWindowScene) else { return }
}
func sceneWillResignActive(_ scene: UIScene) {
Api.User.isOnline(bool: false)
}
}
I also deleted Application Scene Manifest in my info.plist.
Can someone help me check if I'm setting up version supports correctly?
I would really appreciate any help.
Update:
I tested it works in iOS 13.5 but not 13.2 or 14.3. Why is it showing different behaviors in different versions?
In your AppDelegate inside didFinishLaunchingWithOptions add this:
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
if #available(iOS 13.0, *){}
else{
//Load your vc
}
return true
}
Then in your SceneDelegate inside willConnectTo add these:
#available(iOS 13.0, *)
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).
guard let _ = (scene as? UIWindowScene) else { return }
guard let windowScene = (scene as? UIWindowScene) else {return}
let window:UIWindow = UIWindow(windowScene: windowScene)
self.window = window
self.window?.rootViewController = // set you VC
self.window?.makeKeyAndVisible()
}
I am trying to handle open apps from universal link click. below ios 13 its working good but for ios 13 its working only app running in background. If app not working foreground or background, clicking link opens app not called continue userActivity function. I also tried to get it in scene delegate willconnnect to delegate. But still not calling
My code is below what is wrong?
scene delegate
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
guard let _ = (scene as? UIWindowScene) else { return }
if connectionOptions.userActivities.first != nil {
self.scene(scene, continue: userActivity!)
}
}
func scene(_ scene: UIScene, continue userActivity: NSUserActivity) {
continueUserActivity(userActivity: userActivity)
}
func continueUserActivity(userActivity : NSUserActivity){
if userActivity.activityType == NSUserActivityTypeBrowsingWeb {
let url = userActivity.webpageURL!
let dataDict:[String: String] = [AppLinkManager.appLinkExtraKey: url.absoluteString]
NotificationCenter.default.post(name: .didReceiveAppLink, object: nil, userInfo: dataDict)
}
}
app delegate
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
if let userActivityDict = launchOptions?[.userActivityDictionary] as? [AnyHashable : Any],
let userActivity = userActivityDict["UIApplicationLaunchOptionsUserActivityKey"] as? NSUserActivity {
continueUserActivity(userActivity: userActivity)
}
return true
}
func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: #escaping ([UIUserActivityRestoring]?) -> Void) -> Bool {
continueUserActivity(userActivity: userActivity)
return true
}
func continueUserActivity(userActivity : NSUserActivity){
if userActivity.activityType == NSUserActivityTypeBrowsingWeb {
let url = userActivity.webpageURL!
let dataDict:[String: String] = [AppLinkManager.appLinkExtraKey: url.absoluteString]
NotificationCenter.default.post(name: .didReceiveAppLink, object: nil, userInfo: dataDict)
}
}
Have you tried implementing continue userActivity function in SceneDelegate:
func scene(_ scene: UIScene, continue userActivity: NSUserActivity) { }
I faced the same issue when using Firebase DynamicLink and looks like UniversalLinks (and probably several other APIs) use this callback on the SceneDelegate.
So if you're targeting iOS 13 and below try implementing both for SceneDelegate and AppDelegate
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
if let navController = window.rootViewController as? UINavigationController,
let personController = navController.controllers[0] as? PeopleTableViewController{
print("something")
}
return true
}
But it is giving me an error, it is not recognizing the window property of the AppDelegate, did they change with IOS 13 because it works in my IOS 12 project
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
lazy var persistentContainer: NSPersistentContainer = {
let container = NSPersistentContainer(name: "DataModel")
container.loadPersistentStores { description, error in
if let error = error {
fatalError("Unable to load persistent stores: \(error)")
}
}
return container
}()
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
if let navController = window?.rootViewController as? UINavigationController,
let personController = navController.viewControllers[0] as? PeopleTableViewController{
personController.persistentContainer = persistentContainer
print("Nailed it ")
} else {
print("Sorry")
}
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.
}
func applicationWillTerminate(_ application: UIApplication) {
persistentContainer.saveContextIfNeeded()
}
}
This is how my AppDelegate class looks
Here is how you set App's initialView Controller/rootViewController inside Scene Delegate class (this class handles Application Lifecycle Methods which were earlier handled by AppDelegate).
import UIKit
import SwiftUI
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
var window: UIWindow?
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
let initialViewController = UIStoryboard(name: "Storyboard", bundle: nil).instantiateViewController(identifier: "DatePicker") as! DatePicker
let navigation = UINavigationController(rootViewController: initialViewController)
// Use a UIHostingController as window root view controller.
if let windowScene = scene as? UIWindowScene {
let window = UIWindow(windowScene: windowScene)
window.rootViewController = tabBarCnt
self.window = window
window.makeKeyAndVisible()
}
}
func sceneDidDisconnect(_ scene: UIScene) {
// Called as the scene is being released by the system.
// This occurs shortly after the scene enters the background, or when its session is discarded.
// Release any resources associated with this scene that can be re-created the next time the scene connects.
// The scene may re-connect later, as its session was not neccessarily discarded (see `application:didDiscardSceneSessions` instead).
}
func sceneDidBecomeActive(_ scene: UIScene) {
// Called when the scene has moved from an inactive state to an active state.
// Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive.
}
func sceneWillResignActive(_ scene: UIScene) {
// Called when the scene will move from an active state to an inactive state.
// This may occur due to temporary interruptions (ex. an incoming phone call).
}
func sceneWillEnterForeground(_ scene: UIScene) {
// Called as the scene transitions from the background to the foreground.
// Use this method to undo the changes made on entering the background.
}
func sceneDidEnterBackground(_ scene: UIScene) {
// Called as the scene transitions from the foreground to the background.
// Use this method to save data, release shared resources, and store enough scene-specific state information
// to restore the scene back to its current state.
print("App entered Background")
}
}
I found out a way around. instead of putting your code in didFinsishLoadingWithOptions method, getting the UIview comptroller's persistentContainer variable and then passing the persistentContainer in AppDelegate. you can just add this to the top of the view Controller.
let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.context
if you want the appDelegate you can access like this
let AppDelegate = UIApplication.shared.delegate as! AppDelegate
This works both IOS12 and IOS13 so, you don't have to change your code
There's no shortage of tips and tutorials on handling custom URL schemes in iOS. What ALL fail to do is actually show you how to pass data parsed from those URLs to your app/views. Yea, I can use a global variable, but that's not the "right" way and plus if you want your Swift view to react to a change in that global variable, you can't.
For example, I have,
func scene(_ scene: UIScene, openURLContexts URLContexts: Set<UIOpenURLContext>){
let urlContext = URLContexts.first // Because I don't know how to properly deal with Sets
if let url = urlContext?.url{
guard let components = NSURLComponents(url: url, resolvingAgainstBaseURL: true),
let params = components.queryItems else {
print("Invalid URL or album path missing")
return
}
if let token = params.first(where: { $0.name == "token" })?.value {
print("Token: \(token)")
MyGlobalToken = token
}
}
}
You'll see the MyGlobalToken option in there which works, but I can't respond to a change in that variable. Do I have to do something with the self.window?.rootViewController but I can't find any documentation on what to do. Or do you set up a "notification" so that you view responds? Or is this not implemented yet in SwiftUI?
FWIW I'm new to iOS development.
Here is a great blog to learn about SceneDelegate in iOS 13.
First answer is not a great answer.
If you run your app from a completely inactive state -- i.e. when you run it from XCode aka when it's restarted or not running in the background -- the app will call the func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) method to initialize the scenes infrastructure.
If you are running your app in the background when you point to the URL in Safari the app will call the func scene(_ scene: UIScene, openURLContexts URLContexts: Set<UIOpenURLContext>) method.
In order to be thorough, you will have to attempt to get the url parameters in both methods. You can pass this query to the AppDelegate for future use by ViewControllers in both methods as well.
NOTE: If the first ViewController the app opens needs the URL query information you will need to do a few extra steps. To get your ViewControllers to actually update with the information, you will need to use the sceneDidBecomeActive() method for when the app is run from an inactive state/in the background. This method will have to call a method in the ViewController in order for it to pull the variable from the app delegate when the user enters your app. In this case I used viewDidLoad() methods to pull the updated variable from the AppDelegate.
Below is the full code for reference:
import UIKit
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
var window: UIWindow?
let appDelegate = UIApplication.shared.delegate as! AppDelegate
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
appDelegate.query = connectionOptions.urlContexts.first?.url.query ?? "No query"
guard let _ = (scene as? UIWindowScene) else { return }
}
func scene(_ scene: UIScene, openURLContexts URLContexts: Set<UIOpenURLContext>) {
appDelegate.query = URLContexts.first?.url.query
}
func sceneDidDisconnect(_ scene: UIScene) {}
// Only needed if first ViewController needs updated AppDelegate Variable
func sceneDidBecomeActive(_ scene: UIScene) {
self.window?.rootViewController?.viewDidLoad()
}
func sceneWillResignActive(_ scene: UIScene) {}
func sceneWillEnterForeground(_ scene: UIScene) {}
func sceneDidEnterBackground(_ scene: UIScene) {
(UIApplication.shared.delegate as? AppDelegate)?.saveContext()
}
}
I had many trouble in this problem.
I don't know many things in IOS.
But There is no answer. I write it.
If you don't use sceneDelegate, You may use your global variable.
(I don't know why it doesn't work)
For this, I do like below.
Delete scenedelegate.swift.
delete sceneDelegate thing in Info.plist
initialLize check variable
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
UserDefaults.check = ""
return true
}
Add lines in Appdelegate for setting global variable.
open func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool {
//set Global variable
// ... You should add check url for your application ...
UserDefaults.check = "checked"
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
if let uservc = self.storyboard?.instantiateViewController(withIdentifier: "MainViewController") as? MainViewController
{
if #available(iOS 13.0, *) {
uservc.isModalInPresentation = true
}
uservc.debugText.text = "This is openUrl State"
self.present(uservc, animated: false) {
}
}
}
Make IntroViewController and MainViewController
IntroViewController is rootviewcontroller.
and MainViewController is second view controller.
if check variable is "checked", Viewdidload in IntroView is not excuted.
and this time is application-open is excuted with safari or chrome.
//IntroViewController
class IntroViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
DispatchQueue.main.asyncAfter(deadline: .now() + 3.0) {
if UserDefaults.check.elementsEqual("") {
if let uservc =
self.storyboard?.instantiateViewController(withIdentifier: "MainViewController") as? MainViewController
{
if #available(iOS 13.0, *) {
uservc.isModalInPresentation = true
}
uservc.debugText.text = "This is normal State"
self.present(uservc, animated: false) {
}
}
}
}
6.Global Variable
//UserDefaults.swift
import Foundation
fileprivate let keyCheck = "keyCheck"
extension UserDefaults {
static var check: String {
get {
return UserDefaults.standard.string(forKey: keyCheck) ?? ""
}
set(value) {
UserDefaults.standard.set(value, forKey: keyCheck)
UserDefaults.standard.synchronize()
}
}
}
When I use this logic in scenedelegate, It didn't work well.(I couldn't check "check variable".)
I am currently getting a blank screen with Xcode 11, Target iOS 13.0 (the app works fine with all below versions iOS 12.1 till 12.4), I want to make my App work for both iOS users above 12.1 and also 13.0 currently getting blank screen despite adding the below UIWindowSceneDelegate to my existing project and App Manifest:
adding App Manifest file
import UIKit
import SwiftUI
#available(iOS 13.0, *)
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
var window: UIWindow?
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
//guard let _ = (scene as? UIWindowScene) else { return }
let user = UserDefaults.standard.object(forKey: "defaultsuserid")
let userSelfIdent = UserDefaults.standard.object(forKey: "userinitialident")
if let windowScene = scene as? UIWindowScene {
let internalWindow = UIWindow(windowScene: windowScene)
if (user != nil && userSelfIdent != nil){
let mainstoryboard:UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
let newViewcontroller:UIViewController = mainstoryboard.instantiateViewController(withIdentifier: "swrevealviewcontroller") as! SWRevealViewController
internalWindow.rootViewController = newViewcontroller
self.window = internalWindow
internalWindow.makeKeyAndVisible()
}else {
guard let _ = (scene as? UIWindowScene) else { return }
}
}
}
The following is my AppDelegate which is getting called first and executing the didFinishLaunchWithOptions method. I want to know how can i make this method call only if my Target ios is less than 13.0 and call SceneDelegate method to initialize my rootViewController after 13.0?
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
#available(iOS 13.0, *)
func application(_ application: UIApplication,
configurationForConnecting connectingSceneSession: UISceneSession,
options: UIScene.ConnectionOptions) -> UISceneConfiguration {
return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role)
}
#available(iOS 13.0, *)
func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set<UISceneSession>) {
}
#available(iOS 13.0, *)
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
guard let _ = (scene as? UIWindowScene) else { return }
if (user != nil && userSelfIdent != nil){
let mainstoryboard:UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
let newViewcontroller:UIViewController = mainstoryboard.instantiateViewController(withIdentifier: "swrevealviewcontroller") as! SWRevealViewController
self.window?.rootViewController = newViewcontroller
}
}
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
Thread.sleep(forTimeInterval: 3.0)
UINavigationBar.appearance().barTintColor = UIColor(red:0.08, green:0.23, blue:0.62, alpha:1.0)
if (user != nil && userSelfIdent != nil){
let mainstoryboard:UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
let newViewcontroller:UIViewController = mainstoryboard.instantiateViewController(withIdentifier: "swrevealviewcontroller") as! SWRevealViewController
self.window?.rootViewController = newViewcontroller
}
return true
}
func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
let defaultUserID = UserDefaults.standard.string(forKey: "defaultUserID")
}
func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable : Any], fetchCompletionHandler completionHandler: #escaping (UIBackgroundFetchResult) -> Void) {
switch (application.applicationState) {
case UIApplicationState.active:
do something
case UIApplicationState.background, UIApplicationState.inactive:
let mainstoryboard:UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
let newViewcontroller = mainstoryboard.instantiateViewController(withIdentifier: "swrevealviewcontroller") as! SWRevealViewController
self.window?.rootViewController = newViewcontroller
}
}
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
}
}
Steps for running with iOS 13 and lower versions:
Change deployment target to iOS 12.
Replace the AppDelegate's methods with what they ought to have for iOS 12 development. Also add this:
var window: UIWindow?
Remove SceneDelegate.
Remove Application Scene Manifest in your info.plist.
It will work on both iOS 13 and lower iOS Version
Easily follow these steps:
Remove scene delegate file
Add below code to AppDelegate.swift
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
return true
}
}
Remove the Application Scene Manifest line from your .plist file
The accepted answer is correct in a lot of ways, but I believe it's not a complete answer. I posted a comment that already helped several people to fully solve the issue, so I will post this as an answer as well:
If you messed this up once and it's already installed in the simulator, every new launch will still have this issue, even if your settings are correct...
The solution is to remove the App and reinstall it with the corrected code! It's a crazy fix, but it works!
I commented 2 scene methods in the AppDelegate, and then it works
please check my screenshot
I was stuck with this problem, and finally i solved removing the searchDisplayController references from storyboard.
<searchDisplayController id="pWz-So-g6H">
<connections>
<outlet property="delegate" destination="Yci-sd-Mof" id="fjs-ah-jLs"/>
<outlet property="searchContentsController" destination="Yci-sd-Mof" id="gQN-1r-gti"/>
<outlet property="searchResultsDataSource" destination="Yci-sd-Mof" id="2Jf-lh-Ute"/>
<outlet property="searchResultsDelegate" destination="Yci-sd-Mof" id="Hap-SA-f02"/>
</connections>
</searchDisplayController>
When I had a similar issue it was due to the Single-App template generated using Xcode 11.0 was incompatible to the one being needed for an app built with Xcode 11.2.
So I just created a new Single-Page-App with Xcode 11.2 and copied the generated SceneDelegate to my old project which was created using Xcode 11.0.
After that, the blank screen was gone an my interface visible once more.
Please refer above image and add the line in AppDelegate.
xcode 11.6
#ionic/react capacitor app
UPDATED:7/22/2020
We had black screen issue at TestFlight too.
WE finally solved.
Check config.capacitor.json and see if u have localhost there,
delete URLs under server section..
we(I) forgot our URL localhost still there when we run release, to be honest i did not know that could be problem or never think to check there,
I spent time(days) to check storyBoard suggestions.
I was porting some sample code from older App Delegate presentation to a new Xcode project with the new Scene Delegate infrastructure.
Even after moving the code the Scene Delegate, I was getting a black screen.
The issue was the way in which I was getting a reference to the window.
Use the new UIWindow(windowScene:) initializer rather than initWithFrame:.
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
var window: UIWindow?
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
// OLD: causes black screen
self.window = UIWindow(frame: UIScreen.main.bounds)
// NEW: works perfectly
guard let windowScene = (scene as? UIWindowScene) else { return }
self.window = UIWindow(windowScene: windowScene)
let vc = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "INITAL_VIEW_CONTROLLER")
self.window?.rootViewController = vc
self.window?.makeKeyAndVisible()
}
}