Instantiating Window from AppDelegate Failing in Swift 5.0 [duplicate] - swift

This question already has an answer here:
Xcode 11 & iOS13, using UIKIT can't change background colour of UIViewController
(1 answer)
Closed 3 years ago.
I'm using Xcode 11 and Swift 5.0. I copied over my code from another app that used Swift 4.2. That code instantiates a tabBarViewController in the AppDelegate, changes the window to the tabBarVC and shows it if certain conditions are met. But it is not working in Swift 5.0 and Xcode 11. Here's the code:
var window: UIWindow?
func application(_ application: UIApplication, willFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
// Run Firebase
FirebaseApp.configure()
// Check Firebase Auth to see if the user is signed in already.
if Auth.auth().currentUser != nil {
print ("User is already authenticated and signed in.")
// Check to make sure the user is stored in local settings.
if let appUser = LocalStorageService.loadCurrentUser() {
print ("Local storage has a user: \(appUser)")
// Create a tab bar controller.
let tabBarVC = UIStoryboard(name: "Main", bundle: .main).instantiateViewController(withIdentifier: Constants.Storyboard.tabBarController)
// Show it
window?.rootViewController = tabBarVC
window?.makeKeyAndVisible()
}
return true // If auth has a user and there is local user, window changes.
} else {
print ("No Authenticated current user, must proceed with Login.")
return false
}
}
The tabBarVC is populated successfully, but window remains nil. Then the app shows a different view controller. Nothing is different in the code from the original app that I can see. And that one still works. Thoughts would be appreciated.

I deleted the SceneDelegate from the Navigator and the following sections from the info.plist dealing with Scenes and now it works as expected (the other app did not have the SceneDelegate or these sections in the info.plist. I assume they were added when the project was created in Xcode 11):
I read about scenes for iOS 13 in the Apple Documentation. I don't plan to use scenes, but if someone does want to use scenes, they will have to determine how to configure the info.plist to allow the instantiation of a VC in the AppDelegate.

Related

Using Document Browser View Controller with Mac Catalyst

I have just started experimenting with Catalyst. My app is a document browser based app.
The stock MacOS Finder dialog is indeed launched when the appropriate button is clicked. The main app window completely disappears when the Finder dialog appears, unless I choose in the IB for the document browser view controller to appear in "Automatic" mode.
Cancelling the operation indeed brings back the main window.
However, selecting a file will yield a blank screen and no results. A little debugging revealed that none of the file selection functions is being called, and I have implemented all of them:
func documentBrowser(_ controller: UIDocumentBrowserViewController, didPickDocumentURLs documentURLs: [URL]) {...}
func documentBrowser(_ controller: UIDocumentBrowserViewController, didImportDocumentAt sourceURL: URL, toDestinationURL destinationURL: URL) {...}
func documentBrowser(_ controller: UIDocumentBrowserViewController, failedToImportDocumentAt documentURL: URL, error: Error?) {...}
Is there another function or handle used in Catalyst? I found nothing in the documentation.
EDIT:
I should clarify that I manipulated the app to present the DocumentViewController before the DocumentBrowserViewController, although Apple requires that the DocumentBrowserViewController is the initial view controller. I did it by changing the app delegate:
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
...
// Set the documentViewController to appear first
self.window = UIWindow(frame: UIScreen.main.bounds)
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let initialViewController = storyboard.instantiateViewController(withIdentifier: "main")
self.window?.rootViewController = initialViewController
self.window?.makeKeyAndVisible()
return true
}
Taking this out still doesn't change anything. And a default project created from the document browser template does seem to work. What could prevent these methods from being called?
I would suggest to implement func documentBrowser(_ controller: UIDocumentBrowserViewController, didPickDocumentsAt documentURLs: [URL]) as well

How can scene delegate be uses pre iOS 13?

So iOS 13 is really causing trouble for me! I cant get my head around how the SceneDelegate can be uses within my existing application.
Currently I'm using a boolean check in my AppDelegate to determine which ViewController is presented first.. I need to figure out how to replicate this using the SceneDelegate.
My main concern is how can I ensure my app will work on both iOS 13 and any iOS version before? If I add the SceneDelegate won't it crash my app running on pre iOS 13?
Here is my current method of setting the rootVC
if (loggedIn == nil){
Utilities.setLoginAsInitialViewContoller(window: window)
}
else
{
if(termsAgree == nil){
Utilities.setTermsAsInitialViewController(window: window)
}
if(loggedIn != nil){
if(termsAgree != nil){
Utilities.setHomeAsInitialViewContoller(window: window)
}
}
}
class func setLoginAsInitialViewContoller(window:UIWindow) {
let storyboard = UIStoryboard(name: “Login”, bundle: nil)
let controller = storyboard.instantiateViewController(withIdentifier: “LoginViewController”)
window.rootViewController = controller
window.makeKeyAndVisible()
}
class func setHomeAsInitialViewContoller(window:UIWindow) {
let storyboard = UIStoryboard(name: “Home”, bundle: nil)
let controller = storyboard.instantiateViewController(withIdentifier: “MainViewNavController”)
window.rootViewController = controller
window.makeKeyAndVisible()
}
class func setTermsAsInitialViewController(window:UIWindow){
let storyboard = UIStoryboard(name: “Terms”, bundle: nil)
let controller = storyboard.instantiateViewController(withIdentifier: “TermsViewController”)
window.rootViewController = controller
window.makeKeyAndVisible()
}
The above code is contained in my Utilities.swift class and is called in my AppDelegate.swift like this:
Utilities.decideInitialViewController(window: self.window!)
You cannot use the SceneDelegate pre iOS 13, this will actually lead to a compilation error.
xxx is only available in iOS 13.0 or newer
All existing app doesn't require the SceneDelegate to be compatible with iOS 13.
You can just build your application with the latest Xcode version, and you should be good to go (with few adjustments, like dark mode compatibility and some new UI changes, but this won't prevent your app from running in an iOS 13+ device).
Even if I would not recommend that, you still can use the SceneDelegate, but you must use the #available tag to tell Xcode that this code will only be used for iOS 13+ devices.
This means that iOS 13+ devices will use the SceneDelegate, and iOS 12 and less, will continue to use the AppDelegate. This method require to maintain some backward compatibility only code.
There is some explanation on that here

Receive SIGABRT error when trying to hijack root view controller

I am trying to have my application open a different view controller based upon whether an array is empty in the user's NSUserDefaults. Essentially, if the user has previously saved data in the app, the app will open up to where they can select the data. Otherwise, the app will open to a welcome screen.
However, when the array is empty, I see the background color that I set for the welcome screen, but not the text or button that I laid out in the storyboard. When the array is not empty and the data page should open, my app crashes with a SIGABRT error. I checked all of the outlets for the view controller in question and nothing seems to be disconnected. Additionally, when I comment out the code in the app delegate and set the data view controller as my initial starting view, the app runs fine.
The full error is "Thread 1: signal SIGABRT" and it is tagged in the class AppDelegate line.
The code I used in the App Delegate is below:
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
var accounts = loadAccounts()!
if accounts.isEmpty {
let welcomeController = WelcomeViewController()
self.window!.rootViewController = welcomeController
} else {
let tableController = AccountTableViewController()
self.window!.rootViewController = tableController
}
return true
}
func loadAccounts() -> [Account]? {
return NSKeyedUnarchiver.unarchiveObject(withFile: Account.ArchiveURL.path) as? [Account]
}
Maybe the UIWindow is not set properly.
let bounds = UIScreen.main.bounds
self.window = UIWindow(frame: bounds)
self.window?.rootViewController = `your view controller`
self.window?.makeKeyAndVisible()
What else can go wrong?
var accounts = loadAccounts()!
This line is the culprit for you. Precisely this symbol ! is I guess. You are trying to fetch data from a database or a filesystem and expect it will always be there.
Think about it, it can't be true all the time.
# check if account array is not empty; I would have returned nil if it would be empty and so we can avoid that extra check here.
if let accounts = loadAccounts(), !accounts.isEmpty {
let tableController = AccountTableViewController()
self.window!.rootViewController = tableController
return true
}
let welcomeController = WelcomeViewController()
self.window!.rootViewController = welcomeController
Also, if you can provide more info about the error message from your debug console. Then I would be able to help in a better way.

How to call function from viewcontroller in appdelegate? (Swift)

So basically I am trying to run the function Refresh (Located in ViewController.swift) in AppDelegate.swift. After searching for the better part of 5 hours I can't figure out why this isnt working.
here is my function:
func Refresh(){
if(Count==0 && QuickActionChecker==true){
Previous.text=String(TipArray[CountCurrent])
Current.text=String(TipArray[CountCurrent])
Deliveries.text="\(Runs)"
}
if(Count>0 && QuickActionChecker==true){
Previous.text=String(TipArray[CountCurrent-1])
Current.text=String(Total)
Deliveries.text="\(Runs)"
}
}
In my Appdelegate.swift I have initialized the ViewController by setting it to Main:
let Main = ViewController()
and here is where I'm attempting to run the function from (force touch quick action from the homescreen):
func application(_ application: UIApplication, performActionFor shortcutItem: UIApplicationShortcutItem, completionHandler: #escaping (Bool) -> Void) {
if shortcutItem.type == "com.Sicarix.Ticket.Add5"{
if(CountCurrent==0){
TipArray.append(5)
Count=Count+1
CountCurrent=CountCurrent+2
Total=TipArray.reduce(0) {$0+$1}
Runs=Runs+1
QuickActionChecker=true
Main.Refresh()
}
else{
TipArray.append(5)
Count=Count+1
CountCurrent=CountCurrent+1
Total=TipArray.reduce(0) {$0+$1}
Runs=Runs+1
QuickActionChecker=true
Main.Refresh()
}
}
}
however, when I try to use the shortcut it opens the app to a white screen and stays there. Console is spitting out:
fatal error: unexpectedly found nil while unwrapping an Optional value
the code runs fine when I remove the Main.Refresh(), it just doesn't update the labels in my app, hence the need for the function.
Please help, I'm so ready to move on past this bug....
also please bear in mind that I haven't even been coding in swift for a week yet, so please break down what was wrong as best you can. TIA
Change your viewController object
let nextVC = storyboard?.instantiateViewController(withIdentifier:"ViewController") as! ViewController
The problem is that you are instantiating a new ViewController using ViewController() and that ViewController isn't added to your view controller hierarchy.
You need to use your Storyboard to instantiate the ViewController by using let mainVC = UIStoryboard(name: "Main", bundle: "nil").instantiateViewController(withIdentifier:"ViewController") as! ViewController and make sure your identifier is set up in Storyboard.
If you are not using Storyboard, another solution is to store the text you want to display on the UI in data source variables and only update those variables in 'AppDelegate', then load the content of those variables onto your labels in 'viewDidLoad' or 'viewWillAppear'.

Populate a Navigation Controller on First Launch

When my app is first launched I want to create some sample data that new users will see. I'd like them to start a level (maybe more) into the navigation controller, like so:
tableViewController0 -> tvc1 (user starts here)
Picture a notes app that has folders as its top level of navigation. You might want to show the user a few sample notes in a sample folder first, then let him/her go back later and create new folders.
My thought was that I'd run a method in application didFinishLaunchingWithOptions that would check for first launch (checking/setting a Bool in NSUserDefaults) and then, if we are in the first launch, create some sample data. Then I thought I could just create each view controller and set my UINavigationController's viewControllers property, but I get this error:
'NSInternalInconsistencyException', reason: 'unable to dequeue a cell with identifier Cell - must register a nib or a class for the identifier or connect a prototype cell in a storyboard'
(The cell definitely does have an identifier Cell in the storyboard and works if I don't create the data and view controllers beforehand.)
Some sample code from my AppDelegate:
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
// From Xcode's stock AppDelegate
// Override point for customization after application launch.
let splitViewController = self.window!.rootViewController as! UISplitViewController
let navigationController = splitViewController.viewControllers[splitViewController.viewControllers.count-1] as! UINavigationController
navigationController.topViewController.navigationItem.leftBarButtonItem = splitViewController.displayModeButtonItem()
splitViewController.delegate = self
let masterNavigationController = splitViewController.viewControllers[0] as! UINavigationController
// Check for first launch, get back a sample object.
if isFirstLaunch == true {
let newObject = prepareFirstLaunch()
let tvc0 = TableViewController0()
tvc0.managedObjectContext = managedObjectContext
let tvc1 = TableViewController1()
tvc1.someObject = newObject
masterNavigationController.viewControllers = [tvc1, tvc0]
} else {
// this is moved from the stock AppDelegate down into this else statement.
let controller = masterNavigationController.topViewController as! TableViewController0
controller.managedObjectContext = self.managedObjectContext
}
return true
}
private func isFirstLaunch() -> Bool {
// return whether we're launching for the first time
}
private func prepareSampleObject() -> SomeObject {
/*
If we're launching for the first time
create someObject, create some other objects that are owned
by this object in CoreData, set up their relationships, etc.
*/
return someObject
}
Is there another way I can set this up so the user can jump right into a populated navigation stack rather than having to start at the top level?
You're using storyboards, which means you have an initial view controller.
Make this initial view controller a UINavigationController whose rootViewController is some SetupViewController where all of your checking logic occurs. Show a UIActivityIndicatorView in it, or whatever loading animation. Then, depending on what you've found, push either the dummy notes screen or the top-level folder screen.
In the storyboard, you will create two segues from the SetupViewController--one to the notes, one to the folder. Give each segue its own name. You call performSegueWithIdentifier in the code where you determine which screen is getting pushed.