How to run Asynchronous function in a Uibutton in swift - ios

I want the getUserToken function and userLogin function to run before the next line which is the Firebase Authentication. For it to run ansynchronous
#IBAction func loginButtonPressed(_ sender: UIButton) {
self.showSpinner(onView: self.view)
guard var phoneNumber = phoneTextField.getRawPhoneNumber() else { return }
phoneNumber = "+234\(phoneNumber)"
guard var userPhoneNumber = phoneTextField.getRawPhoneNumber() else { return }
userPhoneNumber = "234\(userPhoneNumber)"
guard let userName = nameTextField.text else {return}
print(phoneNumber)
getUserAcessToken()
userLogin()
//Validate Required fields are mnot empty
if nameTextField.text == userName {
//Firebase Manipulation
PhoneAuthProvider.provider().verifyPhoneNumber(phoneNumber, uiDelegate: nil) { (verificationId, error) in
if error == nil {
print(verificationId!)
//UserDefaults Database manipulation for Verification ID
guard let verifyid = verificationId else {return}
self.defaults.set(verifyid, forKey: "verificationId")
self.defaults.synchronize()
self.removeSpinner()
}else {
print("Unable to get secret verification code from Firebase", error?.localizedDescription as Any)
let alert = UIAlertController(title: "Please enter correct email and phone number", message: "\n", preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil))
self.present(alert, animated: true, completion: nil)
return
}
}
}
let OTPRequestVC = storyboard?.instantiateViewController(withIdentifier: "OTPRequestViewController") as! OTPRequestViewController
OTPRequestVC.userId = userId
OTPRequestVC.userEmailData = userEmail
self.present(OTPRequestVC, animated: true)
}
I want the two functions to run asynchronously before the firebase auth.

Its not a good idea to run the time consuming functions on the main thread.
My suggestions would be.
getUserAcessToken() and userLogin() functions Should have a callback. Which will make those functions run on a different thread (I believe those functions are making api call which is done in the background thread)
You could call userLogin() in the completion handler of getUserAcessToken() and then firebaseAuth in the completion handler of getUserAcessToken().
This will make sure that the UI is not hanged till you make those api calls and the user will know that something is going on in the app and the app is not hanged.

Without reproducing the entire intended functionality, the pattern you want to follow is:
func loginButtonPressed(_ sender: UIButton) {
// Any immediate changes to the UI here
// ...
// Start time consuming task in background
DispatchQueue.global(qos: .userInitiated).async {
getUserAccessToken()
userLogin()
// Make your Firebase call
PhoneAuthProvider.provider().verifyPhoneNumber(phoneNumber, uiDelegate: nil) { (verificationId, error) in
// Any response validation here
// ...
DispatchQueue.main.async {
// Any UI updates here
}
}
}
}

Related

the UI is not update After i call the logout method

need your support I've been struggling with this for days. I'll appreciate any help
hi
I'm trying to learn IOS Development & No SQL Database
using Firebase
by creating a Chat app using Firebase real time database & swift
my problem is UI is not updated unless i rebuild The App
sum up
-the log out functionality will not work unless i rebuild the App again ( if i sign out i can login but i cant see the Chat Messages unless i rebuild the App)
if i want to login (to see the Chat Messages) this is my Process
A- log out from the APP
B -login to the APP (here i need to be logged in ) i rebuild the App again
so every time i add a message i need to rebuild because so the signin method will get activates
the Login Method
extension FCViewController : FUIAuthDelegate
{
func authUI(_ authUI: FUIAuth, didSignInWith authDataResult: AuthDataResult?, error: Error?)
{
if (error != nil)
{
return
}
login()
}
}
func login(
let authUI = FUIAuth.defaultAuthUI()
let googleAuthProvider = FUIGoogleAuth(authUI: authUI!)
let provider : [FUIAuthProvider] = [googleAuthProvider , FUIEmailAuth()]
authUI?.providers = provider
_authHandle = Auth.auth().addStateDidChangeListener
{
(auth : Auth , user : User?) in
self.messages.removeAll(keepingCapacity: false)
self.messagesTable.reloadData()
if let activeUser = user
{
if (self.user != activeUser)
{
self.user = activeUser
self.signedInStatus(isSignedIn: true)
let name = user!.email?.components(separatedBy: "#")[0]
self.displayName = name!
print("The first call",Auth.auth().currentUser as Any)
}
else
{
// user must Sign in
self.signedInStatus(isSignedIn: false
self.loginSession()
}
}
}
}
the library
import UIKit
import Firebase
import FirebaseEmailAuthUI
import FirebaseAuthUI
import FirebaseGoogleAuthUI
the signout Method
#IBAction func signOut
try Auth.auth().signOut()
the UI
func showAlert(title: String, message: String) {
DispatchQueue.main.async {
let alert = UIAlertController(title: title, message: message, preferredStyle: .alert)
let dismissAction = UIAlertAction(title: "Dismiss", style: .destructive, handler: nil)
alert.addAction(dismissAction)
self.present(alert, animated: true, completion: nil)
}
}
When I tap the "signOut" button, the method gets called and gets executed. after the try Auth.auth().signOut() line of code gets executed, the current user is nil
(lldb) po Auth.auth().currentUser
nil
And if i login again
the Message will not Appear unless i rebuild the App

Implementation of a loading spinner in swift

I'm attempting to show a loading spinner when I'm doing some network calls when my app first starts up from being closed. These network calls usually take a very small amount of time because they are GETs on a json string and some processing on them, but if they take longer than usual, I don't want my users trying to maneuver in the app without the data they need being there. So, I'm trying to show a spinner when these calls are going on. But the spinner never shows up. I had this working before I changed a lot of stuff, and now it's not working again, and I can't for the life of me figure out why.
Here's my viewDidLoad() method in my HomeViewController, where this information is pulled from the API and loaded into CoreData.
override func viewDidLoad() {
super.viewDidLoad()
self.showSpinner(onView: self.view)
let teamsByConferenceNetworkManager = TeamsByConferenceNetworkManager()
teamsByConferenceNetworkManager.getTeamsByConference(completion: { (data, error) in
guard let data = data else {
os_log("Could not unwrap teamsByConference data in LoginViewController.viewDidLoad()", type: .debug)
self.removeSpinner()
let _ = UIAlertAction(title: "Network unavailable", style: .cancel, handler: { (alert) in
alert.isEnabled = true
})
return
}
let dataModelManager = DataModelManager.shared
DispatchQueue.main.sync {
dataModelManager.loadTeamNamesByConference(teamNamesByConferenceName: data)
dataModelManager.loadGamesFromCoreData()
}
if let _ = dataModelManager.allGames {
self.removeSpinner()
return
} else {
let gamesNetworkManager = GamesNetworkManager()
gamesNetworkManager.getGames { (data, error) in
guard let data = data else {
os_log("Could not unwrap games data in LoginViewController.viewDidLoad()", type: .debug)
self.removeSpinner()
let _ = UIAlertAction(title: "Network unavailable", style: .cancel, handler: { (alert) in
alert.isEnabled = true
})
return
}
DispatchQueue.main.sync {
dataModelManager.loadGames(gameApiResponses: data)
}
}
}
})
self.removeSpinner()
}
You need to remove this
DispatchQueue.main.sync {
dataModelManager.loadGames(gameApiResponses: data)
}
}
}
})
self.removeSpinner(). <<<<<< this line
}
as the call is asynchronous and you remove the spinner directly after you add it with self.showSpinner(onView: self.view)

Attempt to present ... on ... whose view is not in the window hierarchy

I am trying to perform a segue to a "Success window" when a payment has been correctly processed. I am trying to do this by using the:
self.performSegue(withIdentifier: "successView", sender: self)
inside my addCardViewController function. (shown here:)
func addCardViewController(_ addCardViewController: STPAddCardViewController, didCreateToken token: STPToken, completion: #escaping STPErrorBlock) {
// Monetary amounts on stripe are based on the lowest monetary unit (i.e. cents),
// therefore, we need to multiply the dollar amount by 100 to get the correct amount.
let stripeAmount = toPay * 100
// Call the 'stripeCharge' Firebase cloud function, with user's card token and amount
functions.httpsCallable("stripeCharge").call(["token": token.tokenId, "amount": String(stripeAmount)]) { (result, error) in
if let error = error {
print("Error: \(error)")
}
// Get the charge id after successful payment
var chargeId: String
if let data = result?.data as? [String: Any] {
chargeId = data["chargeId"] as? String ?? "no id"
print("Charge id: \(chargeId)")
//send new info
//show successfull payment view with charge
//self.present(self.successViewController, animated: true, completion: nil)
self.performSegue(withIdentifier: "successView", sender: self)
}
completion(nil)
//self.performSegue(withIdentifier: "successView", sender: self)
}
}
but I keep getting the error "Attempt to present ... on ... whose view is not in the window hierarchy"
Anyone knows why this is? here is a picture of the main.storyboard
here is a picture of the main.storyboard
Could be that you are not on the main thread? Usually the callback functions of network calls are off of the main thread. Unless you're sure that that's not the problem, try adding it:
DispatchQueue.main.async {
self.performSegue(withIdentifier: "successView", sender: self)
}

How can I inform the user with an alert from another Swift class and file

I have a Settingsviewcontroller.swift file that is connected to the storyboard and a separate file called connectionapi.swift.
Now, when a user clicks the checkbutton, the given username and password are checked against the API in the connectionapi class. Now when for instant the result generates an error I want to inform the user with an alert message. I am trying to find out for a few days how to do this, but I can't seem to find it. I keep ending up in errors.
Can someone please help me with some code examples:
the viewcontroler file
//
// ViewControllerSettings.swift
//
import Foundation
import UIKit
class ViewControllerSettings: UIViewController {
#IBOutlet weak var tGebruikersnaam: UITextField!
#IBOutlet weak var tCode: UITextField!
#IBOutlet weak var AnimatedImage: UIImageView!
#IBOutlet weak var lCopyright: UILabel!
#IBOutlet weak var lStatus: UILabel!
// declare a iCloud Store to save and load data from
var iCloudStore:NSUbiquitousKeyValueStore!
var userName:String = ""
var password:String = ""
let API = myAPI()
#IBAction func didTapOpslaan(_ sender: Any) {
if tGebruikersnaam.text == "" || tGebruikersnaam.text == nil || tCode.text == "" || tCode.text == nil {
return
}
print("Lets check username and password against the api")
let newUsername = "\(tGebruikersnaam.text!)"
let newCode = "\(tCode.text!)"
API.CheckUsernamePassword(username: newUsername, code: newCode) {
isValid in
print(isValid)
if isValid == true {
DispatchQueue.main.async {
print("The credentials are correct")
self.ShowAnimationOk()
}
}else {
DispatchQueue.main.async {
print("the credentials are wrong")
self.ShowAnimationNo()
}
}
}
}
// Code removed
}
The API Swift file
//
// ConnectAPI.swift
import Foundation
import UIKit
class myAPI{
let api_key = "b88a734f186sad"
let baseurl = "https://xxxx:443/xxxx/api"
func CheckUsernamePassword(username :String ,code:String, completion: #escaping (Bool)->() ) {
let urlString = "\(self.baseurl)/accounts/validateusernamepassword.json?username=\(username)&password=\(code)&api_key=\(self.api_key)"
let url = URL(string: urlString)
URLSession.shared.dataTask(with:url!) { (data, response, error) in
if error != nil {
print("API | Error URLSession : \(error!)")
completion(false)
} else {
do {
let parsedData = try JSONSerialization.jsonObject(with: data!, options: []) as! [String:Any]
print("----------")
if parsedData["validated"] != nil {
if "\(parsedData["validated"]!)" == "1" {
print("API JSON | validated = \(parsedData["validated"]!)")
print("API JSON | message = \(parsedData["message"]!)")
completion(true)
}else {
print("Credential Check not valid")
print("API JSON | validated = \(parsedData["validated"]!)")
print("API JSON | message = \(parsedData["message"]!)")
completion(false)
}
}else{
print("Json Parse error: \(parsedData)")
// Raise a Alert here
}
} catch let error as NSError {
print("API | Error Parsing JSON \(error)" )
// Raise a Alert here
// main.showAlert(message: "API | Error Parsing JSON \(error)")
//A error occured when checking credentials, try again later.
completion(false)
}
}
}.resume()
}
I'm not sure what's your problem is. I assume that you have two problems:
you don't know how to pass error message from your API class to
the ViewControllerSettings class.
you don't know how to show alert in iOS before
If that's the case, let's look at this.
1. Passing error message
You can see that the completion block (e.g., #escaping (Bool) -> ()) will be called after the function checkUsernamePassword done with the URLSession's dataTask:
func CheckUsernamePassword(username :String ,code:String, completion: #escaping (Bool)->() ) { ... }
If success, you call the block with value true
completion(true)
in other cases you call with value false
completion(false)
To pass more informations (e.g. error message), you can simply add input parameter at the completion block. Better added names for each one so it's more clear. E.g., change it to:
..., completion: #escaping (valid: Bool, errorMessage: String?) -> ())
Then, if the API succeeds, you call it without errorMessage:
completion(valid: true, errorMessage: nil)
And if it's error, you pass either your own error message or get it from NSError
completion(valid: false, errorMessage: "Credential check not valid.")
2. Show the error in UIAlertController
First make a utility function that shows standard alert in your ViewControllerSettings:
// Simple Alert UI
func showAlert(title: String, message: String) {
let actionSheetController: UIAlertController = UIAlertController(title: title, message: message, preferredStyle: .alert)
// add close button
let cancelAction: UIAlertAction = UIAlertAction(title: "Close", style: .cancel) { _ in }
actionSheetController.addAction(cancelAction)
// show on self
self.present(actionSheetController, animated: true, completion: nil)
}
If you did the first step, you have accessed to your error message at call site (ViewController).
When you call the API, errorMessage is available, just show it with alert:
// didTap...
API.CheckUsernamePassword(username: newUsername, code: newCode) {
(isValid, errorMessage) in
if isValid {
...
} else {
let error = errorMessage! // I don't recommend forced-casting though
self.showAlert(title: "Error!", message: error) // show Alert UI
}
}

If user does not exist should display an error Message with UIAlert

I am currently working on a IOS App using Swift 3. I am working on the login system. Verification of logging in works fine. The logic is that, if login succeeds, it goes to the next Screen. However, if user does not exist, it should display an error message with UIAlert. But when I try to display the UIAlert, i get an error that says "Assertion failure in -[UIKeyboardTaskQueue waitUntilAllTasksAreFinished]"
//Getting data from database
func getData() -> Void {
let url: String = "http://localhost/fridge_app/login.php" //this will be changed to the path where service.php lives
//created NSURL
let requestURL = NSURL(string: url)
//creating NSMutableURLRequest
var request = URLRequest(url: requestURL! as URL)
//setting the method to post
request.httpMethod = "POST"
//Getting values from textfield
let usernameVal = username.text
let passwordVal = password.text
//creating the post parameter by concatenating the keys and values from text field
let postString = "username=\(usernameVal!)&password=\(passwordVal!)";
print(postString)
request.httpBody = postString.data(using: String.Encoding.utf8)
//creating a task to send the post request
let task = URLSession.shared.dataTask(with: request as URLRequest){
data, response, error in
//exiting if there is some error
if error != nil{
print("error is \(error)")
return;
}
// Print out response string
var responseString: NSString?;
responseString = NSString(data: data!, encoding: String.Encoding.utf8.rawValue)
if(responseString == "invalid"){
self.isValid = false;
print(self.isValid)
}
if self.checkLogin(data: responseString!) == true {
self.performSegue(withIdentifier: "profileViewController", sender: self)
}
else{
print("Hello")
// It prints hello fine, but when it tries to run the showAlert function it fails
self.showAlert()
}
//print("responseString = \(self.responseString)")
}
//executing the task
task.resume()
}
This is the alert function
/*
* Show UIAlert Message
*/
func showAlert() -> Void{
let alert = UIAlertController(title: "User Does Not Exist",
message: "",
preferredStyle: UIAlertControllerStyle.alert)
let loginFail = UIAlertAction(title: "Close", style: .default, handler: nil);
alert.addAction(loginFail);
present(alert, animated: true)
}
This is method is called when user clicks login.
Unless you take special steps, the completion handlers for the tasks you submit to NSURLSession get run on a background thread. That means that any UI calls you do must be sent to the main thread or they don't work correctly (And may crash your app.)
The code in your completion handler is doing more UI work than just invoking an alert. You're also invoking a segue. If your completion handler doesn't do time-consuming work you might want to wrap the whole thing in a GCD call to the main thread:
DispatchQueue.main.async() {
//Put the entire body of your completion handler in here
}
Otherwise, you'll need to individually wrap each UI call in a call to the main queue like above.
EDIT:
Looking at your specific completion handler, you have one if/then/else block that does UIKit calls:
if self.checkLogin(data: responseString!) {
self.performSegue(withIdentifier: "profileViewController",
sender: self)
} else {
print("Hello")
self.showAlert()
}
So just wrap that part in a call to the main queue:
DispatchQueue.main.async() {
if self.checkLogin(data: responseString!) {
self.performSegue(withIdentifier: "profileViewController",
sender: self)
} else {
print("Hello")
self.showAlert()
}
}

Resources