having trouble trying to keep users logged in with addStateDidChangeListener() - swift

So my goal for now is to successfully keep users logged in and show a certain viewController depending if they're logged in or not. I've read a lot of the Stack questions that showed up first on Google searches about this same topic and they said use addStateDidChangeListener() and that's exactly what I did.
I didn't know how to approach this, so I watched a Youtube video and copied the exact code the guy had, his project did what I wanted mine to do, so I gave it a shot. Unfortunately when I run the simulator, sign in, exit the simulator and simulate again, nothing changes. I will add my code and it's location.
This is the code in my AppDelegate.swift in the didFinishLaunchingWithOptions method
let storyboard = UIStoryboard.init(name: "Main", bundle: Bundle.main)
let auth = Auth.auth()
auth.addStateDidChangeListener { (_, user) in
switch user {
case nil:
guard self.activeViewController! is StudentSegmentedTableViewController else { return }
let nonLoggedInViewController = storyboard.instantiateViewController(withIdentifier: Constants.StoryboardIDs.GothereMainMenuStoryboardID) as! GothereMainMenuViewController
self.navigationController.setViewControllers([nonLoggedInViewController], animated: false)
self.navigationController.popToViewController(nonLoggedInViewController, animated: true)
self.activeViewController = nonLoggedInViewController
default:
guard self.activeViewController! is GothereMainMenuViewController else { return }
let alreadyLoggedInViewController = storyboard.instantiateViewController(withIdentifier: Constants.StoryboardIDs.StudentEventDashboardStoryboardID) as! StudentSegmentedTableViewController
self.navigationController.setViewControllers([alreadyLoggedInViewController], animated: false)
self.navigationController.popToViewController(alreadyLoggedInViewController, animated: true)
self.activeViewController = alreadyLoggedInViewController
}
}
let nonLoggedInViewController = storyboard.instantiateViewController(withIdentifier: Constants.StoryboardIDs.GothereMainMenuStoryboardID) as! GothereMainMenuViewController
let alreadyLoggedInViewController = storyboard.instantiateViewController(withIdentifier: Constants.StoryboardIDs.StudentEventDashboardStoryboardID) as! StudentSegmentedTableViewController
activeViewController = nonLoggedInViewController
switch Auth.auth().currentUser != nil {
case true:
activeViewController = alreadyLoggedInViewController
default:
break
}
navigationController = UINavigationController.init(rootViewController: activeViewController)
self.window?.rootViewController = navigationController
self.window?.makeKeyAndVisible()
I tried just this alone at first, and it didn't work so then I implemented a state listener in reasonable spots in my app.
First I added one that enables right after successful log in/signup and the segue is performed .
func enableAuth() {
authListener = Auth.auth().addStateDidChangeListener { (_, user) in
print("State Listener activated")
}
}
This is what I call in the viewDidLoad() of the segued viewController right after login/signup. To remove it, I simply call it when the logout button is pressed..
func disableAuthState() {
Auth.auth().removeStateDidChangeListener(self.authListener!)
print("State Listener Deactivated")
}
func studentLogoutSelected() {
var text = UITextField()
let alert = UIAlertController(title: "Logout", message: "Are you sure you want to logout?", preferredStyle: .alert)
let cancelAction = UIAlertAction(title: "Cancel", style: .cancel) { (action) in
self.dismiss(animated: true, completion: nil)
}
let logoutAction = UIAlertAction(title: "Logout", style: .default) { (logoutAction) in
let firebaseAuth = Auth.auth()
do {
try firebaseAuth.signOut()
self.disableAuthState()
self.performSegue(withIdentifier: Constants.Segues.studentLogout, sender: self)
} catch let signOutError as NSError {
print("There was an error signing the user out. \(signOutError)")
}
}
alert.addAction(cancelAction)
alert.addAction(logoutAction)
present(alert, animated: true, completion: nil)
}
After all these functions and implementations, the shown blocks of code still don't do what I expected them to do. If anybody can point out issues or suggestions, that would be great, thanks.

First of all are you add FirebaseApp.configure() on your didFinishLaunchingWithOptions function in appdelegate? Then, Can you try call enableAuth in viewWillAppear()

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

Code not finishing the function, it is ending execution halfway through

My code is as follows:
#IBAction func clicked(_ sender: Any) {
let ref = Database.database().reference()
let pass = password.text
var firpass = ""
var bool = false;
ref.child(name.text as! String).child("password").observeSingleEvent(of: .value, with: { dataSnapshot in
firpass = dataSnapshot.value as! String
if firpass == pass {
bool = true
print("in here")
}
})
print(bool)
if bool {
self.sendname = name.text!
let vc = DatabaseTableViewController(nibName: "DatabaseTableViewController", bundle: nil)
vc.finalName = self.sendname
navigationController?.pushViewController(vc, animated: true)
performSegue(withIdentifier: "username", sender: self)
} else {
let alert = UIAlertController(title: "Error", message: "Incorrect username or password", preferredStyle: UIAlertController.Style.alert)
alert.addAction(UIAlertAction(title: "OK", style: UIAlertAction.Style.default, handler: nil))
self.present(alert, animated: true, completion: nil)
}
}
"in here" gets printed, but bool is never printed and the alert is showing. Why does my code not enter the if bool block and output the alert?
Data is loaded from Firebase asynchronously, since it may take a while. Instead of making your app wait for the data (which would be a bad user experience), your main code continues while the data is being loaded, and then once the data is available your closure is called.
This explains the behavior you're seeing: by the time your runs, the hasn't run yet.
the solution is as simple as it is initially confusing and annoying: any code that needs the data from the database must be inside the closure, or be called from there.
So for example:
ref.child(name.text as! String).child("password").observeSingleEvent(of: .value, with: { dataSnapshot in
firpass = dataSnapshot.value as! String
if firpass == pass {
bool = true
print("in here")
}
print(bool)
if bool {
self.sendname = name.text!
let vc = DatabaseTableViewController(nibName: "DatabaseTableViewController", bundle: nil)
vc.finalName = self.sendname
navigationController?.pushViewController(vc, animated: true)
performSegue(withIdentifier: "username", sender: self)
} else {
let alert = UIAlertController(title: "Error", message: "Incorrect username or password", preferredStyle: UIAlertController.Style.alert)
alert.addAction(UIAlertAction(title: "OK", style: UIAlertAction.Style.default, handler: nil))
self.present(alert, animated: true, completion: nil)
}
})
Also see:
Firebase with Swift 3 counting the number of children
Array of struct not updating outside the closure
getting data out of a closure that retrieves data from firebase (showing examples with custom callbacks and delegates)
How to reload data after all Firebase calls finished? (showing how to use a dispatch group)
Finish all asynchronous requests before loading data? (another example using a dispatch group)
Also you have to set variable bool to false when you are navigating to next view controller after login. So that you login again and if password is wrong then you can not navigate to next page and only it shows alert for wrong password.

segue/push to different view controller from IBAction function?

I am trying to push to the next viewcontroller programatically, such that I can save data in core data and then push to the next view controller. Basically when I tap a button with an IBAction, it will do all this. However, I am getting a thread 1 SIGABRT error whenever I use this code in my IBAction func:
#IBAction func onPlusTapped(){
let alert = UIAlertController(title: "New Course", message: nil, preferredStyle: .alert)
alert.addTextField { (textField) in
textField.placeholder = "Course"
}
let action = UIAlertAction(title: "New", style: .default){ (_) in
let course = alert.textFields!.first?.text!
print(course)
let coursy = Course(context: PersistenceService.context)
coursy.name = course
PersistenceService.saveContext()
}
alert.addAction(action)
present(alert, animated: true, completion: nil)
// jump to next view controller
segway()
}
func segway(){
let storyBoard: UIStoryboard = UIStoryboard(name: "viewcontroller", bundle: nil)
let balanceViewController = storyBoard.instantiateViewController(withIdentifier: "viewcontroller") as! ViewController
self.present(balanceViewController, animated: true, completion: nil)
}
}
If you want to jump to your next viewController once Course saved, you have to call the func segway() in the UIAlertAction completion block.
Example:
let action = UIAlertAction(title: "New", style: .default){ (_) in
let course = alert.textFields!.first?.text!
print(course)
let coursy = Course(context: PersistenceService.context)
coursy.name = course
PersistenceService.saveContext()
// jump to next view controller
segway()
}
Also double check your storyboard name and balanceViewController's identifier is "viewcontroller".
Why are you jumping segway() and present alert at the same time? shouldn't you put segway() inside your alert action?
Please see the following code.
#IBAction func onPlusTapped() {
let alert = UIAlertController(title: "New Course", message: nil, preferredStyle: .alert)
alert.addTextField { (textField) in
textField.placeholder = "Course"
}
let action = UIAlertAction(title: "New", style: .default){ (_) in
let course = alert.textFields!.first?.text!
print(course)
let coursy = Course(context: PersistenceService.context)
coursy.name = course
PersistenceService.saveContext()
// jump to next view controller
segway()
}
alert.addAction(action)
present(alert, animated: true, completion: nil)
}
func segway() {
let storyBoard: UIStoryboard = UIStoryboard(name: "viewcontroller", bundle: nil)
let balanceViewController = storyBoard.instantiateViewController(withIdentifier: "viewcontroller") as! ViewController
self.present(balanceViewController, animated: true, completion: nil)
}
}
Hope this help!

How to rectify error when the app is uninstalled and then reinstalled and goes to the home page

If I uninstalls the app and then reinstall my app then it directly goes to the home page of the app. It doesn't ask the user to login.
I wrote the authStateListener for the current user to monitor when the app is removed in the background and then reinstalled, the app will show the login page instead of the Home page. When the user logs out then the app shows the login page otherwise the app goes to the home page. So when the user is logged out from the app and then uninstalls and reinstalls the app, it works correctly.
But my problem is if user isn't logged out, and uninstalls and reinstalls the app then it shows the home page of the app instead of the log in page. How do I solve this?
The code is :
class ViewController: UIViewController {
var db : Firestore!
var handle:AuthStateDidChangeListenerHandle?
#IBOutlet weak var email: UITextField!
#IBOutlet weak var pswd: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
db=Firestore.firestore()
print(currentReachabilityStatus != .notReachable)
let token = Messaging.messaging().fcmToken
let authid = Auth.auth().currentUser?.uid
print("token\(String(describing: token))")
let docRef = self.db.collection("deyaPayUsers").document(authid!)
docRef.setData(["FCMToken":token as Any],options:SetOptions.merge())
self.handle = Auth.auth().addStateDidChangeListener { auth, user in
if user != nil {
if (user?.isEmailVerified)!{
let myVC = self.storyboard?.instantiateViewController(withIdentifier: "deyaPay") as!deyaPay
self.navigationController?.pushViewController(myVC, animated:true)
}
else{
let alertVC = UIAlertController(title: "Error", message: "Sorry. Your email address has not yet been verified. Do you want us to send another verification email to \(String(describing: self.email.text!)).", preferredStyle: .alert)
let alertActionCancel = UIAlertAction(title: "Cancel", style: .default, handler: nil)
//alertVC.addAction(alertActionOkay)
alertVC.addAction(alertActionCancel)
self.present(alertVC, animated: true, completion: nil)
}
}
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
override func viewDidAppear(_ animated: Bool) {
if currentReachabilityStatus != .notReachable
{
print("Connected")
}
else
{
let controller = UIAlertController(title: "No Internet Detected", message: "This app requires an Internet connection", preferredStyle: .alert)
let ok = UIAlertAction(title: "OK", style: .default, handler: nil)
let cancel = UIAlertAction(title: "Cancel", style: .cancel, handler: nil)
controller.addAction(ok)
controller.addAction(cancel)
present(controller, animated: true, completion: nil)
}
Auth.auth().addStateDidChangeListener{ auth, user in
if Auth.auth().currentUser != nil {
let vc = self.storyboard?.instantiateViewController(withIdentifier: "deyaPay")as!deyaPay
//self.present(vc, animated: true, completion: nil)
let navigationController = UINavigationController(rootViewController: vc)
self.present(navigationController, animated: true, completion: nil)
} else {
print("signout")
}
}
}
}

Click alert button to show MFMailComposeViewController

Trying to use MFMailComposeViewController when click alert button. But when I click alert button, controller doesn't pop-up. The code is below. I used extension and I'm trying to call sendmail function when clicking alert button.
extension Alert:MFMailComposeViewControllerDelegate {
func sendmail(){
let mailComposeViewController = configureMailController()
if MFMailComposeViewController.canSendMail() {
let VC = storyboard?.instantiateViewController(withIdentifier: "MainVC")
VC?.present(mailComposeViewController, animated: true, completion: nil)
} else {
showMailError()
}
}
func configureMailController() -> MFMailComposeViewController {
let mailComposerVC = MFMailComposeViewController()
let VC = storyboard?.instantiateViewController(withIdentifier: "MainVC")
mailComposerVC.mailComposeDelegate = VC as? MFMailComposeViewControllerDelegate
mailComposerVC.setToRecipients(["**"])
mailComposerVC.setSubject("**")
mailComposerVC.setMessageBody("\n\n\n\nModel: \nSistem versiyon: )\nuygulamaversiyon:", isHTML: false)
return mailComposerVC
}
func showMailError() {
let sendMailErrorAlert = UIAlertController(title: "Could not send email", message: "Your device could not send email", preferredStyle: .alert)
let dismiss = UIAlertAction(title: "Ok", style: .default, handler: nil)
sendMailErrorAlert.addAction(dismiss)
let VC = storyboard?.instantiateViewController(withIdentifier: "MainVC")
VC?.present(sendMailErrorAlert, animated: true, completion: nil)
}
public func mailComposeController(_ controller: MFMailComposeViewController, didFinishWith result: MFMailComposeResult, error: Error?) {
controller.dismiss(animated: true, completion: nil)
}
}
Your code tries to present the mail view controller (and also the error alert) on a new view controller which you create with instantiateViewController. Since this new VC itself is not part of your app's VC hierarchy, neither it nor the mail VC will appear on screen.
To make this work, you can either pass a view controller which is part of your app into your sendmail method:
func sendmail(_ root: UIViewController) {
...
root.present(mailComposeViewController, animated: true, completion: nil)
...
Or you can use a globally accessible VC of your app; in a simple app using the root VC should work:
func sendmail() {
...
let root = UIApplication.shared.keyWindow?.rootViewController
root?.present(mailComposeViewController, animated: true, completion: nil)
...
I'd suggest the first approach because it is more flexible (e.g. it allows you to open your mail screen anywhere, even when your VC hierarchy gets more complex).