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.
Related
(To forestall well-intended suggestions, yes I posted this question last week on Facebook's developer forum. No responses yet.)
TL;DR
Facebook SDK 5.8 complains at startup FBSDKLog: Unable to find a valid UIWindow.
The Main Story
In a from-scratch, one-view Xcode 11/iOS 13 project, there is no longer a default UIWindow member associated with the application. (The window per se is still around; you can see it, contained in a UIWindowScene, using the View Hierarchy Debugger in Xcode, or the Reveal app.)
FBSDK 5.8 does seem to be iOS-13-aware, and looks around for it. The relevant code is at line 498 of
https://github.com/facebook/facebook-ios-sdk/blob/master/FBSDKCoreKit/FBSDKCoreKit/Internal/FBSDKInternalUtility.m.
Facebook's code iterates over the application's connectedScenes member, which for me is an empty set. How do I modify my code so that FBSDK finds the window?
Some Hacking
I tried adding the following to scene(_:willConnectTo:options:) but it seems to be too late — the FBSDKLog message has already appeared by then. (So I'm flailing...)
guard let s = (scene as? UIWindowScene) else { return }
self.window = UIWindow(windowScene: s)
The following also failed, but it was just a shot in the dark:
guard let s = (scene as? UIWindowScene) else { return }
self.window = UIWindow(frame: s.coordinateSpace.bounds)
self.window?.windowScene = s
self.window?.rootViewController = ViewController(nibName: nil, bundle: nil)
self.window?.makeKeyAndVisible()
If you don't use the new behaviour and don't mind reverting to the old way, you can try the following
Delete Application Scene Manifest key from Info.plist
Delete SceneDelegate.swift
Add var window: UIWindow?to AppDelegate.swift
import UIKit
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow? // <-- Here
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
// window!.makeKeyAndVisible()
return true
}
}
Add window variable to AppDelegate class
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
Assign UIWindow inside didFinishLaunchingWithOptions
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
window = UIWindow(frame: UIScreen.main.bounds)
if let window = window {
window.makeKeyAndVisible()
self.window = window
}
ApplicationDelegate.shared.application(
application,
didFinishLaunchingWithOptions: launchOptions
)
return true
}
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.
I've read several of the suggested posts that were to help with this problem but could not find something for this particular issue.
I need to set a property on my controller, the book is saying to do so in the app delegate. In the previous assignment using a navbar, this worked:
func application(_ application: UIApplication, didFinishLaunchingWithOptions
launchOptions: [UIApplicationLaunchOptionsKey : Any]?) -> Bool {
// Override point for customization after application launch.
// Create an ItemStore to hold list of items
let itemStore = ItemStore()
// Access the ItemsViewController and set its item store
let itemsController = window!.rootViewController as! ItemsViewController
itemsController.itemStore = itemStore
return true
}
However, creating a similar program I need to use tab bar but can't get it to work and keep running into the error stated above, on the let editcontroller = tabcontroller. - line
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
// Create an pindatabase
let pinDataBase = PinDatabase()
// Access the pairsViewController and set its database
let tabController = window!.rootViewController as! UITabBarController
// this edit controller line is where I am stuck
let editController = tabController.tabBar as! EditViewController
editController.pinDataBase = pinDataBase
return true
}
The error is:
fatal error: unexpectedly found nil while unwrapping an Optional value
The controller I'm trying to set the property on is not the root controller but the third tab if that helps.
Your issue is your attempt to cast the UITabBar to your EditViewController. You need to access the array of view controllers from the tab bar controller and access the one that is actually the EditViewController.
let editController = tabController.viewControllers[2] as! EditViewController
This will access the 3rd view controller and cast it as needed.
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.
I have created a view controller in didFinishLaunching method of AppDelegate and assigning it to window's root view controller.
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
// Override point for customization after application launch.
window = UIWindow(frame: UIScreen.mainScreen().bounds) // issue with allocating UIWindow
self.viewController = AKSidePanelController()
self.viewController!.centerPanel = UINavigationController(rootViewController: UIStoryboard.centerViewController()!)
window!.rootViewController = self.viewController
window!.makeKeyAndVisible()
return true
}
func shouldAutorotate() -> Bool {
return true
}
func supportedInterfaceOrientations() -> Int {
return UIInterfaceOrientation.Portrait.rawValue | UIInterfaceOrientation.LandscapeLeft.rawValue | UIInterfaceOrientation.LandscapeRight.rawValue
}
for some reasons, auto rotation is not working. i think the problem is because of allocating window in didfinishlaunching. When i try to launch directly from storyboard(by marking isInitialViewController = yes) without adding any code on didFinishLaunching, autorotation works. As you can see, i need to load "viewController" as rootviewcontroller. What i am doing wrong?
I am using Xcode 6.1
The problem is that i am loading View from storyboard as well as initialising view controller in didFinishLaunchingOption in App delegate. the solution is to avoid loading initial View from storyboard.
Set IsinitialViewController to no and load the view controller from appdelegate
This one helps me to fix the issue. Programmatically set the initial view controller using Storyboards
Note that you have to make "Main storyboard file base name" as empty in plist.