Swift: Hold code execution until API response received - swift

I need to set up authorization screen:
1) Login and pass is inputted
2) App requests token from API
3) Response is received
4) App checks if response contains token: if contains, should perform authorization
PROBLEM TO SOLVE: app makes 4) before step 3)
What I tried: I used escaping closure in order to hold the execution until API data is received, but it helped only partially - it helped to hold only assignment of value to token, but I cannot hold 'if checks' still.
override func shouldPerformSegue(withIdentifier identifier: String, sender: Any?) -> Bool {
if identifier == "loginSegue" {
let login = loginInput.text!
let password = passInput.text!
tokenLoader.getToken(login: login, password: password) { [weak self] token in
self?.token = token.access_token
}
//THIS CODE I NEED TO HOLD UNTIL DATA FROM API RECEIVED
if token != "" {
return true
} else {
let alert = UIAlertController(title: nil, message: "Incorrect password", preferredStyle: .alert)
let action = UIAlertAction(title: "OK", style: .cancel, handler: nil)
alert.addAction(action)
present(alert, animated: true, completion: nil)
return false
}
//
} else {
return true
}
}
class TokenLoader {
let session = Session.instance
let baseUrl = "http://46.254.18.193:9096"
func getToken(login: String, password: String, completion: #escaping (AccessToken) -> Void) {
let path = "/token"
let parameters: Parameters = [
"grant_type": "client_credentials",
"client_id": login,
"client_secret": password,
"scope": "read"
]
let url = baseUrl+path
AF.request(url, method: .get, parameters: parameters).responseData { response in
do {
let token = try JSONDecoder().decode(AccessToken.self, from: response.value!)
print(token)
self.session.token = token.access_token
completion(token)
} catch {
print(error)
}
}
}
}

You can resolve the issue using this function ... perform it on event when you want to segue or to get token
func getTokenAndPerformSegue() {
let login = loginInput.text
let password = passInput.text
if let getLogin = login ,let getPassword = password {
tokenLoader.getToken(login: getLogin, password: getPassword) { [weak self] token in
self?.token = token.access_token
if token.isEmpty() {
let alert = UIAlertController(title: nil, message: "Incorrect password", preferredStyle: .alert)
let action = UIAlertAction(title: "OK", style: .cancel, handler: nil)
alert.addAction(action)
present(alert, animated: true, completion: nil)
} else {
performSegue(withIdentifier: "loginSegue", sender: nil)
}
}
}
}
you need to write it like this ... to return nil or error in completion
func getToken(login: String, password: String, completion: #escaping (AccessToken?) -> Void) {
let path = "/token"
let parameters: Parameters = [
"grant_type": "client_credentials",
"client_id": login,
"client_secret": password,
"scope": "read"
]
let url = baseUrl+path
AF.request(url, method: .get, parameters: parameters).responseData { response in
do {
let token = try JSONDecoder().decode(AccessToken.self, from: response.value!)
print(token)
self.session.token = token.access_token
completion(token)
} catch {
completion(nil)
print(error)
}
}
}
Let me know if you did not get anything or you need further help

Related

How to add another key/value to Firebase Array

The problem that I'm facing is that I have successfully created the array and have displayed the values like so:
Users
-uid
- Name: Example
- Profile Pic URL: example12345
- email: example#example.co.uk
However, in another swift file I have successfully generated a personality type and am struggling to add this to the array so that I end up with something that looks like this:
Users
-uid
- Name: Example
- Profile Pic URL:
- email: example#example.co.uk
- personality type: INTJ
I have tried copying the code from the previous swift class to no avail
This is the code for the working firebase array
#IBAction func createAccountAction(_ sender: AnyObject) {
let usersRef = Database.database().reference().child("Users")
let userDictionary : NSDictionary = ["email" : emailTextField.text!, "Name": nameTextField.text!]
if emailTextField.text == "" {
let alertController = UIAlertController(title: "Error", message: "Please enter your email and password", preferredStyle: .alert)
let defaultAction = UIAlertAction(title: "OK", style: .cancel, handler: nil)
alertController.addAction(defaultAction)
self.present(alertController, animated: true, completion: nil)
} else {
Auth.auth().createUser(withEmail: self.emailTextField.text ?? "", password: self.passwordTextField.text ?? "") { (result, error) in
if error != nil {
let alertController = UIAlertController(title: "Error", message: error?.localizedDescription, preferredStyle: .alert)
let defaultAction = UIAlertAction(title: "OK", style: .cancel, handler: nil)
alertController.addAction(defaultAction)
self.present(alertController, animated: true, completion: nil)
return
}
guard let user = result?.user else { return }
let vc = self.storyboard?.instantiateViewController(withIdentifier: "ViewController") as! ViewController
self.present(vc, animated: true, completion: nil)
// HERE YOU SET THE VALUES
usersRef.child(user.uid).setValue(userDictionary, withCompletionBlock: { (error, ref) in
if error != nil { print(error); return }
let imageName = NSUUID().uuidString
let storageRef = Storage.storage().reference().child("profile_images").child("\(imageName).png")
if let profileImageUrl = self.profilePicture.image, let uploadData = UIImageJPEGRepresentation(self.profilePicture.image!, 0.1) {
storageRef.putData(uploadData, metadata: nil, completion: { (metadata, error) in
if error != nil, metadata != nil {
print(error ?? "")
return
}
storageRef.downloadURL(completion: { (url, error) in
if error != nil {
print(error!.localizedDescription)
return
}
if let profileImageUrl = url?.absoluteString {
self.addImageURLToDatabase(uid: user.uid, values: ["profile photo URL": profileImageUrl as AnyObject])
}
})
})
}
}
)}
}
}
This is the other swift file function which generates the personality type which I would like to add to the array
#IBAction func JPbtn(_ sender: Any) {
if (Judging < Perceiving){
Result3 = "P"
} else {
Result3 = "J"
}
let PersonalityType = "\(Result) \(Result1) \(Result2) \(Result3)"
print(PersonalityType)
let vc = self.storyboard?.instantiateViewController(withIdentifier: "Example") as! ViewController
self.present(vc, animated: true, completion: nil)
}
So if you are just trying to add a new key with a value, all you need to do is create a new reference like this.
guard let currentUserUID = Auth.auth().currentUser?.uid else { return }
print(currentUserUID)
let userPersonalityRef = Database.database().reference().child("users").child(currentUserUID).child("personality")
userPersonalityRef.setValue("Some Value")
When you set the value it can also be a dictionary if you want. But if your users don't all have personality make sure it optional on your data model or else It might crash your app. When you are getting your user from firebase.

Alamofire request doesn't work inside a method but the same request works inside a controller | Swift 3.0

In my app I've got one class called User and this class has the method "login", the method login is only a request to my API but it doesn't work when I call it from the controller. If I copy the same code into my controller it works.
This is my method:
func login () -> Void {
Alamofire.request("myUrl", method: .post , parameters: ["nick": "myNick", "pass" : "myPass"] , encoding: JSONEncoding.default, headers: nil).responseJSON{ response in
let status = response.response?.statusCode as! Int
switch status{
case 200:
let data = response.data
let utf8Text = String(data: data!, encoding: .utf8)
self.token = utf8Text
break
default:
break
}
}
}
This is my controller:
#IBAction func loginFunc(_ sender: Any) {
var usuario = User(nick:"myNick", pass:"myPass",token:nil)
usuario.login()
if usuario.token == nil{
let alert = UIAlertController(title: "¡Vaya!", message: "Ese nombre de usuario o esa contraseña son incorrectas", preferredStyle: UIAlertControllerStyle.alert)
alert.addAction(UIAlertAction(title: "Aceptar", style: UIAlertActionStyle.default, handler: nil))
self.present(alert, animated: true, completion: nil)
}
print(usuario.token!)
}
It always print nil but the server return the correct token
You have to use completion block :
func login (completion : ()->()) {
Alamofire.request("myUrl", method: .post , parameters: ["nick": "myNick", "pass" : "myPass"] , encoding: JSONEncoding.default, headers: nil).responseJSON{ response in
let status = response.response?.statusCode as! Int
switch status{
case 200:
let data = response.data
let utf8Text = String(data: data!, encoding: .utf8)
self.token = utf8Text
break
default:
break
}
completion()
}
}
#IBAction func loginFunc(_ sender: Any) {
var usuario = User(nick:"myNick", pass:"myPass",token:nil)
usuario.login {
if usuario.token == nil{
let alert = UIAlertController(title: "¡Vaya!", message: "Ese nombre de usuario o esa contraseña son incorrectas", preferredStyle: UIAlertControllerStyle.alert)
alert.addAction(UIAlertAction(title: "Aceptar", style: UIAlertActionStyle.default, handler: nil))
self.present(alert, animated: true, completion: nil)
}
print(usuario.token!)
}
}
func login (_ completion: ((Response) -> Void)) -> Void {
Alamofire.request("myUrl", method: .post , parameters: ["nick": "myNick", "pass" : "myPass"] , encoding: JSONEncoding.default, headers: nil).responseJSON(completion)
}
Now you can show your alert controller like this:
#IBAction func loginFunc(_ sender: Any) {
var usuario = User(nick:"myNick", pass:"myPass",token:nil)
usuario.login { [weak self] response in
guard let strongSelf = self else { return }
let status = response.response?.statusCode as! Int
switch status{
case 200:
let data = response.data
let utf8Text = String(data: data!, encoding: .utf8)
strongSelf.usuario.token = utf8Text
break
default:
break
}
print(usuario.token!)
if usuario.token == nil {
let alert = UIAlertController(title: "¡Vaya!", message: "Ese nombre de usuario o esa contraseña son incorrectas", preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "Aceptar", style:
UIAlertActionStyle.default))
strongSelf.present(alert, animated: true)
}
}
}

ApplePaybutton is not functioning (ApplePay integration with Stripe)

I am trying to integrate Applepay using Stripe so that users are able to make easy payment. I use heroku account as a backend server. I could add the ApplePay Button on the UI (Please take a look at My Current Storyboard) and tried to configure a pop up check-out window coming from bottom for users to make payment.
My Current UI
However, even if I click the ApplePaybutton, nothing happens. My goal now is making the checkout window when users click the ApplePay Button My Goal UI.
I assume that func beginPayment() is supposed to be called when the button is pressed but doesn't work. I suspect that if (stripePublishableKey == "") { and codes after that is set incorrectly. Any help would be appreciated!
import UIKit
import Stripe
enum STPBackendChargeResult {
case success, failure
}
typealias STPTokenSubmissionHandler = (STPBackendChargeResult?, NSError?) -> Void
class ViewController: UIViewController, PKPaymentAuthorizationViewControllerDelegate {
let stripePublishableKey = "my Stripe PublishableKey"
let backendChargeURLString = "my heroku URL"
let appleMerchantId = "my apple merchand Id"
let shirtPrice : UInt = 1000 // this is in cents
override func viewDidLoad() {
super.viewDidLoad()
//This is the method of making the ApplePayButton( didn't use storyboard)
let button = PKPaymentButton(type: .buy, style: .black)
button.addTarget(self, action: #selector(ViewController.beginPayment(_:)), for: .touchUpInside)
let bw = button.frame.size.width
let bh = button.frame.size.height
let vw = view.frame.size.width
let vh = view.frame.size.height
button.frame = CGRect(origin: CGPoint(x: vw/2 - bw/2, y: vh/2 - bh/2), size: button.frame.size)
view.addSubview(button)
}
//This func is supposed to be called when ApplePayButton is pressed.
func beginPayment(_: UIButton) {
if (stripePublishableKey == "") {
let alert = UIAlertController(
title: "You need to set your Stripe publishable key.",
message: "You can find your publishable key at https://dashboard.stripe.com/account/apikeys .",
preferredStyle: UIAlertControllerStyle.alert
)
let action = UIAlertAction(title: "OK", style: UIAlertActionStyle.default, handler: nil)
alert.addAction(action)
present(alert, animated: true, completion: nil)
return
}
if (appleMerchantId != "") {
let paymentRequest = Stripe.paymentRequest(withMerchantIdentifier: appleMerchantId)
if Stripe.canSubmitPaymentRequest(paymentRequest) {
paymentRequest.paymentSummaryItems = [PKPaymentSummaryItem(label: "Cool shirt", amount: NSDecimalNumber(string: "10.00")), PKPaymentSummaryItem(label: "Stripe shirt shop", amount: NSDecimalNumber(string: "10.00"))]
let paymentAuthVC = PKPaymentAuthorizationViewController(paymentRequest: paymentRequest)
paymentAuthVC.delegate = self
self.present(paymentAuthVC, animated: true, completion: nil)
return
}
} else {
print("You should set an appleMerchantId.")
}
}
func paymentAuthorizationViewController(_ controller: PKPaymentAuthorizationViewController, didAuthorizePayment payment: PKPayment, completion: #escaping ((PKPaymentAuthorizationStatus) -> Void)) {
let apiClient = STPAPIClient(publishableKey: stripePublishableKey)
apiClient.createToken(with: payment, completion: { (token, error) -> Void in
if error == nil {
if let token = token {
self.createBackendChargeWithToken(token, completion: { (result, error) -> Void in
if result == STPBackendChargeResult.success {
completion(PKPaymentAuthorizationStatus.success)
}
else {
completion(PKPaymentAuthorizationStatus.failure)
}
})
}
}
else {
completion(PKPaymentAuthorizationStatus.failure)
}
})
}
func paymentAuthorizationViewControllerDidFinish(_ controller: PKPaymentAuthorizationViewController) {
dismiss(animated: true, completion: nil)
}
func createBackendChargeWithToken(_ token: STPToken, completion: #escaping STPTokenSubmissionHandler) {
if backendChargeURLString != "" {
if let url = URL(string: backendChargeURLString + "/charge") {
let session = URLSession(configuration: URLSessionConfiguration.default)
let request = NSMutableURLRequest(url: url)
request.httpMethod = "POST"
let postBody = "stripeToken=\(token.tokenId)&amount=\(shirtPrice)"
let postData = postBody.data(using: String.Encoding.utf8, allowLossyConversion: false)
session.uploadTask(with: request as URLRequest, from: postData, completionHandler: { data, response, error in
let successfulResponse = (response as? HTTPURLResponse)?.statusCode == 200
if successfulResponse && error == nil {
completion(.success, nil)
} else {
if error != nil {
completion(.failure, error as NSError?)
} else {
completion(.failure, NSError(domain: StripeDomain, code: 50, userInfo: [NSLocalizedDescriptionKey: "There was an error communicating with your payment backend."]))
}
}
}).resume()
return
}
}
completion(STPBackendChargeResult.failure, NSError(domain: StripeDomain, code: 50, userInfo: [NSLocalizedDescriptionKey: "You created a token! Its value is \(token.tokenId). Now configure your backend to accept this token and complete a charge."]))
}
}

Can't get account info Parse & FB SDK

I'm using the ParseFacebookUtilsV4 and the fb idk seem to be having issues with retrieving the users info just to print into the console for now. The issue is that it seems that the block to start the request isn't being executed since whenever i debug it just seems to skip the start completion handler.
// View controller code
PFFacebookUtils.facebookLoginManager().loginBehavior = .web
var loginTask = PFFacebookUtils.logInInBackground(withReadPermissions: [])
loginTask.continue( { (bfTask) -> Any? in
print("I'm here")
let request = FBSDKGraphRequest(graphPath:"me", parameters: ["fields":"id,email,name,first_name,last_name,picture"] )
print(request)
request?.start {
(connection, result, error) in
print(result)
}
return ""
})
// App delegate config
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
let configuration = ParseClientConfiguration {
$0.applicationId = "xxxx"
$0.clientKey = "xxxxx"
$0.server = "https://parseapi.back4app.com"
}
Parse.initialize(with: configuration)
PFFacebookUtils.initializeFacebook(applicationLaunchOptions: launchOptions)
PFTwitterUtils.initialize(withConsumerKey: "xxxxx", consumerSecret: "xxxxx")
// Override point for customization after application launch.
return true
}
here's the func I use for the Facebook request. I am using a block request:
func loadFacebookUserDetails() {
// Define fields we would like to read from Facebook User object
let requestParameters = ["fields": "id, email, first_name, last_name, name"]
// Send Facebook Graph API Request for /me
let userDetails = FBSDKGraphRequest(graphPath: "me", parameters: requestParameters)
userDetails.startWithCompletionHandler({
(connection, result, error: NSError!) -> Void in
if error != nil {
let userMessage = error!.localizedDescription
let myAlert = UIAlertController(title: "Alert", message: userMessage, preferredStyle: UIAlertControllerStyle.Alert)
let okAction = UIAlertAction(title: "Ok", style: UIAlertActionStyle.Default, handler: nil)
myAlert.addAction(okAction)
self.presentViewController(myAlert, animated: true, completion: nil)
PFUser.logOut()
return
}
// Extract user fields
let userId:String = result.objectForKey("id") as! String
let userEmail:String? = result.objectForKey("email") as? String
let userFirstName:String? = result.objectForKey("first_name") as? String
let userLastName:String? = result.objectForKey("last_name") as? String
// Get Facebook profile picture
let userProfile = "https://graph.facebook.com/" + userId + "/picture?type=large"
let profilePictureUrl = NSURL(string: userProfile)
let profilePictureData = NSData(contentsOfURL: profilePictureUrl!)
// Prepare PFUser object
if(profilePictureData != nil)
{
let profileFileObject = PFFile(name:"profilePic.jpeg",data:profilePictureData!)
PFUser.currentUser()?.setObject(profileFileObject!, forKey: "ProfilePic")
}
PFUser.currentUser()?.setObject(userFirstName!, forKey: "Name")
PFUser.currentUser()?.setObject(userLastName!, forKey: "last_name")
if let userEmail = userEmail
{
PFUser.currentUser()?.email = userEmail
PFUser.currentUser()?.username = userEmail
}
PFUser.currentUser()?.saveInBackgroundWithBlock({ (success, error) -> Void in
if(error != nil)
{
let userMessage = error!.localizedDescription
let myAlert = UIAlertController(title: "Alert", message: userMessage, preferredStyle: UIAlertControllerStyle.Alert)
let okAction = UIAlertAction(title: "Ok", style: UIAlertActionStyle.Default, handler: nil)
myAlert.addAction(okAction)
self.presentViewController(myAlert, animated: true, completion: nil)
PFUser.logOut()
return
}
if(success)
{
if !userId.isEmpty
{
NSUserDefaults.standardUserDefaults().setObject(userId, forKey: "user_name")
NSUserDefaults.standardUserDefaults().synchronize()
dispatch_async(dispatch_get_main_queue()) {
self.dismissViewControllerAnimated(true, completion: nil)
}
}
}
})
})
}

Heimdallr.swift can not use access token

I am using the Heimdallr.swift repository in my swift app to login with OAuth2 Password grant. But after getting the "success" message, i can still not access the protected resources. Do anyone know how to save the token that you receive or what the problem might be?
#IBAction func loginButton(sender: UIButton) {
let username: String = usernameTextfield.text!;
let password: String = passwordTextfield.text!;
let tokenURL = NSURL(string: "http://challyme.dk:8888/index.php/api/v1.1/oauth/access_token")!
let identifier = "id0"
let secret = "secret0"
let credentials = OAuthClientCredentials(id: identifier, secret: secret)
let heimdall = Heimdallr(tokenURL: tokenURL, credentials: credentials)
heimdall.requestAccessToken(username: username, password: password) { result in
switch result {
case .Success:
self.callUserInfo(heimdall)
dispatch_async(dispatch_get_main_queue()) {
let mainStoryboard = UIStoryboard(name: "Main", bundle: NSBundle.mainBundle())
let vc : UIViewController = mainStoryboard.instantiateViewControllerWithIdentifier("LogedInView") as UIViewController
self.presentViewController(vc, animated: true, completion: nil)
}
case .Failure:
dispatch_async(dispatch_get_main_queue()) {
print("Wrong password or username")
let alertView = UIAlertController(title: "Alert", message: "You entered the wrong username or password", preferredStyle: .Alert)
alertView.addAction(UIAlertAction(title: "Ok", style: .Default, handler: nil))
self.presentViewController(alertView, animated: true, completion: nil)
}
}
}
}
The method that should display the protected resources:
func callUserInfo(heimdall: Heimdallr) {
let urlPath = "http://linkToResources"
let url: NSURL = NSURL(string: urlPath)!
let session = NSURLSession.sharedSession()
let request = NSMutableURLRequest(URL: url)
heimdall.authenticateRequest(request, completion: { result in
switch result {
case .Success(let request):
let task = session.dataTaskWithRequest(request) { data, response, error in
let json = JSON(data: data!);
print(response!.description);
if response != nil {
print(json);
} else {
print(json[999].error) // "Array[999] is out of bounds"
}
}
task.resume()
case .Failure:
print("failure")
}
})
}
Best regards
Anders B. Christensen