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)
}
Related
I am trying to jump to a second view controller after I have authorized a user's TouchID. I am able to validate that the TouchID is working but I am having an issue of jumping to a second viewController.
I have created a SecondViewController and a Segue with the Identifier "dispenseScreen". However, whenever I try to jump to the second screen my program crashes.
#IBAction func touchID(_ sender: Any)
{
let context:LAContext = LAContext()
//Removes Enter Password during failed TouchID
context.localizedFallbackTitle = ""
if context.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: nil)
{
context.evaluatePolicy(LAPolicy.deviceOwnerAuthenticationWithBiometrics, localizedReason: "We require your TouchID", reply: { (wasCorrect, error) in
self.isBiometryReady()
if wasCorrect {
self.performSegue(withIdentifier: "dispenseScreen", sender: self)
print("Correct")
}
else {
print("Incorrect")
}
})
} else {
//Enter phone password if too many login attempts
//Add message alerting user that TouchID is not enabled
}
}
There are no semantic errors in my code but I am receiving a threading error when I try to go to the second view controller.
You're trying to do the segue in the callback from evaluatePolicy. For anything involving UI, you need to make sure you're on the main thread: (wasCorrect, error) in DispatchQueue.main.async { ... }
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
}
}
}
}
I am trying to upload a product to Database, and I want all information to be written in one transaction. While this does happen, it doesn't for an uploaded image. This is my code :
let storageRef = Storage.storage().reference().child("ProductsImages").child(product.UniqueID()).child("MainImage.png")
if let mainChosenImage = self.selectedImageToUpload
{
if let uploadData = UIImageJPEGRepresentation(mainChosenImage, 0.2)
{
storageRef.putData(uploadData, metadata: nil)
{
(StorageMetaData, error) in
if error != nil
{
// MARK - Print error
return
}
self.mainImageURL = StorageMetaData?.downloadURL()?.absoluteString
if let urlString = self.mainImageURL
{
self.ref.child("Products").child(product.UniqueID()).child("MainImage").setValue(urlString)
self.ref.child("Users").child(user.uid).child("Products").child(product.UniqueID()).child("MainImage").setValue(urlString)
product.AddImageURLToProduct(URL: urlString)
}
}
}
}
product.RegisterProductOnDatabase(database: self.ref)
self.performSegue(withIdentifier: "unwindToMyProductsViewController", sender: self)
My code for registering the product:
public func RegisterProductOnDatabase(database dataBase: DatabaseReference)
{
// Run in one transaction
let key = dataBase.child("Products").child(self.UniqueID()).key
let thisProductToRegister : [String:Any] = [
"Name": self.Name(),
"UniqueID": self.UniqueID(),
"Price": self.Price(),
"Description": self.Description(),
"ToBuy?": self.IsToBuy(),
"ToSell?": self.IsToSell(),
"Owner": self.m_Owner,
"Amount": self.Amount(),
"MainImage": self.m_PicturesURLs.first
]
let childUpdates = ["/Products/\(key)/": thisProductToRegister,
"/Users/\(self.m_Owner)/Products/\(key)/": thisProductToRegister]
dataBase.updateChildValues(childUpdates)
}
I want the complete product to be registered before the segue is performed. How can I do that ?
As of right now, product is registered, segue is performed and product is loaded to CollectionView with default image, then product image is written to Firebase and then loaded to collectionView. I want my product to load with the correct image from the start
The idea is to nest network calls , and in final one perfromSegue
CallAPI1
{
if(sucess)
{
CallAPI2
{
if(sucess)
{
self.performSegue(withIdentifier: "unwindToMyProductsViewController", sender: self)
}
}
}
}
You want the segue to happen when RegisterProductOnDatabase is finished with its asynchronous calls, so add a parameter to give it its own completion callback, and call it when all the asynchronous work is done:
public func RegisterProductOnDatabase(database dataBase: DatabaseReference, completionHandler: #escaping () -> Void) {
// all your code here
completionHandler()
}
Then call it like this:
product.RegisterProductOnDatabase(database: self.ref, completionHandler: {
self.performSegue(withIdentifier: "unwindToMyProductsViewController", sender: self)
})
I have just started using Digits - Twitter API for Phone Number verification, but it seems I'm unable to read the user's Phone number, I'm not sure if there is a function for that or so, but after reading a while I knew that I can do that with a Call back after successful phone verification but no explanation for that !
AuthConfig.Builder authConfigBuilder = new AuthConfig.Builder()
.withAuthCallBack(callback)
.withPhoneNumber(phoneNumberOrCountryCodeFromMyActivity)
found this snippet but again not sure where to implement it.
HERE is my Action for the login button with phone verification:
fileprivate func navigateToMainAppScreen() {
performSegue(withIdentifier: "signedIn", sender: self)
}
#IBAction func tapped(_ sender: Any) {
let configuration = DGTAuthenticationConfiguration(accountFields: .defaultOptionMask)
configuration?.appearance = DGTAppearance()
configuration?.appearance.backgroundColor = UIColor.white
configuration?.appearance.accentColor = UIColor.red
// Start the Digits authentication flow with the custom appearance.
Digits.sharedInstance().authenticate(with: nil, configuration:configuration!) { (session, error) in
if session != nil {
// Navigate to the main app screen to select a theme.
self.navigateToMainAppScreen()
} else {
print("Error")
}
}
}
So I found the answer after digging a lot more in Digits Documentations and it was pretty simple, I had to add:
print(session.phoneNumber)
print(session.userID)
In the didTap function, so the complete code will be:
#IBAction func tapped(_ sender: Any) {
let configuration = DGTAuthenticationConfiguration(accountFields: .defaultOptionMask)
configuration?.appearance = DGTAppearance()
configuration?.appearance.backgroundColor = UIColor.white
configuration?.appearance.accentColor = UIColor.red
// Start the Digits authentication flow with the custom appearance.
Digits.sharedInstance().authenticate(with: nil, configuration:configuration!) { (session, error) in
if session != nil {
//Print Data
print(session?.phoneNumber)
print(session?.userID)
// Navigate to the main app screen to select a theme.
self.navigateToMainAppScreen()
} else {
print("Error")
}
}
}
Here is the Reference I have used:
https://docs.fabric.io/apple/examples/cannonball/index.html#sign-in-with-digits
I am using Firebase for Login/Sign Up authentication but I ran into a problem. I got everything to set up and it works fine, but I am having a bit of an issue with the login part.
Here's my code:
#IBAction func clickLogin(_ sender: UIButton) {
FIRAuth.auth()?.signIn(withEmail: emailTextField.text!, password: passwordTextField.text!, completion: { (user, error) in
if let error = error {
print(error.localizedDescription)
}
})
performSegue(withIdentifier: "toMainSegue", sender: self) //Issue
}
What's wrong is that when the email or the password is incorrect, it will still perform the segue. I tried:
#IBAction func clickLogin(_ sender: UIButton) {
FIRAuth.auth()?.signIn(withEmail: emailTextField.text!, password: passwordTextField.text!, completion: { (user, error) in
if let error = error {
print(error.localizedDescription)
} else {
performSegue(withIdentifier: "toMainSegue", sender: self) //Error Line
}
})
But I get an error:
Implicit use of ‘self’ in closure, use ‘self.’ to capture semantics explicit.
Is there a better way of bring the user to the next UI if and only if login was successful?
In the code that you have shared
#IBAction func clickLogin(_ sender: UIButton) {
FIRAuth.auth()?.signIn(withEmail: emailTextField.text!, password: passwordTextField.text!, completion: { (user, error) in
if let error = error {
print(error.localizedDescription)
}
})
performSegue(withIdentifier: "toMainSegue", sender: self) //Issue
}
The performSegue(withIdentifier:sender:) method is being called within the #IBAction and not inside the completion handler of the signIn(withEmail:password:completion) method. Thus, regardless of what is written or executed in the latter, your performSegue(withIdentifier:sender:) will be called. Try modifying the code to the following
#IBAction func clickLogin(_ sender: UIButton) {
FIRAuth.auth()?.signIn(withEmail: emailTextField.text!, password: passwordTextField.text!, completion: { (user, error) in
if let error = error {
print(error.localizedDescription)
} else {
self.performSegue(withIdentifier: "toMainSegue", sender: self)
}
})
}
Keep in mind that, because the logic is being executed in a closure, you need to specify the self. prefix before methods and variables!
Any variables or methods used inside of block needs to use of ‘self’.
#IBAction func clickLogin(_ sender: UIButton) {
FIRAuth.auth()?.signIn(withEmail: emailTextField.text!, password: passwordTextField.text!, completion: { (user, error) in
if let error = error {
print(error.localizedDescription)
} else {
self.performSegue(withIdentifier: "toMainSegue", sender: self) //Error Line
}
})
Your code goes in the else part everytime your API hits successfully even if the login credentials are wrong.
The FIRAuth API must be returning some data when it gets hit, for example a string or dictionary named "success" = 1 or 0. Check in ur else part for the success to be true or false. false being wrong credentials and true being correct credentials.
The error part gets executed when there is any error in hitting the API itself like network error or the API's parameters being in wrong format or any other error.
In your case its getting hit and returning a result too. You have to check the result dictionary if your user did get logged in or not and segue onto the next controller based on that result.
Try this. This is how I do my login.
FIRAuth.auth()?.signIn(withEmail: emailField.text!, password: passwordField.text!, completion: { user, error in
if error == nil {
print("Successfully Logged IN \(user!)")
self.performSegue(withIdentifier: "signedIn", sender: self)
}
})
This just tests if there is no error with the signing in process, then performs the segue. I haven't had any trouble with it, and it seems to work great.