Populate a Navigation Controller on First Launch - swift

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.

Related

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.

UINavigationController, UITabBarController, UITableViewController

I currently have the application set up with a UINavigationController as the initial view, which has a UITableViewController as its root view controller. The app runs fine up until this point. I have the following code in AppDelegate.swift:
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
let favLibrary = FavLibrary()
let navController = window!.rootViewController as! UINavigationController
let favController = navController.topViewController as! FLViewController
favController.favLibrary = favLibrary
return true
}
I am trying to implement a UITabBarController so that I can switch between two UITableViewControllers at the same level (Favorites and a Library) using the Tab Bar.
I embed each VC in its own Navigation Controller, then I embed the two Navigation controllers into one Tab Bar Controller.
Upon running the application, it crashes with the following error:
Could not cast value of type 'UITabBarController' (0x115f9e430) to 'UINavigationController' (0x115f971d0).
2018-09-27 15:49:43.811377-0700 appName [3675:954448] Could not cast value of type 'UITabBarController' (0x115f9e430) to 'UINavigationController' (0x115f971d0).
How can I correct the code in AppDelegate to retain functionality with the new arrangement of Tab Bar and Navigation Controllers?
If I understand your question correctly, I suppose you should access the tabBarController first, then retrieve the view controllers it contains, which should be a list of navigation controllers. Then you can get your view controller inside the selected navigation controller:
let tabBar = window!.rootViewController as! UITabBarController
let targetTabNav = tabBar.viewControllers![1] as! UINavigationController // change index to what you want
let targetVc = targetTabNav.viewControllers.first!
// Do what you want with the target Vc ...

Simple data transfer within viewcontroller without transition

So i am new to app development and i am trying to set up a very simple delegation/protocol pattern. I have been searching and trying different tutorials but can't seem to find anything that works and am getting in such a muddle. Please can somebody help. I will break i down so that its really clear as to what i need -
I have two view controllers, 'DetailedVC' and 'SelectionsVC'.
DetailedVC has a variable called -
var sendingData = (choice: "", choiceValue:0.0)
and
UIbutton buttonSelectTapped
SelectionsVC has a variable called -
var recievedData = (choice: "", choiceValue:0.0)
And all i want to do is send the data from the variable 'sendingData' in DetailedVC when the button (buttonSelectTapped) is tapped to the SelectionsVC and store it in the variable 'recievedData'. I do not want the VC to transition from one to the other or anything to be sent back, only to send the data to the other VC.
Then when the user views that controller 'SelectionsVC' at whatever stage, the data will be called in the viewDidLoad when loading that controller.
Use NSUserDefault to pass data between viewcontroller if you do not want the VC to transition from one to the other or anything to be sent back, only to send the data to the other VC.
DetailedVC Code
func viewDidLoad() {
NSUserDefaults.standardUserDefaults().removeObjectForKey("selectedData")
}
func didTapButtonSelectTapped() {
NSUserDefaults.standardUserDefaults().setDouble(sendingData , forKey: "selectedData")
}
SelectionsVC code
func viewDidLoad() {
if(NSUserDefaults.standardUserDefaults().doubleForKey("selectedData")) {
recievedData = NSUserDefaults.standardUserDefaults().doubleForKey("selectedData")
}
}
But as your question title describe their is no use of protocol/delegate in above code.
Passing Data on transition from viewcontroller :
DetailedVC Code
func didTapButtonSelectTapped() {
let vc = SelectionsVC()
vc.recievedData = sendingData
self.presentViewController(vc, animated: true, completion: nil)
}

Swift - Access different View Controllers in App Delegate

In app delegate, with a simple app having only 2 screens:
first screen is a Table View Controller embedded in Navigation Controller
second screen is a View Controller which is used to add items to the first screen table via protocol/delegate/segue
And this is the code for didFinishLaunchingWithOptions in app delegate that I can reference the first screen as viewController[0]:
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
let navController = self.window?.rootViewController as! UINavigationController
let courseListController = navController.viewControllers[0] as! CourseListController
courseListController.managedObjectContext = self.managedObjectContext
return true
}
How can I reference the screens at above, below and next to the center sreen? Please suggest me a solution. Thank you!
It's important to keep in mind that the view controller objects for all of the "peripheral" view controllers in your story board won't actually exist until the segue to get to them is executed, so there's no way to get access to them directly from the app delegate. Instead, you need to push state to each child view controller as it's created, from whatever the source view controller is. Segues are the appropriate way to do this.
You will probably want to assign each of the segues from the central view controller a unique segue identifier in Interface Builder. Click on the segue, then enter it here:
In the central view controller, implement prepareForSegue(_:sender:), doing something like the following:
func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject) {
switch segue.identifier {
case "SegueIdentifier1":
guard let destination = segue.destinationViewController as? ViewController1 else {
return
}
// set up your view controller here
case "SegueIdentifier2":
guard let destination = segue.destinationViewController as? ViewController2 else {
return
}
// set up your view controller here
// add additional segues as required
default:
break // unknown segue
}
}
Go to each view Controller you want to reference and in the identity inspector, add some string to its StoryBoard ID.
next to reference it from the new ViewController (say, XViewController) to (say, YViewController)
do this :
var referencedViewController = self?.storyboard.
instantiateViewControllerWithIdentifier("referenceViewID") as! YViewController
self.presentViewController(referencedViewController,
animated: true, completion: nil)

Orientation issue when loading storyboard - iOS

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.