How best to remove all UIViewcontrollers and goto a single UIViewController? - ios

I have an application with the option to change a users password. The reset password process is all done via web links. When the process is complete, the user returns to their app. Given that the password has now been changed, i would like to log the user out.
This is done when a user makes a request to the server for information and my server code will respond with a custom code to let the app know that the password has been changed. In this instance the code is '177'.
I have attempted to present the user with the 'login' uiviewcontroller - but without success. the screen just freezes. Please can someone advise?
if code == 177{
// INVALID API KET USED. LOG USER OUT
print("INVALID API KET USED. LOG USER OUT")
GlobalFunction.logout(action: {
// GO TO LOGIN VIEWCONTROLLER
print("GO TO LOGIN PAGE")
let loginVC = self.storyboard?.instantiateViewController(withIdentifier: "login") as! MyLoginViewController
self.present(loginVC, animated: true, completion: nil)
})
return
}
SECOND CLASS:
class GlobalFunction{
// LOG USER OUT OF APPLICATION
static func logout(action: #escaping (() -> ())){
// Remove logged in user credentials
UserDefaults.standard.removeObject(forKey: "userId")
UserDefaults.standard.removeObject(forKey: "firstname")
UserDefaults.standard.removeObject(forKey: "firstname")
UserDefaults.standard.removeObject(forKey: "api_key")
UserDefaults.standard.synchronize()
print("user logged out")
}
}

Make sure you have to run the UI events on main thread.
DispatchQueue.main.async {
let loginVC = self.storyboard?.instantiateViewController(withIdentifier: "login") as! MyLoginViewController
self.present(loginVC, animated: true, completion: nil)
}

Related

Mapview delegate function continues to run after dismissing View Controller that contains mapview

I'm using mapbox and firebase.
I have a delegate function that updates the user's coordinates(inside of the firebase database) when the user's location changes.
To the best of my knowledge, it functions as it should when signed into the app. The mapviews delegate is the view controller (self.mapView.delegate = self)
func mapView(_ mapView: MGLMapView, regionDidChangeAnimated animated: Bool){
let latitude = mapView.userLocation?.coordinate.latitude
let longitude = mapView.userLocation?.coordinate.longitude
Database.database().reference().child(pathToCoord).updateChildValues(["latitude":latitude, "longitude":longitude], withCompletionBlock: { (err, ref) in
if err != nil { print(err!); return }
}
When I sign out of the app, I would like to stop updating the user location.
Ideally I would just like the View Controller with the map to go away completely and for everything on it to stop running.
I've written this sign out function that try several different methods of making sure that the location is no longer updated.
func signOut(){
for id in Auth.auth().currentUser?.providerData{
if id.providerID == "facebook.com"{
FBSDKLoginManager().logOut()
}
}
do {
try Auth.auth().signOut()
}catch let logoutError {
print(logoutError)
}
self.mapView.delegate = nil
if let vc = self.storyboard!.instantiateViewController(withIdentifier: "SignInViewController") as? SignInViewController{
UIApplication.shared.keyWindow?.rootViewController = vc
self.dismiss(animated: true, completion: nil)
}
}
Sometimes when I'm logged out though, I continuously get the error below in my console. The most logical solution I can think of for why this is happening is that the View Controller is still running. I don't know how to make it stop.
[Firebase/Database] updateChildValues: at `path/to/coord` failed: permission_denied
Error Domain=com.firebase Code=1 "Permission denied" UserInfo={NSLocalizedDescription=Permission denied}
Edit
So it looks like the problem was probably that I had this in my SignInViewController
if let uid = Auth.auth().currentUser?.uid{
if let vc = self.storyboard!.instantiateViewController(withIdentifier: "MainViewController") as? MainViewController{
vc.uid = uid
UIApplication.shared.keyWindow?.rootViewController = vc
}
}
And then the delegate would run once for each uid, as if two of the view controllers were running at the same time. When I signed out, I'm guessing the other one didn't sign out and kept running for the other user id.
This is off topic to my original question but I'd like to know what the proper way to check if a user is already signed in, and then sign them in is. Because clearly my method didn't work.
Why don't you try dismiss with Completion Handler block like below.
self.dismiss(animated: true, completion: {
if let vc = self.storyboard!.instantiateViewController(withIdentifier: "SignInViewController") as? SignInViewController{
UIApplication.shared.keyWindow?.rootViewController = vc
}
})

How can I handle offline logout with Firebase in Swift?

I'm unsure how to handle logout in my IOS app when the user is offline.
I'm using Firebase in Swift for authentication and it works perfectly when online for login/logout. However, I want to let the user logout when offline. Currently, when offline and the user clicks the Logout button, nothing happens on screen. If I restart the app, I am taken to the login screen as-if the logout occurred.
do {
try Auth.auth().signOut()
} catch let signOutError as NSError {
print (signOutError)
}
self.performSegue(withIdentifier: "unwindToMain", sender: self)
This code works for signout when online, but when offline the segue is not performed. I've also tried putting segue in the do and catch.
Please try the below code
if Auth.auth().currentUser != nil {
do {
try Auth.auth().signOut()
let vc = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "Welcome")
present(vc, animated: true, completion: nil)
} catch let error as NSError {
print(error.localizedDescription)
}
}

How to login user automatically in iOS swift?

I am using phone authentication using cloud firestore. In firestore db, I am storing user phone number and uid. Here is the code which, I have tried for mobile number login:
#IBAction func signUp(_ sender: Any) {
// dismiss keyboard
view.endEditing(true)
if sendOTP == false {
let mobileNumber = "+91" + phoneNumberTextField.text!
self.Userdefaults.set(mobileNumber, forKey: "mobileNumber")
print("mobileNumber::::\(mobileNumber)")
sendOTPCode()
sendOTP = true
} else {
let codestring = OTPCodeTextField.text
if codestring?.count == 6 {
loginusingOTP(OTPtext: codestring!)
} else {
print("Enter 6 digit code")
}
}
func sendOTPCode() {
let mymobilenumber = Userdefaults.string(forKey: "mobileNumber")
PhoneAuthProvider.provider().verifyPhoneNumber(mymobilenumber!) { (verificationID, error) in
self.Userdefaults.set(verificationID, forKey: "authVerificationID")
if error != nil
{
print ("insde SendCode, there is error")
print("error: \(String(describing: error?.localizedDescription))")
} else {
print ("code sent")
self.phoneNumberTextField.allowsEditingTextAttributes = false
}
}
}
func loginusingOTP(OTPtext: String) {
let db = Firestore.firestore()
let verificationID = self.Userdefaults.string(forKey: "authVerificationID")
let credential: PhoneAuthCredential = PhoneAuthProvider.provider().credential(withVerificationID: verificationID!,
verificationCode: OTPtext)
Auth.auth().signIn(with: credential)
{
(user, error) in
if error != nil
{
print("error: \(String(describing: error?.localizedDescription))")
}
else if user != nil
{
print("Phone number: \(String(describing: user?.phoneNumber))")
let userInfo = user?.providerData[0]
print("Provider ID: \(String(describing: userInfo?.providerID))")
var _: DocumentReference? = nil
print("currentUser:::\(String(describing: currentUser))")
db.collection("users").document(currentUser!).setData([
"User_Phone_number": user?.phoneNumber as Any,
"uid": currentUser as Any
]) { err in
if let err = err {
print("Error writing document: \(err)")
} else {
print("Document successfully written!")
if PrefsManager.sharedinstance.isFirstTime == false{
let when = DispatchTime.now() + 0
DispatchQueue.main.asyncAfter(deadline: when) {
self.performSegue(withIdentifier: "signUpToTabBar", sender: nil)
}
}else{
let when = DispatchTime.now() + 0
DispatchQueue.main.asyncAfter(deadline: when) {
let storyboard = UIStoryboard(name: "Start", bundle: nil)
let initialViewController = storyboard.instantiateViewController(withIdentifier: "onboardvc")
self.present(initialViewController, animated: true, completion: nil)
}
}
}
}
} else {
print("error::::::")
}
}
}
User login flow - First user enter phone number and then taps on send otp then user enters otp code, logged in successfully. Since user login first time, user needs to fill user detail page and then goes to home page. If its already logged in user, after successful of login user will redirected to home screen not the user detail page.
My question is now user is login each and every time to get inside of the app, I want user to login automatically without login each and every time unless user logout. How to check already logged in user UID or phone in cloud firestore for user exist or new user.
Any help much appreciated pls...
Let me give you brief scenario.
SwiftyUserDefaults is the best library to store UserDefaults throughout the app.
Create one extension like this,
extension DefaultsKeys {
static let username = DefaultsKey<String?>("username")
static let phoneNo = DefaultsKey<String?>("phoneNo")
static let islogin = DefaultsKey<Bool?>("islogin")
}
After successful login, you can set the values of the above DefaultKeys like below, first import SwiftyUserDefaults,
Defaults[.username] = Your_User_Name
Defaults[.phoneno] = Your_Phone_No
Defaults[.islogin] = true
Now on your first LoginViewController, in viewDidLoad() method, Please check following,
if Defaults[.islogin] == true {
//Go to Home with animation false
}
Here you go, let me know in case of any queries.
FYI. This is just the scenario, actual may be different depending on your final requirement. This answer may help you.
For Firebase (and it looks like FireStore). Once the User is Authenticated on the device, they will automatically be "logged in" to Firebase/Firestore next sessions unless there is a specific SignOut (Auth.auth().signOut()) or unless there is a super long delay (not sure how long, maybe a month).
To check to see if the user is already logged in.
On Start Up (didFinishLaunchingWithOptions) set up an Auth Listener and it will fire once and return the current Auth Status.
func addUserListener() {
listenHandler = Auth.auth().addStateDidChangeListener { (auth, user) in
if user == nil {
// We are Logged Out of Firebase.
// Move to Login Screen
} else {
// we are Logged In to Firebase.
// Move to Main Screen
}
}
You really need to use an Auth Listener for Login calls as if someone has two devices and logs out on one device, they will be logged out of firebase and the 2nd device will crash when you try a firebase call, because it still thinks its logged in.
Update your login credentials OR loggedIn flag to user Defaults
UserDefaults.standard.set("value", forKey: "username")
UserDefaults.standard.set("value", forKey: "password")
(NB: i recommended not to store password directly, you could store authentication token in Userdefaults, & if you want password to be stored, use keychain instead)
And redirect to corresponding page from AppDelegate:
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
var initialViewController: UIViewController?
let mainStoryboard : UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
if let username = UserDefaults.standard.value(forKey: "username"), let password = UserDefaults.standard.value(forKey: "password") {
initialViewController = mainStoryboard.instantiateViewController(withIdentifier: "HomeVC")
} else {
initialViewController = mainStoryboard.instantiateViewController(withIdentifier: "LoginVC")
}
self.window = UIWindow(frame: UIScreen.main.bounds)
self.window?.rootViewController = initialViewController
self.window?.makeKeyAndVisible()
return true
}

How can i send different users to separate view controllers using firebase and Xcode

I am fairly new to coding and have started using firebase as a back end server for an application i am creating in Xcode using swift.
The app itself will have one login page but 3 separate types of users. The admin will have different permissions to the other 2 users.
The code I currently have is:
FIRAuth.auth()?.signIn(withEmail: username!, password: password!, completion: { (user, error) in
if error == nil {
let vc = self.storyboard?.instantiateViewController(withIdentifier: "AdminVC")
self.present(vc!, animated: true, completion: nil)
}
The code is getting the email and password for the authentication page. But because of the 3 different types of users I don't want them all going to the 'AdminVC' view controller.
Is there a way of getting the 2 other users to go to their own view controllers using this authentication method?
If you want to store a type for a user you have to use the database. Like this
When the user logs in, get the value from the database for the path "users/<userId>/type". Then use a switch statement to redirect to the correct view controller.
Here's the full code
// Sign in to Firebase
FIRAuth.auth()?.signIn(withEmail: "ntoonio#gmail.com", password: "Password123", completion: {
(user, error) in
// If there's no errors
if error == nil {
// Get the type from the database. It's path is users/<userId>/type.
// Notice "observeSingleEvent", so we don't register for getting an update every time it changes.
FIRDatabase.database().reference().child("users/\(user!.uid)/type").observeSingleEvent(of: .value, with: {
(snapshot) in
switch snapshot.value as! String {
// If our user is admin...
case "admin":
// ...redirect to the admin page
let vc = self.storyboard?.instantiateViewController(withIdentifier: "adminVC")
self.present(vc!, animated: true, completion: nil)
// If out user is a regular user...
case "user":
// ...redirect to the user page
let vc = self.storyboard?.instantiateViewController(withIdentifier: "userVC")
self.present(vc!, animated: true, completion: nil)
// If the type wasn't found...
default:
// ...print an error
print("Error: Couldn't find type for user \(user!.uid)")
}
})
}
})
Instead of the whole switch statement you can do
let vc = self.storyboard?.instantiateViewController(withIdentifier: "\(snapshot.value)_View")
self.present(vc!, animated: true, completion: nil)
Warning! This will crash if the type isn't found. But that's fixable :)

Firebase - iOS Swift: FIRAuth.auth().signOut() not signing out current user

I'm building an app using Firebase with an initial SignInViewController that loads a sign in page for users to authenticate with email which triggers the following methods:
#IBAction func didTapSignIn(sender: AnyObject) {
let email = emailField.text
let password = passwordField.text
FIRAuth.auth()?.signInWithEmail(email!, password: password!) { (user, error) in
if let error = error {
print(error.localizedDescription)
return
}
self.signedIn(user!)
}
}
func signedIn(user: FIRUser?) {
AppState.sharedInstance.displayName = user?.displayName ?? user?.email
AppState.sharedInstance.signedIn = true
NSNotificationCenter.defaultCenter().postNotificationName(Constants.NotificationKeys.SignedIn, object: nil, userInfo: nil)
performSegueWithIdentifier(Constants.Segues.SignInToHome, sender: nil)
}
The SignInViewController also checks if there is a cached current user when the app launches and, if so, signs that user in:
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(true)
//Synchronously gets the cached current user, or null if there is none.
if let user = FirebaseConfigManager.sharedInstance.currentUser {
self.signedIn(user)
}
}
Once the user is signed in, the app segues to a HomeScreenViewController which displays a "Sign Out" button at the top left of the navigation bar. When a user taps the "Sign Out" button, that user is supposed to get signed out and the app should segue back to the SignInViewController with the following method:
#IBAction func didTapSignOut(sender: UIBarButtonItem) {
print("sign out button tapped")
let firebaseAuth = FIRAuth.auth()
do {
try firebaseAuth?.signOut()
AppState.sharedInstance.signedIn = false
dismissViewControllerAnimated(true, completion: nil)
} catch let signOutError as NSError {
print ("Error signing out: \(signOutError)")
} catch {
print("Unknown error.")
}
}
When I tap the "Sign out" button, the didTapSignOut method gets called and gets executed.
However, after the try firebaseAuth?.signOut() line of code gets executed, the current user should be nil. But when I print out the current user in the Xcode console, the current user is still logged in:
po FIRAuth.auth()?.currentUser
▿ Optional<FIRUser>
- Some : <FIRUser: 0x7fde43540f50>
Since the current user doesn't get signed out after firebaseAuth?.signOut() gets called, once the app segues back to the SignInViewController the app still thinks there is a cached current user so that user gets signed in again.
Could this be a Keychain issue?
Does it have to do with NSNotificationCenter.defaultCenter().postNotificationName being called?
My code comes directly from the Google Firebase Swift Codelab so I'm not sure why it's not working:
https://codelabs.developers.google.com/codelabs/firebase-ios-swift/#4
You can add a listener in your viewDidAppear method of your view controller like so:
FIRAuth.auth()?.addStateDidChangeListener { auth, user in
if let user = user {
print("User is signed in.")
} else {
print("User is signed out.")
}
}
This allows you to execute code when the user's authentication state has changed. It allows you to listen for the event since the signOut method from Firebase does not have a completion handler.
GIDSignIn.sharedInstance().signOut()
Use exclamation points not question marks.
try! FIRAuth.auth()!.signOut()
I actually had this issue as well. I was also logging out the user (as you are) with the method's provided by Firebase but when I printed to the console it said that I still had a optional user.
I had to change the logic of setting the current user so that it is always configured by the authentication handler provided by Firebase:
var currentUser: User? = Auth.auth().currentUser
var handle: AuthStateDidChangeListenerHandle!
init() {
handle = Auth.auth().addStateDidChangeListener { (auth, user) in
self.currentUser = user
if user == nil {
UserDefaults.standard.setValue(false, forKey: UserDefaults.loggedIn)
} else {
UserDefaults.standard.setValue(true, forKey: UserDefaults.loggedIn)
}
}
}
As long as you are referencing the current user from this handle, it will update the current user no matter the authentication state.
Some answers are using a force unwrap when the firebase signing out method can throw an error. DO NOT DO THIS!
Instead the call should be done in a do - catch - block as shown below
do {
try Auth.auth().signOut()
} catch let error {
// handle error here
print("Error trying to sign out of Firebase: \(error.localizedDescription)")
}
You can then listen to the state change using Auth.auth().addStateDidChangeListener and handle accordingly.
I just had what I think is the same problem - Firebase + Swift 3 wouldn't trigger stateDidChangeListeners on logout, which left my app thinking the user was still logged in.
What ended up working for me was to save and reuse a single reference to the FIRAuth.auth() instance rather than calling FIRAuth.auth() each time.
Calling FIRAuth.auth()?.signOut() would not trigger stateDidChangeListeners that I had previously attached. But when I saved the variable and reused it in both methods, it worked as expected.

Resources