I am building a renting app with firebase. I have a screen 'WelcomeViewController'.Now the functionality of this screen is that when app starts, it checks wether the user is logged in or not. If it is, then it perform storyboard instantiate to HomeScreenViewController and if user is logged out, then it should instantiate storyboard to LoginViewController. Now, the first part is running fine and storyboard does instantiate to HomeScreenViewController but during the second part when storyboard.instantiate is run, it crashes with error "found nil while force unwrapping optional". I have crosschecked all the storyboard ID's and everything. I cant seem to figure out.
import UIKit
import Firebase
class WelcomeViewController: UIViewController {
var docRef:DocumentReference!
var Uid:String?
var homeVC:UITabBarController? = nil
override func viewDidLoad() {
super.viewDidLoad()
if Auth.auth().currentUser != nil {
//User is signed in
print("User is logged in")
docRef = Firestore.firestore().document("Users/\(Auth.auth().currentUser!.uid)")
docRef.getDocument { (docSnapshot, error) in
guard let docSnapshot = docSnapshot, docSnapshot.exists else { print("Error Founddddd");return}
let myData = docSnapshot.data()
let type = myData?["Role"] as? String ?? ""
print(type)
if type == "Owner" {
self.homeVC = self.storyboard?.instantiateViewController(identifier: "OwnerHome") as? OwnerHomeTabBarViewController
self.view.window?.rootViewController = self.homeVC
self.view.window?.makeKeyAndVisible()
}
else {
self.homeVC = self.storyboard?.instantiateViewController(identifier: "TouristHome") as? TouristHomeTabBarViewController
self.view.window?.rootViewController = self.homeVC
self.view.window?.makeKeyAndVisible()
}
}
}
else {
print("User is loggedout")
//Send User to Login/Signup Screen
let Storyboard = UIStoryboard(name: "Main", bundle: Bundle.main)
let LoginVC = Storyboard.instantiateViewController(identifier: "loginScreen") as! SignInViewController
self.view.window!.rootViewController = LoginVC //Error is coming here
self.view.window?.makeKeyAndVisible()
}
}
}
I think you need to remove ! from self.view.window! and replace with ?.
Have you set storyboard ID in your Storyboard for each ViewController ?
Related
Tell me, please, what am I doing wrong?
There are 3 View:
The user selects his gender
The user enters his weight
Main View Application
After the user has written this data, he gets to the main screen of the application. I want to make the user choose his gender and weight only at the very first launch of the application. To do this, I created a UserSettings model to store the entered values there:
final class UserSettings {
enum SettingsKeys: String {
case userSex
case userWeight
}
static var userSex: String! {
get {
return UserDefaults.standard.string(forKey: SettingsKeys.userSex.rawValue)
}
set {
let defaults = UserDefaults.standard
let key = SettingsKeys.userSex.rawValue
if let sex = newValue {
print("Пол \(sex) добавлен в \(key)")
defaults.set(sex, forKey: key)
} else {
defaults.removeObject(forKey: key)
}
}
}
static var userWeight: String! {
get {
return UserDefaults.standard.string(forKey: SettingsKeys.userWeight.rawValue)
}
set {
let defaults = UserDefaults.standard
let key = SettingsKeys.userWeight.rawValue
if let weight = newValue {
print("Вес \(weight) добавлен в \(key)")
defaults.set(weight, forKey: key)
} else {
defaults.removeObject(forKey: key)
}
}
}
}
In AppDelegate in function didFinishLaunchingWithOptions launchOptions wrote:
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
window = UIWindow(frame: UIScreen.main.bounds)
let defaults = UserDefaults.standard
let skipPageSex = defaults.bool(forKey: UserSettings.userSex)
let skipPageWeight = defaults.bool(forKey: UserSettings.userWeight)
if skipPageSex && skipPageWeight == true {
let mainStoryboard: UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
let nextView: MainViewController = mainStoryboard.instantiateViewController(identifier: "MainViewController") as! MainViewController
window?.rootViewController = nextView
self.window?.makeKeyAndVisible()
} else {
let firstStoryboard: UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
let firstView: WelcomeViewController = firstStoryboard.instantiateViewController(identifier: "welcomeVC") as! WelcomeViewController
window?.rootViewController = firstView
self.window?.makeKeyAndVisible()
}
return true
}
In the Sex select View in the button action, I wrote:
#IBAction func manSelected(_ sender: UIButton) {
sender.setTitle("Male", for: .normal)
UserSettings.userSex = sender.currentTitle
let defaults = UserDefaults.standard
defaults.setValue(true, forKey: UserSettings.userSex)
defaults.synchronize()
}
And the same for the button with the female choice, only sender.setTitle("Female", for: .normal)
I tried removing UIMainStoryboardFile and UIApplicationSceneManifest in Info.plist and in this case I get just a black screen without errors. In the Storyboard ID I use the same value as in AppDelegate. Please, help
Try something like this. Initial with a TempViewController. Depend on your data, it will segue perform show Gender Picker or go directly to Main. Remember to check the Is Initial View Controller for root Navigation Controller.
I am trying to implement a "always logged in" function in to my app. The problem is that if I restart my app, it crashes. This is what I did:
set Userdefault:
#objc func loginButtonTapped(_ sender: Any) {
let email = self.emailTextField.text!.trimmingCharacters(in: .whitespacesAndNewlines)
let password = self.passwordTextField.text!.trimmingCharacters(in: .whitespacesAndNewlines)
// start button animation
loginButton.startAnimation()
let qualityOfServiceClass = DispatchQoS.QoSClass.background
let backgorundQueue = DispatchQueue.global(qos: qualityOfServiceClass)
backgorundQueue.async {
// check if account details correct
Auth.auth().signIn(withEmail: email, password: password) { (result, error) in
if error != nil {
DispatchQueue.main.async {
// error -> stop animation
self.loginButton.stopAnimation(animationStyle: .shake, revertAfterDelay: 0) {
self.errorLabel.text = error!.localizedDescription
self.errorLabel.alpha = 1
}
}
}else {
// correct acount details -> login
DispatchQueue.main.async {
UserDefaults.standard.set(true, forKey: "isLoggedIn")
UserDefaults.standard.synchronize()
// transition to home ViewController
self.transitionToHome()
}
}
}
}
}
checking UserDefault:
class MainNavigationControllerViewController: UINavigationController {
override func viewDidLoad() {
super.viewDidLoad()
if isLoggedIn() {
let homeController = MainViewController()
viewControllers = [homeController]
}
}
fileprivate func isLoggedIn() -> Bool {
return UserDefaults.standard.bool(forKey: "isLoggedIn")
}
}
The user logs in via Firebase and all the data is stored in Cloud Firestore.
Error
cell.customWishlistTapCallback = {
let heroID = "wishlistImageIDX\(indexPath)"
cell.theView.heroID = heroID
let addButtonHeroID = "addWishButtonID"
self.addButton.heroID = addButtonHeroID
// track selected index
self.currentWishListIDX = indexPath.item
let vc = self.storyboard?.instantiateViewController(withIdentifier: "WishlistVC") as? WishlistViewController
vc?.wishList = self.dataSourceArray[self.currentWishListIDX]
// pass drop down options
vc?.theDropDownOptions = self.dropDownButton.dropView.dropDownOptions
vc?.theDropDownImageOptions = self.dropDownButton.dropView.dropDownListImages
// pass current wishlist index
vc?.currentWishListIDX = self.currentWishListIDX
// pass the data array
vc?.dataSourceArray = self.dataSourceArray
// set Hero ID for transition
vc?.wishlistImage.heroID = heroID
vc?.addWishButton.heroID = addButtonHeroID
// allow MainVC to recieve updated datasource array
vc?.dismissWishDelegate = self
vc?.theTableView.tableView.reloadData()
self.present(vc!, animated: true, completion: nil)
}
Thread 1: EXC_BAD_INSTRUCTION (code=EXC_I386_INVOP, subcode=0x0)
at line:
let vc = self.storyboard?.instantiateViewController(withIdentifier: "WishlistVC") as! WishlistViewController
I guess it is not as easy as I thought. Does anyone know why the app crashes and how I can solve this? :)
You are creating your MainViewController instance using a simple initialiser (MainViewController()) rather than instantiating it from the storyboard. As a result, any #IBOutlet properties will be nil since it is the the storyboard process that allocates those object instances and assigns them to the properties.
You need to add an identifier to your main view controller scene (if it doesn't already have one) and use that to instantiate the view controller instance. E.g. assuming the scene identifier is "MainScene" you would have something like:
override func viewDidLoad() {
super.viewDidLoad()
if isLoggedIn() {
let homeController = UIStoryboard(name: "Main", bundle: nil).instantiateViewControllerWithIdentifier("MainScene")
viewControllers = [homeController]
}
}
The crash in your updated question indicates that either the scene with the identifier WishlistVC doesn't have its class set to WishlistViewController or it isn't found so the forced downcast crashes.
I'm trying to check specific VC if app is running in foreground. My root view controller class is SWRevealViewController. After that I have a TabBarController and under it there is NavigationController and ViewController under it.
My heirachy is,
SWRevealViewController --> TabBar Controller --> Navigation Controller --> MessageVC --> ChatVC
I want to check in app delegate if app is on ChatVC or not if running on foreground.I have tried this code,
let tabBar:UITabBarController = self.window?.rootViewController as! UITabBarController
let navInTab:UINavigationController = tabBar.viewControllers?[1] as! UINavigationController
let storyboard = UIStoryboard(name: "Dashboard", bundle: nil)
let destinationViewController = storyboard.instantiateViewController(withIdentifier: "ChatDetailViewController") as? ChatDetailViewController
if destinationViewController?.restorationIdentifier == "ChatDetailViewController"
{
print("Yes")
}
else
{
print("No")
}
But app crashes with this error,
Could not cast value of type 'SWRevealViewController' (0x100dc4b20) to 'UITabBarController' (0x211b289f0).
How i can check if app is on ChatVC or not?
Screenshot of storyboard :
I have an extension for it
extension UIApplication {
class func topViewController(base: UIViewController? = UIApplication.shared.keyWindow?.rootViewController) -> UIViewController? {
if let nav = base as? UINavigationController {
return topViewController(base: nav.visibleViewController)
}
if let tab = base as? UITabBarController {
let moreNavigationController = tab.moreNavigationController
if let top = moreNavigationController.topViewController, top.view.window != nil {
return topViewController(base: top)
} else if let selected = tab.selectedViewController {
return topViewController(base: selected)
}
}
if let presented = base?.presentedViewController {
return topViewController(base: presented)
}
return base
}
}
USAGE:
if UIApplication.topViewController is YourViewController {
// do smth
}
I never used SWRevealViewController but you can try this.
As you said in your first line "SWRevealViewController is the RooVC" and you are converting the SWRevealViewController to UITabBarController (check below line of your code). This is your crash reason.
let tabBar:UITabBarController = self.window?.rootViewController as! UITabBarController
Now you need to change, UITabBarController to SWRevealViewController
let rootVC = self.window?.rootViewController as! SWRevealViewController
Now get all ViewControllers
if let navController = rootVC.navigationController { // for safety check
for controller in navController.viewControllers {
if controller is ChatVC {
print("Chat VC is available")
break
}
}
}
For safe coding and keeping swift optional binding in mind, you can do code like below,
Updated answer
var haveChatVC = false
if let rootVC = self.window?.rootViewController as? SWRevealViewController,
let tabbar = rootVC.frontViewController as? UITabBarController {
if let requiredNC = tabbar.viewControllers?[1] as? UINavigationController {
for vc in requiredNC.viewControllers {
if vc is ChatVC {
// ...
haveChatVC = true
break
}
}
}
else {
print("Navigation controller not found.")
}
}
else {
print("Unable to get root controller or navigation controller")
}
if haveChatVC {
// do task here when chat vc available
}
else {
// do task here when chat vc not available
}
Note: This is only pseudo code.
Was wondering if anyone could help me out. I just finished my Sign Up page where the user can create an account. The problem I have now is that in the Login View the user can simply just press the Login button and it will redirect them to the next view even if their email and password are incorrect.
class LoginViewController: UIViewController, GIDSignInUIDelegate {
#IBOutlet var userEmailLoginField: UITextField!
#IBOutlet var userPasswordLoginField: UITextField!
#IBAction func loginButton(_: Any) {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let tabBarController = storyboard.instantiateViewController(withIdentifier: "TabBarController") as! UITabBarController
let appDelegate = UIApplication.shared.delegate as! AppDelegate
appDelegate.window?.rootViewController = tabBarController
guard let email = userEmailLoginField.text, let password = userPasswordLoginField.text else { return }
FIRAuth.auth()?.signIn(withEmail: email, password: password) { (user, error) in
if let error = error {
print(error.localizedDescription)
return
}
}
}
Try moving your code that instantiates the new view controller. In the example below I moved it to only run if there is no error when authenticating with Firebase.
class LoginViewController: UIViewController, GIDSignInUIDelegate {
#IBOutlet var userEmailLoginField: UITextField!
#IBOutlet var userPasswordLoginField: UITextField!
#IBAction func loginButton(_: Any) {
guard let email = userEmailLoginField.text, let password = userPasswordLoginField.text else { return }
FIRAuth.auth()?.signIn(withEmail: email, password: password) { (user, error) in
if let error = error {
print(error.localizedDescription)
return
} else {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let tabBarController = storyboard.instantiateViewController(withIdentifier: "TabBarController") as! UITabBarController
let appDelegate = UIApplication.shared.delegate as! AppDelegate
appDelegate.window?.rootViewController = tabBarController
}
}
}
i want check in appdelegate.swift file if user logged in showing HomeviewController like Instagram and if not Showing LoginViewController , iam using mysql to save users data and php bridge lang
code in appdalegate.swift
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
// Override point for customization after application launch.
var rootViewController = self.window!.rootViewController
let isUserLoggedIn:Bool = NSUserDefaults.standardUserDefaults().boolForKey("isUserLoggedIn")
if(isUserLoggedIn) {
let mainStoryboard = UIStoryboard(name: "Main" , bundle: nil)
let protectedPage = mainStoryboard.instantiateViewControllerWithIdentifier("goToHome") as! HomeViewController
window!.rootViewController = protectedPage
window!.makeKeyAndVisible()
}
else{
let mainStoryboard = UIStoryboard(name: "Main" , bundle: nil)
let loginViewController = mainStoryboard.instantiateViewControllerWithIdentifier("loginview") as! LoginViewController
window!.rootViewController = loginViewController
window!.makeKeyAndVisible()
}
return true
}
loginviewcontoler.swift
let success:NSInteger = jsonData.valueForKey("success") as! NSInteger
//[jsonData[#"success"] integerValue];
NSLog("Success: %ld", success);
if(success == 1)
{
NSLog("Login SUCCESS");
let prefs:NSUserDefaults = NSUserDefaults.standardUserDefaults()
prefs.setObject(username, forKey: "USERNAME")
prefs.setInteger(1, forKey: "ISLOGGEDIN")
prefs.synchronize()
self.performSegueWithIdentifier("goToHome", sender: self)
ProfileViewController.swift
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(true)
let prefs:NSUserDefaults = NSUserDefaults.standardUserDefaults()
let isLoggedIn:Int = prefs.integerForKey("ISLOGGEDIN") as Int
if (isLoggedIn != 1) {
self.performSegueWithIdentifier("goto_login", sender: self)
}
else
{
self.usernameLabel.text = prefs.valueForKey("USERNAME") as? String
}
}
sorry for long question
Don't use in this situation NSUserDefaults
https://github.com/matthewpalmer/Locksmith
Here is good example of using safe LogIn / LogOut
so, after implementation
if let dictionary = Locksmith.loadDataForUserAccount("accaunt")
{
//go to profile
}
else
{
//go to login
}
Follow step:-
Step 1 :- Create custom SplashViewController set rootViewController SplashViewController
Step 2 :- Write login service call on SplashViewController
Setp 3 :- When you get Login success then just change rootViewController
Setp 4 :- Write one method in AppDelegate For change rootViewController for redirect Homescreen.
Setp 5 :- Call AppDelegate method for go to Homescreen.
You have to do some works.
1. You have to make sure that the user is logged in. to do that when you press the button (login button) use UserDefaults
let userDefault = UserDefaults.standard
userDefault.set(true, forKey: "isLoggedIn")
userDefault.synchronize()
2.When user will launch the app again just check whether the user logged in or not in viewDidAppear method/ viewDidLoad(its your choice which method you will use. most of the time I use viewDidAppear)
let userDefault = UserDefaults.standard
let savedData = userDefault.bool(forKey: "isLoggedIn")
if(savedData){
performSegue(withIdentifier: "segueTest", sender:nil)//here u have decide the which view will show if the user is logged in how. here i used segue.
}else{
viewController = self// this is the main view. just make the object of the class and called it.
}
I hope you know how to prepare segue
Just a concise way of doing the same thing:
func checkIfUserIsLoggedIn() {
if Auth.auth().currentUser == nil {
DispatchQueue.main.async {
// present login controller
let loginVC = LoginVC()
let navController = UINavigationController(rootViewController: loginVC)
self.present(navController, animated: true, completion: nil)
}
return
}
}