Search code examples
iosswiftfirebasegoogle-cloud-firestorensuserdefaults

Swift - app crashes after setting UserDefaults


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? :)


Solution

  • 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.