Programmatically connecting Storyboard w/ ViewController - ios

I've been SLOWLY learning how to build iOS applications with Swift and was sent this article by someone why has been doing it professionally: A Case For Using Storyboards on iOS. Although I have taken the approach of learning how to build iOS application programmatically, I do believe that storyboards have their place in iOS development. I'm trying to implement Marin Benčević idea by having a new storyboard for every view w/ its own ViewController. However, I'm having issues getting this connected. I continuously run into this same error:
Failed to instantiate the default view controller for UIMainStoryboardFile 'LoginViewController' - perhaps the designated entry point is not set?
Now my file structure is a little different as I want to keep my views separated from my controllers but its as shown:
LoginViewController.swift:
class LoginViewController: UIViewController {
override func viewDidLoad() {
self.view.backgroundColor = UIColor.green
let vc = LoginViewController.instance()
self.present(vc, animated: true, completion: nil)
}
override var preferredStatusBarStyle: UIStatusBarStyle {
return .lightContent
}
}
AppDelegate.swift:
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
self.window = UIWindow(frame: UIScreen.main.bounds)
let loginStoryboard = LoginViewController()
self.window?.rootViewController = loginStoryboard
self.window?.makeKeyAndVisible()
return true
}
UIStoryboardExt.swift:
import UIKit
extension UIStoryboard {
func initialViewController<T: UIViewController>() -> T {
return self.instantiateInitialViewController() as! T
}
}
UIViewControllerExt.swift:
import UIKit
extension UIViewController {
class func instance() -> Self {
let storyboardName = String(describing: self)
let storyboard = UIStoryboard(name: storyboardName, bundle: nil)
return storyboard.initialViewController()
}
}
info.plist:
Main storyboard file base name: LoginViewController
All in all, I'm wondering how can I connect a UIViewController to a View.storyboard programmatically without having to use the property tab of the storyboard. I've tried so many different variations of how to do this but I cannot seem to figure it out.
Here was a StackOverflow Question that I also attempted to get working but could not:StackOverflow
EDIT
Along with what Christian Abella said, I did need to also set the class like of the storyboard object

This is doable and I do this in some of my projects.
But you need to do the following:
Remove all "As Initial View Controller" ticks form your storyboard files.
In the Apps' Settings go to General tab and clear the Main Interface from the Deployment Info
In the Apps's Setting go to Info tab and clear the 'Main storyboard file base name' property.
self.window = UIWindow(frame: UIScreen.main.bounds)
let main : UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
let loginVC = main.instantiateViewController(withIdentifier: "LoginViewController") as! LoginViewController
self.window?.rootViewController = loginVC
self.window?.makeKeyAndVisible()
return true
And as #Losiowaty pointed out, your viewDidLoad will cause an endless loop so you need to remove that code the present the LoginViewController.

Related

Swift: Two View Controllers presented

Apologies for the ambiguity of the question; I'll try to explain clearer here.
The Problem
I have some code in AppDelegate.swift that performs conditional navigation; if the user is signed in (and therefore currentUser is not nil), the app will start in View, a View Controller in the Main storyboard. Else, if the user isn't signed in, the app will start in Welcome, a View Controller in an Auth storyboard.
The following is my code from AppDelegate.swift, ViewController.swift (which is View), and WelcomeViewController.
Note:
View is the Storyboard ID I set for ViewController; Welcome is the Storyboard ID I set for WelcomeViewController.
AppDelegate.swift
import UIKit
import Firebase
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// Configures the Firebase library.
FirebaseApp.configure()
// Performs conditional navigation on the condition of currentUser's status.
self.window = UIWindow(frame: UIScreen.main.bounds)
let user = Auth.auth().currentUser
if user != nil {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let rootViewController = storyboard.instantiateViewController(identifier: "View")
window?.rootViewController = rootViewController
UIView.animate(withDuration: 0.5) {
self.window?.makeKeyAndVisible()
}
} else {
let storyboard = UIStoryboard(name: "Auth", bundle: nil)
let rootViewController = storyboard.instantiateViewController(identifier: "Welcome")
window?.rootViewController = rootViewController
UIView.animate(withDuration: 0.5) {
self.window?.makeKeyAndVisible()
}
}
return true
}
}
ViewController.swift
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
print("ViewController presented.")
}
}
WelcomeViewController.swift
import UIKit
class WelcomeViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
print("WelcomeViewController presented.")
}
}
Expectations
The app is freshly installed on the Simulator, and therefore there should be no currentUser (and upon debugging, proves to be true). Therefore, I'd expect to see this as the output in the Console:
WelcomeViewController presented.
Instead, this shows up in the Console:
WelcomeViewController presented.
ViewController presented.
Attempts
So far, I thought that the problem might have arised from the fact that Main storyboard is the default Storyboard set since the creation of the project. Therefore, I tried unchecking the 'Initial View Controller' checkbox for the two View Controllers. Just like I'd expected, the following appears instead:
WelcomeViewController presented.
2020-06-20 11:39:38.724658+0800 AppName[11439:237509] [WindowScene] Failed to instantiate the default view controller for UIMainStoryboardFile 'Main' - perhaps the designated entry point is not set?
What should I do to make sure that ViewController doesn't appear and replace WelcomeViewController? All help is appreciated, thanks!
why not set WelcomeViewController as your 'Initial View Controller' and replace its viewDidLoad code with AppDelegate file then:
class WelcomeViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
print("WelcomeViewController presented.")
let user = Auth.auth().currentUser
if user != nil {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let rootViewController = storyboard.instantiateViewController(identifier: "View")
window?.rootViewController = rootViewController
UIView.animate(withDuration: 0.5) {
self.window?.makeKeyAndVisible()
}
}
}
}
you can also look this if you want to set the view controller from AppDelegate:
EDIT:
you can use a UINavigationController as your initial view controller as indicated in this answer:
set initial viewcontroller in appdelegate - swift
then you just need to set the correct view controller here:
navigationController.viewControllers = [rootViewController]
self.window?.rootViewController = navigationController
Solution
I managed to find the solution to the issue that I'm facing here. There are two mistakes above in the code, and I'll explain it here:
Mistake 1: Code should be in SceneDelegate instead of AppDelegate (iOS 13)
Xcode 11, along with the release of iOS 13, introduced SceneDelegate.swift. I was supposed to put my code above in SceneDelegate.swift instead of AppDelegate.swift; because I didn't do this, the code hadn't worked as I'd hoped for my Simulator, which was running iOS 13.5.
Here's my rectified code in SceneDelegate.swift:
import UIKit
import Firebase
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 }
// Performs conditional navigation on the condition of currentUser's status.
let user = Auth.auth().currentUser
if user != nil {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let rootViewController = storyboard.instantiateViewController(identifier: "View")
window?.rootViewController = rootViewController
UIView.animate(withDuration: 0.5) {
self.window?.makeKeyAndVisible()
}
} else {
let storyboard = UIStoryboard(name: "Auth", bundle: nil)
let rootViewController = storyboard.instantiateViewController(identifier: "Welcome")
window?.rootViewController = rootViewController
UIView.animate(withDuration: 0.5) {
self.window?.makeKeyAndVisible()
}
}
}
}
Mistake 2 (Important!): Definition of new UIWindow
In my code above, there was a faulty line which caused the creation of a new UIWindow:
self.window = UIWindow(frame: UIScreen.main.bounds)
By removing this line of code, the problem was resolved. Here's the rectified code:
let user = Auth.auth().currentUser
if user != nil {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let rootViewController = storyboard.instantiateViewController(identifier: "View")
window?.rootViewController = rootViewController
UIView.animate(withDuration: 0.5) {
self.window?.makeKeyAndVisible()
}
} else {
let storyboard = UIStoryboard(name: "Auth", bundle: nil)
let rootViewController = storyboard.instantiateViewController(identifier: "Welcome")
window?.rootViewController = rootViewController
UIView.animate(withDuration: 0.5) {
self.window?.makeKeyAndVisible()
}
}
I hope this helps anyone else facing this issue!

Limit Navigation Controller to a set of UIViewControllers

Is it possible to limit the NavigationController to a certain set of UIViewControllers. See the image, I want the navigation controller, but only for the login/create user session. Once logged in, I obviously don't want the user to be able to go back (except log out). How can I accomplish that? I can't figure it out.
Go to Storyboard -> select NavigationController -> Attributes Inspector -> uncheck "Shows Navigation Bar" property
Then select relationship between Login/SignUp And TabBarController, and delete it.
Once login you can set your TabBarController(or any related controller) as the rootViewController. And when app launches you can check it in your AppDelegate.swift file like this,
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
FirebaseApp.configure()
let storyboard = UIStoryboard(name: "Main", bundle: nil)
if Auth.auth().currentUser != nil {
let tabBarController = storyboard.instantiateViewController(withIdentifier: "tabBarcontroller") as! TabBarController
self.window?.rootViewController = tabBarController
self.window?.makeKeyAndVisible()
}
else
{
let loginNavController = storyboard.instantiateViewController(withIdentifier: "LoginNavController") as! UINavigationController
self.window?.rootViewController = loginNavController
self.window?.makeKeyAndVisible()
}
return true
}
You could change the stack of your navigation controller when needed, e.g:
func logIn() {
//Delete all presented view controllers up to a point
navigationController.setViewControllers([], animated: false)
//Create new view controller
let viewController = ....
navigationController.pushViewController(viewController, animated: true)
}
Take a look at setViewControllers, that may give you some idea.
If you want to leave previous view controllers in stack, and just forbid user to pop to them, then it may be the best solution to subclass UINavigationController and override func popViewController(animated: Bool) -> UIViewController?

LaunchScreen.storyboard not opening Main.storyboard Navigation Controller

I have just started to develop a new application using Swift (newbie). I have
LaunchScreen.storyboard with just a image of my splash screen
I have a Main.storyboard with a Navigation Controller connected to two segues, Home and Registration.
In the ViewController.swift, inside the viewDidLoad I am deciding which segue to call
My Main.Storyboard does not have a rootViewController, I need to decide which viewController to display at run time.
if (Settings.hasRegistrationCompleted()) {
performSegue(withIdentifier: "Home", sender: nil)
} else {
performSegue(withIdentifier: "Registration", sender: nil)
}
My questions
I put a breakpoint on the first line if (Settings.has and the breakpoint never reaches here
LaunchScreen lasts only for 2 seconds (tested on my Simulator) how do I increase it
EDIT
I have Main set as the Main Interface on my project. I did a Clean build and tried again, did not work.
Also below is the Main.Storyboard
in here two things you need to identify
first
check your storyboard name Main.storyboard are attached properly in your Target -> general -> Deployment Info -> main Interface, for e.g like this
second
check your Intial VC connected with navigation Controller and ensure your Initial VC as Root controller
update answer
initially set Stroryboard ID and for each VC
there after change the Root controller in appdelegate
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
self.window = UIWindow(frame: UIScreen.main.bounds)
// Override point for customization after application launch.
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let rootViewController: UIViewController?
if (Settings.hasRegistrationCompleted()) {
rootViewController = storyboard.instantiateViewController(withIdentifier: "HomeVC")
}else
{
rootViewController = storyboard.instantiateViewController(withIdentifier: "RegistrationVC")
}
let navigation = UINavigationController(rootViewController: rootViewController!)
self.window?.rootViewController = navigation
self.window?.makeKeyAndVisible()
return true
}
There you must be have One View-Controller as a RootVieController of your NavigationController and it must be initialize with arow like following screenshot. From The Login you need to segue to two View Controller.
Like Following
So you need to check in LoginViewController that you are already logged in or not. Or you can segue to register
You can also Subclass UINavigationController and set it to the storyboard. Remove the segues (you don't need them)
Then
class ViewController: UINavigationController {
override func viewDidLoad() {
super.viewDidLoad()
if (Settings.hasRegistrationCompleted()) {
let homeVC = self.storyboard?.instantiateViewController(withIdentifier: "HomeVC")
self.setViewControllers([homeVC!], animated: false)
}
else {
let regVC = self.storyboard?.instantiateViewController(withIdentifier: "RegistrationVC")
self.setViewControllers([regVC!], animated: false)
}
}
}

Error trying to programmatically add a controller file to a navigation controller without creating storyboard

I am trying to push a view controller onto a navigation controller without designing a storyboard. Is this possible as I am new to Swift? I created my navigation controller in appdelegate.swift file:
let viewController: UIViewController = ViewController(nibName: nil, bundle: nil)
viewController.view.backgroundColor = UIColor.white
let navController: UINavigationController = UINavigationController(rootViewController: viewController)
self.window = UIWindow(frame: UIScreen.main.bounds)
self.window?.backgroundColor = UIColor.darkGray
self.window?.rootViewController = navController
self.window!.makeKeyAndVisible()
And now in ViewController.swift file, when the user clicks the button below is the file when I try to add the signInController:
func SignIn(sender: UIButton!) {
print("Im, here")
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let controller = storyboard.instantiateViewController(withIdentifier: "signInController") as! signInController
self.navigationController?.pushViewController(controller, animated: true)
}
Below is my Error:
Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'Storyboard (<UIStoryboard: 0x608000269600>) doesn't contain a view controller with identifier 'signInController''
Question:
Do I have to create a storyboard and go to the inspector Id to add the storyboard Id? But what if I wanted to accomplish this without creating a storyboard file?
If you are not using a storyboard then you shouldn't attempt to create your sign-in view controller from a storyboard. Change the code to create the view controller directly:
func SignIn(sender: UIButton!) {
print("I'm, here")
let controller = signInController()
self.navigationController?.pushViewController(controller, animated: true)
}
BTW - you need to rename everything to follow standard naming conventions. Classnames should start with uppercase letters. All method, variable, and parameters names should start with lowercase letters.
Well you want to create a viewController without using the storyboard and you're still trying to use the storyboard in your code.
Try something like this instead:
var window: UIWindow?
var navigationController: UINavigationController?
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
// Override point for customization after application launch.
self.window = UIWindow(frame: UIScreen.main.bounds)
// Override point for customization after application launch.
self.window!.backgroundColor = UIColor.white
self.window!.makeKeyAndVisible()
let myViewController: YourNewViewController = YourNewViewController()
self.navigationController = UINavigationController(rootViewController: myViewController!)
self.window!.rootViewController = self.navigationController
return true
}

Going to another ViewController in closure does not work in swift

I am trying to go to another ViewController (that is part of a TabBarController) after some networking code has been executed (using AlamoFire, inside a closure). The name of my current VC is StartingVC. I want to go to another VC after my closure has validated some code. In my example, the code is met, I am 100% sure because I have a println() that indicates so, but it does not take me to the other VC.
My closure code is the following:
Alamofire.request(.POST, "http://localhost/test.php", parameters: dataToSend).responseJSON{
(request, response, data, error) in
self.showViewController(HomeViewController(), sender: nil)
I am aware that I need to use self inside a closure to refer to the actual VC I am working in. But the code does not work.
Do you know how to present/push/goto another VC using code? I am not using any Storyboard links to my HomeViewController because I had some problems before using a segue both programmatically and in the storyboard, that is why I decided to go for the code approach.
Thank you for you help in advance
Cheers!
If you do not want to set navigation controller with storyboard and unable to start with there, your appdelegate should be like:
class AppDelegate: UIResponder, UIApplicationDelegate {
//you must mark the window and navigationController variables as optional
var window: UIWindow?
var navigationController: UINavigationController?
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: NSDictionary?) -> Bool {
navigationController = UINavigationController()
var homeViewController: UIViewController = UIViewController()
self.navigationController.pushViewController(homeViewController, animated: false)
self.window = UIWindow(frame: UIScreen.mainScreen().bounds)
self.window.rootViewController = navigationController
self.window.backgroundColor = UIColor.whiteColor()
self.window.makeKeyAndVisible()
return true
}
}
Other than that on view controller you can push view controller with storyboard object as:
var firstViewController = self.storyboard!.instantiateViewControllerWithIdentifier("firstViewController") as! firstViewController
self.navigationController?.pushViewController(firstViewController, animated: false);
Present view controller:
let vc = ViewController() //change this to your class name
self.presentViewController(vc, animated: true, completion: nil)

Resources