'Could not find a storyboard named 'MainTabController' in bundle NSBundle - swift

the error I'm receiving that I can't seem to fix is
Terminating app due to uncaught exception
'NSInvalidArgumentException', reason: 'Could not find a storyboard
named 'MainTabController' in bundle NSBundle
the app will build and the login screen will display but crashes immediately after with the error stated above.
I have tried all of the following from other post similar to this and have had no luck.
Removing the reference to the storyboard in the info.plist file. When I do this the app does start but I get a black screen as it doesn't load the storyboard.
Fiddle with the Target Membership Main.storyboard.
Remove the storyboard from the project, clean, run and then adding the storyboard back again.
Uninstall Xcode, reinstall Xcode.
Deleting the Derived Data folder.
the problem appears to be with my presentMainScreen() method
func presentMainScreen(){
let mainstoryboard = UIStoryboard(name: "MainTabController", bundle: nil)
let mainTabController = mainstoryboard.instantiateViewController(withIdentifier: "MainTabController") as! MainTabController
mainTabController.selectedViewController = mainTabController.viewControllers?[1]
//let storyboard:UIStoryboard = UIStoryboard(name:"Main", bundle:nil)
//let loggedInVC:LoggedInVC = storyboard.instantiateViewController(withIdentifier: "LoggedInVC") as! LoggedInVC
//self.present(loggedInVC, animated: true, completion: nil)
}
if I comment out the mainTabController lines the app will work perfectly, also if I uncomment the loggedInVC lines and with the mainTabController lines commented out it works perfectly as well.
any suggestions are greatly appreciated.
below is my entire ViewController.swift code and a screenshot of my workspace
workspace
ViewController.swift
import UIKit
import FirebaseAuth
class ViewController: UIViewController {
#IBOutlet weak var emailTextField: UITextField!
#IBOutlet weak var passwordTextField: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewDidAppear(_ animated: Bool) {
if Auth.auth().currentUser != nil {
print("success")
self.presentMainScreen()
}
}
#IBAction func creatAccountTapped(_ sender: Any) {
if let email = emailTextField.text, let password = passwordTextField.text {
Auth.auth().createUser(withEmail: email, password: password, completion:{ user, error in
if let firebaseError = error {
print(firebaseError.localizedDescription)
return
}
print("success")
self.presentMainScreen()
})
}
}
#IBAction func loginButtonTapped(_ sender: Any) {
if let email = emailTextField.text, let password = passwordTextField.text {
Auth.auth().signIn(withEmail: email, password: password, completion: { (user, error) in
if let firebaseError = error {
print(firebaseError.localizedDescription)
return
}
print("success")
self.presentMainScreen()
})
}
}
func presentMainScreen(){
let mainstoryboard = UIStoryboard(name: "MainTabController", bundle: nil)
let mainTabController = mainstoryboard.instantiateViewController(withIdentifier: "MainTabController") as! MainTabController
mainTabController.selectedViewController = mainTabController.viewControllers?[1]
//let storyboard:UIStoryboard = UIStoryboard(name:"Main", bundle:nil)
//let loggedInVC:LoggedInVC = storyboard.instantiateViewController(withIdentifier: "LoggedInVC") as! LoggedInVC
//self.present(loggedInVC, animated: true, completion: nil)
}
}

What is the name of your storyboard? Is it MainTabController.storyboard or Main.storyboard?
You are trying to load a storyboard named "MainTabController":
let mainstoryboard = UIStoryboard(name: "MainTabController", bundle: nil)
But previously you called it Main.storyboard:
Fiddle with the Target Membership Main.storyboard.
Also if the main tab controller is in the same storyboard as your login view controller then you can try to use this:
let mainTabController = self.storyboard.instantiateViewController(withIdentifier: "MainTabController")

Your story board is named as Main.storyboard. MainTabController is the controller in the Main storyboard.
let mainstoryboard = UIStoryboard(name: "Main", bundle: nil)
let mainTabController = mainstoryboard.instantiateViewController(withIdentifier: "MainTabController") as! MainTabController
mainTabController.selectedViewController = mainTabController.viewControllers?[1]

let mainstoryboard = UIStoryboard(name: "MainTabController", bundle: nil)
Your storyboard is named "Main"

Related

Swift segue not doing what it is supposed to do

I am having a little problem with a segue in my application.
When I try to push a segue so that it has a navbar it shows up correctly in the storyboard but not when I try it on my iPhone.
This is an overview of a couple of view controllers where my problem lays.
This is supposed to be the segue, so you can see that it has a navigation bar and is correctly positioned on the storyboard.
This is the view on the iPhone. No navigation bar or nothing. I tried everything but can't seem to find a solution to this problem.
Does anyone what the problem could be?
A little extra side information:
I don't know if may have something to do with the problem but the navigation view controller is not always present only when the user is logged in the app. this is decided on a log in screen if the user is not logged in the user will see a normal login screen. Else it will go to navigation view controller with a view did appear function and self.present.
Here is the code that handles that action.
// Sees if the user is logged, If yes --> go to the account detail page else go to the account view.
override func viewDidAppear(_ animated: Bool) {
super.viewWillAppear(animated)
if let data = UserDefaults.standard.data(forKey: "User") {
do {
// Create JSON Decoder
let decoder = JSONDecoder()
// Decode Note
_ = try decoder.decode(User.self, from: data)
guard let loginVC = UIStoryboard.init(name: "Main", bundle: Bundle.main).instantiateViewController(withIdentifier:
"AccountDetailViewController") as? AccountDetailViewController else { return }
loginVC.modalPresentationStyle = .overCurrentContext
self.present(loginVC, animated: false, completion: {})
} catch {
print("Unable to Decode Note (\(error))")
}
}
}
You should push view controller instead of present. Please check this article to know more about Pushing, Popping, Presenting, & Dismissing ViewControllers
You can push AccountDetailViewController without segues. And you don't need to call performSegue(withIdentifier:) into tableView's didSelect function.
Remove segue from Interface Builder
let navigator = UINavigationController()
guard let loginVC = UIStoryboard(name: "Main", bundle: Bundle.main).instantiateViewController(withIdentifier:
"AccountDetailViewController") as? AccountDetailViewController else { return }
loginVC.modalPresentationStyle = .overCurrentContext
navigator.pushViewController(loginVC, animated: true)
After succesful login, you are presenting AccountDetailViewController without adding it in a navigation controller. I would suggest you to use these extensions that i created.
extension UIViewController {
func pushVC(vcName : String) {
let vc = UIStoryboard.init(name: "Main", bundle: Bundle.main).instantiateViewController(withIdentifier: vcName)
vc.hidesBottomBarWhenPushed = true
self.navigationController?.pushViewController(vc, animated: true)
}
func pushVC(storyboardName : String, vcName : String) {
let vc = UIStoryboard.init(name: storyboardName, bundle: Bundle.main).instantiateViewController(withIdentifier: vcName)
vc.hidesBottomBarWhenPushed = true
self.navigationController?.pushViewController(vc, animated: true)
}
func popVC() {
self.navigationController?.popViewController(animated: true)
}
func makeRootVC(storyBoardName : String, vcName : String) {
let appDelegate = UIApplication.shared.delegate as! AppDelegate
let vc = UIStoryboard(name: storyBoardName, bundle: Bundle.main).instantiateViewController(withIdentifier: vcName)
let nav = UINavigationController(rootViewController: vc)
nav.navigationBar.isHidden = true
appDelegate.window?.rootViewController = nav // If using XCode 11 and above, copy var window : UIWindow? in your appDelegate file
let options: UIView.AnimationOptions = .transitionCrossDissolve
let duration: TimeInterval = 0.6
UIView.transition(with: appDelegate.window!, duration: duration, options: options, animations: {}, completion: nil)
}
}
Now in your case, when a user logs in, you should change your root view controller to AccountDetailViewController. So first, copy paste the above extension anywhere in your file and then use it like this:
// Sees if the user is logged, If yes --> go to the account detail page else go to the account view.
override func viewDidAppear(_ animated: Bool) {
super.viewWillAppear(animated)
if let data = UserDefaults.standard.data(forKey: "User") {
do {
// Create JSON Decoder
let decoder = JSONDecoder()
// Decode Note
_ = try decoder.decode(User.self, from: data)
self.makeRootVC(storyBoardName : "Main", vcName :"AccountDetailViewController")
} catch {
print("Unable to Decode Note (\(error))")
}
}
}

Sign Out button to Present Login View Controller - Swift 5

Hi I'm kind of new to Swift and I can't figure this out. I am trying to create a sign out button that would take user to the login page. I used the following two methods but the first one doesn't do anything and the second one is throwing Fatal error: Unexpectedly found nil while implicitly unwrapping an Optional value and it refers to the code with the customButton:
//this one doesn't do anything
#objc func SignOut(){
let vc = self.storyboard?.instantiateViewController(identifier: "LoginViewController") as! LoginViewController
let appDelegate = UIApplication.shared.delegate
appDelegate?.window??.rootViewController = vc
}
//this one is throwing an error
let vc = CustomViewController()
self.present(vc, animated: true, completion: nil)
//the Fatal error refers to this code
override func viewDidLoad() {
super.viewDidLoad()
self.customButton.addTarget(self, action: #selector(customButtonPressed), for: .touchUpInside)
}
Also, I was wondering if AppDelegate is the right approach or if I should use SceneDelegate. Any help would be greatly appreciated.
Try to do this
#objc func SignOut(){
let vc = self.storyboard?.instantiateViewController(identifier: "LoginViewController") as! LoginViewController
self.view.window?.rootViewController = vc
}
let vc = self.storyboard?.instantiateViewController(identifier: "LoginViewController") as! LoginViewController
vc.modalPresentationStyle = .fullScreen
self.present(vc, animated: true, completion: nil)
Please make sure the identifier is matching the one inside storyboard for this viewController

How do you present a VC from Xib in Swift?

I am trying to present a VC from a Xib file but it's resulting in
Fatal error: Unexpectedly found nil while unwrapping an Optional value
#IBAction func viewCartBtnTapped(_ sender: Any) {
let cart = storyboard?.instantiateViewController(withIdentifier: "CartVC") as? CartVC
present(cart!, animated: true, completion: nil)
}
try below code, YourStoryBoradName should be the name of storyboard without ".storyboard" extension :
let storyBoard : UIStoryboard = UIStoryboard(name: "YourStoryBoradName", bundle:nil)
let viewController = storyBoard.instantiateViewController(withIdentifier: "CartVC")as! CartVC
self.present(viewController, animated: true, completion: nil)

Whose view is not in the window hierarchy only in First Launch

I have an Onboarding screen that I'm showing to the new users when they first open the app.
In my appDelegate I check whether is the first launch or not.
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
window = UIWindow(frame: UIScreen.main.bounds)
let storyboard = UIStoryboard(name: "Main", bundle: nil)
var initialViewController = storyboard.instantiateViewController(withIdentifier: "OnBoarding")
let userDefaults = UserDefaults.standard
if userDefaults.bool(forKey: "onBoardingComplete") {
initialViewController = storyboard.instantiateViewController(withIdentifier: "MainApp")
}
window?.rootViewController = initialViewController
window?.makeKeyAndVisible()
}
Also I have a collectionViewCell that I have some buttons and when I click them I get an Alert with informations.
Example of one button
#IBAction func guide3Btn(_ sender: Any) {
let infoVC = infoService.info(title: "Title", body: "Information")
self.window?.rootViewController?.present(infoVC, animated: true, completion: nil)
}
When the user first launches the app if he clicks the info button gets this:
Warning: Attempt to present <MyApp.InfoViewController: 0x7f91db45cfb0> on <MyApp.OnbBoardViewController: 0x7f91db506af0> whose view is not in the window hierarchy!
If the user reopens the app everything is ok. I know that when we have first launch we have onBoarding as root controller but I can't understand how to fix this.
Update
This is the infoService class. I use a new storyboard to create the alert.
class InfoService {
func info(title: String, body: String) -> InfoViewController {
let storyboard = UIStoryboard(name: "InfoStoryboard", bundle: .main)
let infoVC = storyboard.instantiateViewController(withIdentifier: "InfoVC") as! InfoViewController
infoVC.infoBody = body
infoVC.infoTitle = title
return infoVC
}
}
You can try add your storyboard instantiate code blocks to main thread using DispatchQueue.main.async like below:
I solved almost all of my whose view is not in the window hierarchy! problems.
DispatchQueue.main.async {
let infoVC = storyboard.instantiateViewController(withIdentifier: "InfoVC") as! InfoViewController
infoVC.infoBody = body
infoVC.infoTitle = title
}
return infoVC
Referenced from : https://stackoverflow.com/a/45126338/4442254

Using present in UIViewController properly

I encounter the problem that I cannot present a dynamic UIViewController in my class using "self". It tells me "Value of type '(LoginScreenVC) -> () -> (LoginScreenVC)' has no member 'view'".
It would work using a closure like e.g. if let loginScreen = UIStoryBoard ..., but since the UIViewController to switch to is dynamic, I cannot cast it to a specific UIViewController.
is there any other way to present the ViewController?
This is my code:
SWIFT 4.2 / XCode 10.1
import UIKit
class LoginScreenVC: UIViewController {
let myTokenHandler = TokenHandler()
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func loginButton(_ sender: Any) {
print("Login button pressed")
let usernameInputField = self.view.viewWithTag(6548) as! UITextField
let passwordInputField = self.view.viewWithTag(6549) as! UITextField
userInput = usernameInputField.text!
passInput = passwordInputField.text!
// call completion handler
requestToken(success: handlerBlock)
}
// completion handler step 1: request token and get redirect string to switch screen
func requestToken(success: (String) -> Void) {
let requestResult = myTokenHandler.requestToken(password: passInput, username: userInput)
success(requestResult)
}
// completion handler step 2: use redirect string to switch screen
let handlerBlock: (String) -> Void = { redirect in
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let loginScreen = storyboard.instantiateViewController(withIdentifier: redirect)
self.present(loginScreen, animated: true, completion: nil) //Value of type '(LoginScreenVC) -> () -> (LoginScreenVC)' has no member 'view'
}
}
You are saying:
let handlerBlock: (String) -> Void = { redirect in
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let loginScreen = storyboard.instantiateViewController(withIdentifier: redirect)
self.present(loginScreen, animated: true, completion: nil)
}
The problem is that you are using the term self in a context where there is no self. (Well, there is a self, but it isn't what you think.) present is a UIViewController instance method, so self needs to be a UIViewController instance; and in this context, it isn't.
I can think of half a dozen ways to express the thing you're trying to express, but the simplest would probably be to rewrite that as:
func handlerBlock(_ redirect:String) -> Void {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let loginScreen = storyboard.instantiateViewController(withIdentifier: redirect)
self.present(loginScreen, animated: true, completion: nil)
}
Now handlerBlock is an instance method, and self is meaningful — it is the instance, which is just what you want. The rest of your code is unchanged, because the bare name handlerBlock in the expression requestToken(success: handlerBlock) is a function name, just as before.