Swift - app crashes after setting UserDefaults - ios

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.

Related

Why self delegate is nil?

I want to make a weather application by adding a city name with openweathermap api. But I could not send the city I added in AddCityViewController back to HomeViewController. Because, self?.delegate is nil, in AddCityViewController.swift
#objc private func didTapSaveButton() {
print("clicked save button")
if let city = cityTextfield.text {
let weatherURL = URL(string: "https://api.openweathermap.org/data/2.5/weather?q=\(city)&APPID=b4251cb51691654da529bccf471596bc&units=imperial")!
let weatherResource = Resource<WeatherViewModel>(url: weatherURL) { data in
let weatherVM = try? JSONDecoder().decode(WeatherViewModel.self, from: data)
return weatherVM
}
Webservice().load(resource: weatherResource) { [weak self] result in
if let weatherVM = result {
if let delegate = self?.delegate {
delegate.addWeatherDidSave(vm: weatherVM)
self?.dismiss(animated: true, completion: nil)
}
}
}
}
}
When I debug the prepare function in HomeViewController.swift was not getting called.
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
guard let nav = segue.destination as? UINavigationController else {
fatalError("NavigationController not found")
}
guard let addWeatherCityVC = nav.viewControllers.first as? AddCityViewController else {
fatalError("AddWeatherCityController not found")
}
addWeatherCityVC.delegate = self
}
What I want is, I want to pass the city name back to HomeViewController when user press the save button.
extension HomeViewController: AddWeatherDelegate {
func addWeatherDidSave(vm: WeatherViewModel) {
print(vm.name)
}
}
Source code in GitHub
You are not using segue for navigation, so the prepareForSegue method won't get triggered. In your code, you are manually initialising an instance of AddCityViewController and presenting it. So to fix the issue, you have to set delegate to that instance.
#objc private func didTapAddButton() {
let vc = AddCityViewController()
vc.title = "Add City"
vc.delegate = self
let nav = UINavigationController(rootViewController: vc)
nav.modalPresentationStyle = .fullScreen
present(nav, animated: true)
}
Or else you can use segue for navigation.

Found nil while storyboard.instantiate

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 ?

My code for NSUserDefaults is not working

I am very new to swift. And need your help!
I want that, when the user logs in for the second time , the app should directly take it to the next view controller named CoreView. It should not ask for details, but I don't know why its not working. And it's asking for details everytime the app is launched. Please check the below code. I am not getting any sort of error too. Unless and until the app is killed or logged out, the user should be able to log in directly .
func pref_write()
{
// To write the data to NSUserDefaults
let prefs = NSUserDefaults.standardUserDefaults() // make a reference
print("OTP:\(OTP)")
// Adding values. Creating objects in prefs
prefs.setObject(OTP, forKey: "OTP")
print("check_OTP:\(check_OTP)")
prefs.setObject(U_ID, forKey: "U_ID")
print("Check_U_ID:\(check_U_ID)")
prefs.synchronize()
self.performSegueWithIdentifier("ContinueToCoreView", sender: self)
}
And in the viewDidLoad function:
override func viewDidLoad()
{
super.viewDidLoad()
//Read the data
self.performSegueWithIdentifier("ContinueToCoreView", sender: self)
pref_write()
let prefs = NSUserDefaults.standardUserDefaults()
check_OTP = prefs.objectForKey("OTP")!
check_U_ID = prefs.objectForKey("U_ID")!
prefs.objectForKey("U_ID")
print("prefs:\(prefs)")
prefs.synchronize()
}
Thanks!
Create a class as
class User_Details : NSObject
{
var user_id : String?
var user_otp : String?
var otp_verified : Bool?
init(u_id:String, otp:String?, verified:Bool)
{
super.init()
self.user_id = u_id
self.otp_verified = verified
self.user_otp = otp
}
}
In AppDelegate,
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool
{
navController = self.window?.rootViewController as? UINavigationController
if self.checkIfUserLoggedIn()
{
let user_details = NSUserDefaults.standardUserDefaults().objectForKey("user_details") as! User_Details
self.moveToNextScreen(user_details)
}
return true
}
//AppDelegate Class or in the class which is globally accessible
func pref_write_user(user_details : User_Details)
{
let prefs = NSUserDefaults.standardUserDefaults()
prefs.setObject(user_details, forKey: "user_details")
prefs.setBool(true, forKey: "is_user_login")
//After saving the OTP for current user, check for otp verified, move to OTP Screen
self.moveToNextScreen(user_details)
}
func moveToNextScreen(user_details : User_Details)
{
if user_details.otp_verified == false
{
// Move to OTP screen
let viewController = self.navController?.storyboard?.instantiateViewControllerWithIdentifier("otpScreen")
self.navController?.pushViewController(viewController!, animated: false)
}
else // Move to Home Screen
{
let viewController = self.navController?.storyboard?.instantiateViewControllerWithIdentifier("homeScreen")
self.navController?.pushViewController(viewController!, animated: false)
}
}
func logoutUser()
{
let prefs = NSUserDefaults.standardUserDefaults()
prefs.setObject(nil, forKey: "user_details")
prefs.setBool(false, forKey: "is_user_login")
}
func checkIfUserLoggedIn() -> Bool
{
let prefs = NSUserDefaults.standardUserDefaults()
if prefs.boolForKey("is_user_login")
{
if let _ = prefs.objectForKey("user_details")
{
return true
}
else
{
//User details not found for some reason, so setting the inital values and return false
self.logoutUser()
}
}
return false
}
Login Class :
Call the API for login by providing the basic credential, get the user_id and user_otp, save them to NSUserDefaults
func requestLoginToServer()
{
//Perform basic server action
....
//In Success Block write this
let appDel = UIApplication.sharedApplication().delegate as! AppDelegate
// pass the values as return by the server
let user_details = User_Details(u_id: "123", otp: "1234", verified: false)
appDel.pref_write_user(user_details)
appDel.moveToNextScreen(user_details)
}
Please try this way. I just rearranged your code.
First it will check the login credentials with in the didload method of initial view controller. If it not there it will call the method pref_write() . Please make sure that the values used in pref_write() method are not nil
override func viewDidLoad()
{
super.viewDidLoad()
let prefs:NSUserDefaults = NSUserDefaults.standardUserDefaults()
// You can give conditions for your need like if(prefs.valueForKey("U_ID") != nil))
// It will check the user defaults whether you already login
if(prefs.valueForKey("OTP") != nil) {
self.performSegueWithIdentifier("ContinueToCoreView", sender: self)
}
else{
pref_write()
}
}
// Make sure the Values are not nil
func pref_write()
{
// To write the data to NSUserDefaults
let prefs = NSUserDefaults.standardUserDefaults() // make a reference
print("OTP:\(OTP)")
// Adding values. Creating objects in prefs
prefs.setObject(OTP, forKey: "OTP")
print("check_OTP:\(check_OTP)")
prefs.setObject(U_ID, forKey: "U_ID")
print("Check_U_ID:\(check_U_ID)")
prefs.synchronize()
self.performSegueWithIdentifier("ContinueToCoreView", sender: self)
}
Hope its working...

check if user is logged in ios swift

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
}
}

Swift – self.navigationController becomes nil after transition

I'm experiencing a very strange error in my app, between two views, self.navigationController is becoming nil
I have a few view controllers: MainViewController, SecondViewController PastSessionsViewController, JournalViewController. I use JournalViewController for two purposes, to save a new entry into CoreData or to edit an older one. The details aren't really relevant to this error.
The error occurs when I try to pop JournalViewController off the stack and return to MainViewController but only when JournalViewController is in "edit" mode, not when it's in "save a new entry mode"
Any idea 1) why this is happening and 2) how to correctly address it so I can return to PastSessionsViewController when coming back from JournalViewController in edit mode?
Here's some code to make things concrete.
In AppDelegate.swift (inside of didFinishLaunchingWithOptions):
navController = UINavigationController()
navController!.navigationBarHidden = true
var viewController = MainViewController()
navController!.pushViewController(viewController, animated: false)
window = UIWindow(frame: UIScreen.mainScreen().bounds)
window?.backgroundColor = UIColor.whiteColor()
window?.rootViewController = navController
window?.makeKeyAndVisible()
In MainViewController:
func goToPastSessions(sender: UIButton) {
let pastSessionsVC = PastSessionsViewController()
self.navigationController?.pushViewController(pastSessionsVC, animated: true)
}
func goToWriteJournalEntry(sender: UIButton) {
let journalVC = JournalViewController(label: "Record your thoughts")
self.navigationController?.pushViewController(journalVC, animated: true)
}
In PastSessionsViewController:
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
let editJournalVC = JournalViewController(label: "Edit your thoughts")
let indexPath = tableView.indexPathForSelectedRow()
let location = indexPath?.row
let currentCell = tableView.cellForRowAtIndexPath(indexPath!) as! EntryCell
if let objects = pastSessionsDataSource.coreDataReturn {
if let location = location {
editJournalVC.journalEntryCoreDataLocation = location
editJournalVC.editEntry = true
editJournalVC.journalEntryToEdit = objects[location].journalEntry
}
}
self.navigationController?.presentViewController(editJournalVC, animated: true, completion: nil)
}
And finally, in JournalViewController:
func doneJournalEntry(sender: UIButton) {
journalEntryTextArea?.resignFirstResponder()
var entry = journalEntryTextArea?.text
if let entry = entry {
let appDelegate = (UIApplication.sharedApplication().delegate as! AppDelegate)
let managedObjectContext = appDelegate.managedObjectContext!
let request = NSFetchRequest(entityName: "Session")
var error: NSError?
// new entry
if journalEntryText == "Record your thoughts" {
let result = managedObjectContext.executeFetchRequest(request, error: &error)
if let objects = result as? [Session] {
if let lastTime = objects.last {
lastTime.journalEntry = entry
}
}
} else {
// existing entry
let sortDescriptor = NSSortDescriptor(key: "date", ascending: false)
request.sortDescriptors = [sortDescriptor]
let result = managedObjectContext.executeFetchRequest(request, error: &error)
if let objects = result as? [Session] {
var location = journalEntryCoreDataLocation
var object = objects[location!]
object.journalEntry = entry
}
}
if !managedObjectContext.save(&error) {
println("save failed: \(error?.localizedDescription)")
}
}
// in "edit" mode, self.navigationController is `nil` and this fails
// in "record a new entry" mode, it's not nil and works fine
self.navigationController?.popViewControllerAnimated(true)
}
func cancelEntryOrEditAndReturn(sender: UIButton) {
self.journalEntryTextArea?.resignFirstResponder()
// in "edit" mode, self.navigationController is `nil` and this fails
// in "record a new entry" mode, it's not nil and works fine
self.navigationController?.popViewControllerAnimated(true)
}
Thanks for taking a look
You should push editJournalVC instead of presenting if you want it to be in same navigation stack. Because when you present controller its no longer in same navigation stack. Also if you are presenting it, you should dismiss it not pop. So if you want to present controller you should use
self.dismissViewControllerAnimated(true, completion: nil)
instead
self.navigationController?.popViewControllerAnimated(true)

Resources