Swift: Two View Controllers presented - ios

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!

Related

how to use navigation controller programaticaly?

i have a button in viewcontroller which navigates to editPage viewcontrooler
i want to use pushNavigation
i did below code in button action method
#IBAction func editBtn(_ sender: Any) {
let storyBoard: UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
let newViewController = storyBoard.instantiateViewController(withIdentifier: "editProfile") as! editProfile
let navigation = UINavigationController(rootViewController: newViewController)
self.navigationController?.pushViewController(navigation, animated: true)
}
but when i click on button it dosent navigate
i dont have navigation controler in storyboard.......
i dont want to present view controler
Looks like you are setting your viewController as initail View Controller from storyboard but do not embed this in navigation controller , either embed your viewController in navigationController and set this navigation controller as initialViewController or follow this approach as you don't want to do it from storyboard:
In SceneDelegate , write :
var window: UIWindow?
and then in this method :
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 }
//Setup user state
if let vc = UIStoryboard.init(name: "YOUR_STORYBOARD_NAME", bundle: nil).instantiateViewController(withIdentifier: "VIEW_CONTROLLER_IDENTIFIER") as? ViewController {
let nav = UINavigationController(rootViewController: vc)
window?.rootViewController = nav
window?.makeKeyAndVisible()
}
}
for IOS < 13, in AppDelegate.swift :
var window : UIWindow?
//MARK:- DID FINISH LAUNCHING WITH OPTIONS : CALLED EVERYTIME WHEN APPLICATION LAUNCHES
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
if let vc = UIStoryboard.init(name: "YOUR_STORYBOARD_NAME", bundle: nil).instantiateViewController(withIdentifier: "VIEW_CONTROLLER_IDENTIFIER") as? ViewController {
let nav = UINavigationController(rootViewController: vc)
window?.rootViewController = nav
window?.makeKeyAndVisible()
}
return true
}

Setting root view controller upon launch, depending on user auth state

This is the storyboard layout I have right now
So what I need is to check if the user is already authenticated when the app launches.
If he is not logged in set the root view controller to the navigation controller with the login and register forms.
If he is logged in set it to the tab bar view controller.
I tried a lot of different solutions but none of them worked. It just kept setting the view controller to the one marked with "Is Initial View Controller".
This is the code I tried in the AppDelegate:
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
FirebaseApp.configure()
self.window = UIWindow(frame: UIScreen.main.bounds)
let storyboard = UIStoryboard.init(name: "Main", bundle: nil)
var viewController: UIViewController
if isAuthenticated() {
viewController = storyboard.instantiateViewController(withIdentifier: Constants.Storyboards.homeViewController) as! UITabBarController
} else {
viewController = storyboard.instantiateViewController(withIdentifier: Constants.Storyboards.authViewController) as! UINavigationController
}
self.window?.rootViewController = viewController
self.window?.makeKeyAndVisible()
return true
}
I can do this by pressing a button easily, but I want it to happen without the user needing to do something.
EDIT: Thanks to #LukaCefarin and #Francesco Deliro I managed to find out that the problem was. I was using XCode 11 and the rootViewController had to be set in the SceneDelegate.swift
This is what my code in the SceneDelegate.swift looks like for anyone who has a similar issue:
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
guard let _ = (scene as? UIWindowScene) else { return }
self.window = UIWindow(windowScene: scene as! UIWindowScene)
let storyboard = UIStoryboard.init(name: "Main", bundle: nil)
var viewController: UIViewController;
if isAuthenticated() {
viewController = storyboard.instantiateViewController(withIdentifier: "HomeVC")
} else {
viewController = storyboard.instantiateViewController(withIdentifier: "AuthVC")
}
self.window?.rootViewController = viewController
self.window?.makeKeyAndVisible()
}
EDIT:
This solution and setting the window rootViewController in the AppDelegate work for versions prior to Xcode11 and iOS13. As suggested in the comments by Luka Cefarin, if you are using Xcode11 and iOS13 you have to set the window rootViewController in the SceneDelegate.swift file.
You have to remove the Main Interface and to uncheck the initial view controller in the storyboard:
Before
After

Programmatically connecting Storyboard w/ ViewController

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.

How to programmatically add a second view in swift 3

Im trying to switch to a second view from a button on the first view, and do this programmatically (not using the traditional way of switching between views using a segue on the storyboard)
My current app delegate:
import UIKit
#UIApplicationMain
public class AppDelegate: UIResponder, UIApplicationDelegate {
public var window: UIWindow?
public func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
window = UIWindow(frame: UIScreen.main.bounds)
window?.makeKeyAndVisible()
window?.rootViewController = UINavigationController(rootViewController: ViewController())
return true
}
Button function in first view controller that when pressed should just switch to new view:
func signupAction (sender: UIButton!){
}
I already have the new viewcontroller file created:
class Signup: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = UIColor.cyan
}
}
Basically, I'm trying to achieve the button in the first viewcontroller transitions to this signup viewcontroller.
If you are in a UINavigationController you could push the Signup view controller to the navigation stack this way:
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let signUpViewController = storyboard.instantiateViewController(withIdentifier: "signUpViewController") as! Signup
navigationController?.pushViewController(signUpViewController, animated: true)
If you are not in UINavigationController and want to present it, you can do it this way:
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let signUpViewController = storyboard.instantiateViewController(withIdentifier: "signUpViewController") as! Signup
self.present(signUpViewController, animated: true, completion: nil)
Note: Don't forget to set the identifier of your view controller in the storyboard.

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
}

Resources