I am relatively new to integrating APIs into Swift. I have a login page and I am trying to authenticate a user and create a session when they sign in. I found a code online that supposedly works for a lot of people, but I am having trouble actually getting the whole thing to work. I have concluded that something is going wrong beginning near this line which you will see in the code below because at the line before it I am able to get the auth token.
if let data_block = server_response["accessToken"] as? NSDictionary
I am including all of the relevant code and I would like to know what is going wrong, how to successfully authenticate and create the session, and get the segue to actually work.
override func viewDidLoad() {
super.viewDidLoad()
_login_button = "Login"
let preferences = UserDefaults.standard
if(preferences.object(forKey: "session") != nil)
{
LoginDone()
}
else
{
LoginToDo()
}
}
#IBAction func signIn() {
let username = emailLoginTextField.text
let password = passwordLoginTextField.text
if(username == "" || password == "")
{
return
}
if(_login_button == "Logout")
{
let preferences = UserDefaults.standard
preferences.removeObject(forKey: "session")
LoginToDo()
return
} else {
DoLogin(username!, password!)
}
}
func DoLogin(_ user:String, _ psw:String)
{
let url = URL(string: "http://agile-castle-10059.herokuapp.com/authentication")
let session = URLSession.shared
let request = NSMutableURLRequest(url: url!)
request.httpMethod = "POST"
let paramToSend = "username=" + user + "&password=" + psw + "&strategy=" + "local"
request.httpBody = paramToSend.data(using: String.Encoding.utf8)
let task = session.dataTask(with: request as URLRequest, completionHandler: {
(data, response, error) in
guard let _:Data = data else
{
return
}
let json:Any?
do
{
json = try JSONSerialization.jsonObject(with: data!, options: [])
}
catch
{
return
}
guard let server_response = json as? NSDictionary else
{
return
}
//problem occurs around here, nothing happens (assuming the data_block isn't being created for some reason)
if let data_block = server_response["data"] as? NSDictionary
{
if let session_data = data_block["session"] as? String
{
let preferences = UserDefaults.standard
preferences.set(session_data, forKey: "session")
DispatchQueue.main.async (
execute:self.LoginDone
)
}
}
})
task.resume()
}
func LoginToDo()
{
_login_button = "Login"
}
func LoginDone()
{
self.shouldPerformSegue(withIdentifier: "showHomeViewControllerFromSignIn", sender: self)
_login_button = "Logout"
}
I fixed a few things and figured it out
guard let server_response = json as? NSDictionary else
{
return
}
if let session_data = server_response["accessToken"] as? String
{
let preferences = UserDefaults.standard
preferences.set(session_data, forKey: "accessToken")
DispatchQueue.main.async (
execute:self.LoginDone
)
}
})
task.resume()
}
Related
Im New to Swift and I'm Integrating PayUmoney IOS SDK in swift, Im getting trouble When I'm doing in live its showing hash mismatch (Hash mismatch1) If I'm doing in test its showing invalid merchant key (Invalid key) I struck here from 2 weeks Did so many things and didn't get any solution for this can any one help it would be awesome. Below is my code, Thank you in Advance.
var params : PUMRequestParams = PUMRequestParams.shared()
var utils : Utils = Utils()
params.environment = PUMEnvironment.test;
params.firstname = txtFldName.text;
params.key = "bZf4AOjj";
params.merchantid = "5745303";
params.logo_url = "";
params.productinfo = "Product Info";
params.email = txtFldEmail.text;
params.phone = "";
params.surl = "https://www.payumoney.com/mobileapp/payumoney/success.php";
params.furl = "https://www.payumoney.com/mobileapp/payumoney/failure.php";
if(params.environment == PUMEnvironment.test){
generateHashForProdAndNavigateToSDK()
}
else{
calculateHashFromServer()
}
// assign delegate for payment callback.
params.delegate = self;
}
func generateHashForProdAndNavigateToSDK() -> Void {
let txnid = params.txnid!
let hashSequence : NSString = "\(params.key)|\(txnid)|\(params.amount)|\(params.productinfo)|\(params.firstname)|\(params.email)|||||||||||2uIsGhXWVw" as NSString
let data :NSString = utils.createSHA512(hashSequence as String!) as NSString
params.hashValue = data as String!;
startPaymentFlow();
}
// MARK:HASH CALCULATION
func prepareHashBody()->NSString{
return "SHA-512key=\(params.key!)&amount=\(params.amount!)&txnid=\(params.txnid!)&productinfo=\(params.productinfo!)&email=\(params.email!)&firstname=\(params.firstname!)" as NSString;
}
func calculateHashFromServer(){
let config = URLSessionConfiguration.default // Session Configuration
let session = URLSession(configuration: config) // Load configuration into Session
let url = URL(string: "https://test.payumoney.com/payment/op/v1/calculateHashForTest")!
var request = URLRequest(url: url)
request.httpBody = prepareHashBody().data(using: String.Encoding.utf8.rawValue)
request.httpMethod = "POST"
let task = session.dataTask(with: request, completionHandler: {
(data, response, error) in
if error != nil {
print(error!.localizedDescription)
} else {
do {
if let json = try JSONSerialization.jsonObject(with: data!, options: []) as? [String: Any]{
print(json)
let status : NSNumber = json["status"] as! NSNumber
if(status.intValue == 0)
{
self.params.hashValue = json["result"] as! String!
OperationQueue.main.addOperation {
self.startPaymentFlow()
}
}
else{
OperationQueue.main.addOperation {
self.showAlertViewWithTitle(title: "Message", message: json["message"] as! String)
}
}
}
} catch {
print("error in JSONSerialization")
}
}
})
task.resume()
}
Hello Vinny do with webview its working for me. Before I also used this PayUmoney IOS SDK but faced so many problems so based on objective-c I did this so I think its useful to you. create a weak var webview and create class UIwebviewdelegate
class PayumoneyViewController: UIViewController, UIWebViewDelegate, UIAlertViewDelegate {
#IBOutlet weak var Webview: UIWebView!
and for test use below credentials
//test
var merchantKey = "40747T"
var salt = "ur salt"
var PayUBaseUrl = "https://test.payu.in"
For live
//Production
var merchantKey = “xxxxxx”
var salt = “xxxxx”
var PayUBaseUrl = "https://secure.payu.in"
let productInfo = “Myapp” //It can be Project name or anything else
let firstName = “Santoshi” //Details of user whose is purchasing order
let email = “santoshi#app.com" //Details of user whose is purchasing order
let phone = "xxxxxxxxx" //Details of user whose is purchasing order
let sUrl = "www.google.com" //By this URL we match whether payment got success or failure
let fUrl = "www.google.com" //By this URL we match whether payment got success or failure
let service_provider = "payu_paisa"
var txnid1: String! = "" //Its an unique id which can give order a specific order number.
let totalPriceAmount = "1.0"
Above viewdidload do like this
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(true)
initPayment()
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(true)
}
In viewdidload do like this
override func viewDidLoad() {
super.viewDidLoad()
Webview.delegate = self
// Do any additional setup after loading the view.
}
Create payment and Generate Hash key
func initPayment() {
txnid1 = “Myapp\(String(Int(NSDate().timeIntervalSince1970)))"
//Generating Hash Key
let hashValue = String.localizedStringWithFormat("%#|%#|%#|%#|%#|%#|||||||||||%#",merchantKey,txnid1,totalPriceAmount,productInfo,firstName,email,salt)
let hash = self.sha1(string: hashValue)
let postStr = "txnid="+txnid1+"&key="+merchantKey+"&amount="+totalPriceAmount+"&productinfo="+productInfo+"&firstname="+firstName+"&email="+email+"&phone="+phone+"&surl="+sUrl+"&furl="+fUrl+"&hash="+hash+"&service_provider="+service_provider
let url = NSURL(string: String.localizedStringWithFormat("%#/_payment", PayUBaseUrl))
let request = NSMutableURLRequest(url: url! as URL)
do {
let postLength = String.localizedStringWithFormat("%lu",postStr.characters.count)
request.httpMethod = "POST"
request.setValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Current-Type")
request.setValue(postLength, forHTTPHeaderField: "Content-Length")
request.httpBody = postStr.data(using: String.Encoding.utf8)
Webview.loadRequest(request as URLRequest)
}
catch let error as NSError
{
print(error)
}
}
Finally Do this
func sha1(string:String) -> String {
let cstr = string.cString(using: String.Encoding.utf8)
let data = NSData(bytes: cstr, length: string.characters.count)
var digest = [UInt8](repeating: 0, count:Int(CC_SHA512_DIGEST_LENGTH))
CC_SHA512(data.bytes, CC_LONG(data.length), &digest)
let hexBytes = digest.map { String(format: "%02x", $0) }
return hexBytes.joined(separator: "")
}
func webViewDidFinishLoad(_ webView: UIWebView) {
let requestURL = self.Webview.request?.url
let requestString:String = (requestURL?.absoluteString)!
if requestString.contains("https://www.payumoney.com/mobileapp/payumoney/success.php") {
print("success payment done")
}else if requestString.contains("https://www.payumoney.com/mobileapp/payumoney/failure.php") {
print("payment failure")
}
}
func webView(_ webView: UIWebView, didFailLoadWithError error: Error) {
let requestURL = self.Webview.request?.url
print("WebView failed loading with requestURL: \(requestURL) with error: \(error.localizedDescription) & error code: \(error)")
if error._code == -1009 || error._code == -1003 {
showAlertView(userMessage: "Please check your internet connection!")
}else if error._code == -1001 {
showAlertView(userMessage: "The request timed out.")
}
}
func showAlertView(userMessage:String){
}
I have Faced Same problem and i have got solution of this problem.
In my code this line generate optional values --> let hashSequence : NSString = "(params.key!)|(txnid)|(params.amount!)|(params.productinfo!)|(params.firstname!)|(params.email!)|||||||||||(params.merchantid!)" as NSString
remove optional from the values.
i tried build login authentication without oauth is work and then i build other project only oauth code for request roken is work too, and now i want to make project authentication login with oauth swift. im really stuck what must i do.
this is my code im trying :
import UIKit
import OAuthSwift
class ViewController: UIViewController {
#IBOutlet weak var passwordTxt: UITextField!
#IBOutlet weak var phonenumberTxt: UITextField!
var oauthswift: OAuth1Swift?
let dataShared = UserDefaults.standard
let fileUrl = NSURL(string: "8villages://app/oauth-callback")
override func viewDidLoad() {
super.viewDidLoad()
requestToken()
}
#IBAction func doLogin(_ sender: Any) {
let phonenumber = phonenumberTxt.text
let password = passwordTxt.text
if(phonenumber == "" || password == "")
{
return
}
login(phonenumber!, password!)
}
func login(_ number:String, _ pswrd:String) {
let url = URL(string: "https://accounts.8villages.com/authentication")
let session = URLSession.shared
let request = NSMutableURLRequest(url: url!)
request.httpMethod = "POST"
let paramToSend = "identifier=" + number + "&password=" + pswrd
request.httpBody = paramToSend.data(using: String.Encoding.utf8)
let task = session.dataTask(with: request as URLRequest, completionHandler: {
(data, response, error) in
guard let _:Data = data else
{
return
}
let json:Any?
do {
json = try JSONSerialization.jsonObject(with: data!, options: [])
}
catch {
return
}
guard let server_response = json as? NSDictionary else {
return
}
if let data_block = server_response["data"] as? NSDictionary {
if let session_data = data_block["session"] as? String {
let preference = UserDefaults.standard
preference.set(session_data, forKey: "session")
DispatchQueue.main.async(
execute:self.LoginDone
)//
}
}
})
task.resume()
}
func LoginDone() {
self.performSegue(withIdentifier: "loginView", sender: self)
}
func requestToken() {
print ("hei")
var is8VillagesInstalled: Bool = {
let appUrl = URL(string: "8villages://app")
return UIApplication.shared.canOpenURL(appUrl!)
}()
self.oauthswift = OAuth1Swift.init(consumerKey: "**********",
consumerSecret: "**********",
requestTokenUrl: "https://oauth.8villages.com/tokens/request-token",
authorizeUrl: "8villages://app/?oauth_token_secret=",
accessTokenUrl: "https://accounts.8villages.com/tokens/access-token")
self.oauthswift?.authorizeURLHandler = OAuthSwiftOpenURLExternally.sharedInstance
self.oauthswift?.allowMissingOAuthVerifier = true
self.oauthswift?.postOAuthRequestToken(
callbackURL: fileUrl as! URL,
success: { credentials, response, parameters in
print("credentials", credentials)
print("response =", response!)
print("parameters =", parameters)
// request ke api login
}, failure: { error in
print("the error is", error.description, error.errorCode)
});
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
so, i dont know how to authentication login with oauth. how to make them work.
There seem to be lots of changes at IG. Many OAuth2 repos, all seem to have bugs, or really not easily converted to Swift3. Wondering if anyone has a solution for moving to Swift3 and working with the latest changes at Instagram?
Solutions most welcome. OAuth2 implementation seems to one of the more complicated things out there. Surprised that IG has not offered their own example docs on how to do this with iOS. They only have docs for web based solutions.
Maybe something brewing there? Zillions of coders they have on staff. But for now, on the hunt for a (dare I say?) simple solution.
thanks a million. :-)
For Swift 3:
Update: April 17 2017: Because of broken dependencies, the installation by Pods is no longer working. Therefore I have ripped the needed content and created a new Github project using a Bridging Header and stored all the needed files within the project. If you clone or download the github project, you'll instantly be able to login to Instagram.
To use those files in your project, simply drag and drop all the files from the SimpleAuth folder to your project, make sure to mark copy item if needed
Also you need to disable Disable implicit oAuth within the Instagram developer console.
Then you either copy/paste my code from the Bridging Header into your or you use mine. Set the Bridging Header at the Target's Build Settings.
Everything else works as before:
I have a struct for the Instagram Account:
struct InstagramUser {
var token: String = ""
var uid: String = ""
var bio: String = ""
var followed_by: String = ""
var follows: String = ""
var media: String = ""
var username: String = ""
var image: String = ""
}
The function to receive the token:
typealias JSONDictionary = [String:Any]
var user: InstagramUser?
let INSTAGRAM_CLIENT_ID = "16ee14XXXXXXXXXXXXXXXXXXXXXXXXX"
let INSTAGRAM_REDIRECT_URI = "http://www.davidseek.com/just_a_made_up_dummy_url" //just important, that it matches your developer account uri at Instagram
extension ViewController {
func connectInstagram() {
let auth: NSMutableDictionary = ["client_id": INSTAGRAM_CLIENT_ID,
SimpleAuthRedirectURIKey: INSTAGRAM_REDIRECT_URI]
SimpleAuth.configuration()["instagram"] = auth
SimpleAuth.authorize("instagram", options: [:]) { (result: Any?, error: Error?) -> Void in
if let result = result as? JSONDictionary {
var token = ""
var uid = ""
var bio = ""
var followed_by = ""
var follows = ""
var media = ""
var username = ""
var image = ""
token = (result["credentials"] as! JSONDictionary)["token"] as! String
uid = result["uid"] as! String
if let extra = result["extra"] as? JSONDictionary,
let rawInfo = extra ["raw_info"] as? JSONDictionary,
let data = rawInfo["data"] as? JSONDictionary {
bio = data["bio"] as! String
if let counts = data["counts"] as? JSONDictionary {
followed_by = String(describing: counts["followed_by"]!)
follows = String(describing: counts["follows"]!)
media = String(describing: counts["media"]!)
}
}
if let userInfo = result["user_info"] as? JSONDictionary {
username = userInfo["username"] as! String
image = userInfo["image"] as! String
}
self.user = InstagramUser(token: token, uid: uid, bio: bio, followed_by: followed_by, follows: follows, media: media, username: username, image: image)
} else {
// this handles if user aborts or the API has a problem like server issue
let alert = UIAlertController(title: "Error!", message: nil, preferredStyle: UIAlertControllerStyle.alert)
alert.addAction(UIAlertAction(title: "OK", style: UIAlertActionStyle.default, handler: nil))
self.present(alert, animated: true, completion: nil)
}
if error != nil {
print("Error during SimpleAuth.authorize: \(error)")
}
}
}
}
Instagram also says:
Important
Even though our access tokens do not specify an expiration time, your
app should handle the case that either the user revokes access, or
Instagram expires the token after some period of time. If the token is
no longer valid, API responses will contain an
“error_type=OAuthAccessTokenException”. In this case you will need to
re-authenticate the user to obtain a new valid token. In other words:
do not assume your access_token is valid forever.
So handle the case of receiving the OAuthAccessTokenException
This Code bellow I use for facebook and google+, I think it'ill work for Instagram too, maybe some adjusts.
import UIKit
class Signup: UIViewController, UIWebViewDelegate {
let GOOGLE_ID = "xxxxxx.apps.googleusercontent.com"
let GOOGLE_SECRET = "xxxxxxx";
let GOOGLE_REDIRECT_URI="http://yourdomain.com/api/account/googlecallback"
let GOOGLE_TOKEN_URL = "https://accounts.google.com/o/oauth2/token";
let GOOGLE_OAUTH_URL = "https://accounts.google.com/o/oauth2/auth";
let GOOGLE_OAUTH_SCOPE = "profile email";
let GOOGLE_GET_PROFILE = "https://www.googleapis.com/userinfo/v2/me";
let FACEBOOK_ID = "xxxxx";
let FACEBOOK_REDIRECT_URI = "http://yourdomain.com/api/account/facebookcallback";
let FACEBOOK_OAUTH_URL = "https://www.facebook.com/dialog/oauth?client_id=";
let FACEBOOK_OAUTH_SCOPE = "public_profile,email"
let FACEBOOK_GET_PROFILE = "https://graph.facebook.com/me?access_token="
var currentURL: String = ""
var queryString: String = ""
var receivedToken: String = ""
var authCode: String = ""
var authComplete = false
var webV:UIWebView = UIWebView(frame: CGRect(x: 0, y: 0, width: UIScreen.main.bounds.width, height: UIScreen.main.bounds.height))
#IBAction func google(_ sender: AnyObject) {
AppVars.Provider = "Google"
webV.delegate = self
let url = GOOGLE_OAUTH_URL + "?redirect_uri=" + GOOGLE_REDIRECT_URI + "&response_type=code&client_id=" + GOOGLE_ID + "&scope=" + GOOGLE_OAUTH_SCOPE
let urlString :String = url.addingPercentEncoding(withAllowedCharacters: CharacterSet.urlQueryAllowed)!
webV.loadRequest(URLRequest(url: URL(string:urlString)!))
self.view.addSubview(webV)
}
#IBAction func facebook(_ sender: AnyObject) {
AppVars.Provider = "Facebook"
webV.delegate = self
let url = FACEBOOK_OAUTH_URL + FACEBOOK_ID + "&redirect_uri=" + FACEBOOK_REDIRECT_URI + "&scope=" + FACEBOOK_OAUTH_SCOPE + "&display=popup&response_type=token"
let urlString :String = url.addingPercentEncoding(withAllowedCharacters: CharacterSet.urlQueryAllowed)!
webV.loadRequest(URLRequest(url: URL(string:urlString)!))
self.view.addSubview(webV)
}
func webView(_ webView: UIWebView, didFailLoadWithError error: Error) {
self.showAlert(self, message: "Internet is not working")
}
func webView(_ webView: UIWebView, shouldStartLoadWith request: URLRequest, navigationType: UIWebViewNavigationType) -> Bool {
return true;
}
func webViewDidStartLoad(_ webView: UIWebView) {
}
func webViewDidFinishLoad(_ webView: UIWebView) {
currentURL = (webView.request?.url!.absoluteString)!
if AppVars.Provider == "Google" {
googleSignup((webView.request?.url!)!)
} else {
facebookSignup((webView.request?.url!)!)
}
}
func googleSignup (_ returnCode: URL) {
let url = String(currentURL)
if (url?.range(of: "?code=") != nil && authComplete != true) {
authCode = getQueryItemValueForKey("code", url: returnCode)!
authComplete = true
let paramString = "code=" + authCode + "&client_id=" + GOOGLE_ID + "&client_secret=" +
GOOGLE_SECRET + "&redirect_uri=" + GOOGLE_REDIRECT_URI + "&grant_type=authorization_code"
self.requestServer(urlSource: GOOGLE_TOKEN_URL, params: paramString, requestType: "POST") { (dataResult, errorResult) -> () in
if errorResult != nil {
self.showAlert(self, message: "Internet is not working")
} else {
let dataString:NSString = NSString(data: dataResult as! Data, encoding: String.Encoding.utf8.rawValue)!
let dataResult2 = dataString.data(using: String.Encoding.utf8.rawValue, allowLossyConversion: false)!
do {
let jsonDict = try JSONSerialization.jsonObject(with: dataResult2, options: .allowFragments) as! [String:Any]
if let token = jsonDict["access_token"] as? String {
self.requestServerSignup(self.GOOGLE_GET_PROFILE, param: token, requestType: "GET") { (dataResult, errorResult) -> () in
if errorResult != nil {
self.showAlert(self, message: "Internet is not working")
} else {
let dataString:NSString = NSString(data: dataResult as! Data, encoding: String.Encoding.utf8.rawValue)!
let dataResult2 = dataString.data(using: String.Encoding.utf8.rawValue, allowLossyConversion: false)!
do {
let jsonDict = try JSONSerialization.jsonObject(with: dataResult2, options: .allowFragments) as! [String:Any]
if let name = jsonDict["name"] as? String {
AppVars.NameLogin = name
AppVars.PictureLogin = jsonDict["picture"] as! String
AppVars.EmailLogin = jsonDict["email"] as! String
self.performSegue(withIdentifier: "externalLoginSegue", sender: self)
// show picture, email and name for checking profile
}
} catch {
self.showAlert(self, message: "Internet is not working")
}
}
}
}
} catch {
self.showAlert(self, message: "Internet is not working")
}
}
}
self.webV.removeFromSuperview()
}
}
func facebookSignup(_ returnCode: URL) {
let url = String(currentURL)
if (url?.range(of: "access_token=") != nil && authComplete != true) {
let url2: String = returnCode.absoluteString.replacingOccurrences(of: "#access_token", with: "access_token")
let url3: URL = URL(string: url2)!
authCode = getQueryItemValueForKey("access_token", url: (url3))!
authComplete = true
let paramString = "code=" + authCode + "&client_id=" + GOOGLE_ID + "&client_secret=" +
GOOGLE_SECRET + "&redirect_uri=" + GOOGLE_REDIRECT_URI + "&grant_type=authorization_code"
self.requestServer(urlSource: self.FACEBOOK_GET_PROFILE + authCode + "&fields=name,picture,email", params: paramString, requestType: "GET") { (dataResult, errorResult) -> () in
if errorResult != nil {
self.showAlert(self, message: "Internet is not working")
} else {
let dataString:NSString = NSString(data: dataResult as! Data, encoding: String.Encoding.utf8.rawValue)!
let dataResult2 = dataString.data(using: String.Encoding.utf8.rawValue, allowLossyConversion: false)!
do {
let jsonDict = try JSONSerialization.jsonObject(with: dataResult2, options: .allowFragments) as! [String:Any]
if let name = jsonDict["name"] as? String {
AppVars.NameLogin = name
if let picture = jsonDict["picture"] as? [String:Any] {
if let dataPicture = picture["data"] as? [String:Any] {
if let url = dataPicture["url"] as? String {
AppVars.PictureLogin = url
}
}
}
AppVars.EmailLogin = jsonDict["email"] as! String
self.performSegue(withIdentifier: "externalLoginSegue", sender: self)
// show picture, email and name for checking profile
}
} catch {
self.showAlert(self, message: "Internet is not working")
}
}
}
self.webV.removeFromSuperview()
}
}
override func viewDidLoad() {
super.viewDidLoad()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
func getQueryItemValueForKey(_ key: String, url: URL) -> String? {
guard let components = URLComponents(url: url, resolvingAgainstBaseURL: false) else {
return nil
}
guard let queryItems = components.queryItems else { return nil }
return queryItems.filter {
$0.name == key
}.first?.value
}
func requestServer(urlSource:String, params:String, requestType:String, result:#escaping (_ dataResult:NSData?, _ errorResult:NSError?) -> ()) {
let url: URL = URL(string: urlSource)!
var request = URLRequest(url:url)
request.httpMethod = requestType
request.setValue("application/x-www-form-urlencoded; charset=utf-8", forHTTPHeaderField: "Content-Type")
if params.characters.count > 0 {
request.httpBody = params.data(using: String.Encoding.utf8)
}
let session = URLSession.shared
session.dataTask(with: request) { (data, response, error) -> Void in
DispatchQueue.main.async(execute: { () -> Void in
if error == nil {
result(data as NSData?, nil)
} else {
result(nil, error as NSError?)
}
})
}.resume()
}
}
I got my app onto the App Store. Everything was working fine on my end, and apparently on the reviewers end.
After the app went live, some users reported crashing immediately after they log in with Facebook. Here's the code for where I think it's crashing (run right after users log in with Facebook):
import UIKit
import FBSDKCoreKit
import FBSDKLoginKit
protocol getUserDataDelegate {
func gotData()
}
public var userEmailForMixpanel = ""
public var userNameForInvites = ""
class GetUserData: NSObject {
var facebookid:String = ""
var userEmail:String = ""
var userFirstName:String = ""
var userLastName: String = ""
var userGender:String = ""
var userBirthday:String = ""
var delegate = getUserDataDelegate?()
func returnUserData() {
let graphRequest : FBSDKGraphRequest = FBSDKGraphRequest(graphPath: "me", parameters: ["fields": "id, email, first_name, last_name, gender, birthday"])
graphRequest.startWithCompletionHandler({ (connection, result, error) -> Void in
if ((error) != nil)
{
print("Error: \(error)")
}
else
{
self.facebookid = (result.valueForKey("id") as? String)!
self.userEmail = (result.valueForKey("email") as? String)!
self.userFirstName = (result.valueForKey("first_name") as? String)!
self.userLastName = (result.valueForKey("last_name") as? String)!
self.userGender = (result.valueForKey("gender") as? String)!
//self.userBirthday = (result.valueForKey("birthday") as? String)!
userEmailForMixpanel = self.userEmail
userNameForInvites = self.userFirstName
NSUserDefaults.standardUserDefaults().setValue(userEmailForMixpanel, forKey: "userEmail")
NSUserDefaults.standardUserDefaults().setValue(userNameForInvites, forKey: "userName")
NSUserDefaults.standardUserDefaults().synchronize()
Mixpanel.sharedInstanceWithToken("abdc")
let mixpanel = Mixpanel.sharedInstance()
mixpanel.registerSuperProperties(["Gender":self.userGender])
print(self.facebookid)
print(self.userEmail)
print(self.userFirstName)
print(self.userLastName)
print(self.userGender)
//print(self.userBirthday)
self.checkIfUserExists()
}
})
}
func checkIfUserExists() {
showTutorial = true
let url:NSURL = NSURL(string: "url")!
let task:NSURLSessionDataTask = NSURLSession.sharedSession().dataTaskWithURL(url) { (data:NSData?, response:NSURLResponse?, error:NSError?) -> Void in
let userTokenDataDictionary:NSDictionary = (try! NSJSONSerialization.JSONObjectWithData(data!, options: NSJSONReadingOptions.MutableContainers)) as! NSDictionary
if userTokenDataDictionary ["token"] != nil {
userAccessToken = (userTokenDataDictionary["token"] as? String)!
NSUserDefaults.standardUserDefaults().setValue(userAccessToken, forKey: "userAccessToken")
NSUserDefaults.standardUserDefaults().synchronize()
print("Token for Existing User:\(userAccessToken)")
self.finishedGettingData()
}
if userTokenDataDictionary ["error"] != nil {
userAccessToken = (userTokenDataDictionary["error"] as? String)!
print(userAccessToken)
print("User needs to be created")
self.createNewUserFromFacebook()
}
}
task.resume()
}
func createNewUserFromFacebook() {
let url:NSURL = NSURL(string: "url")!
print(url)
let task:NSURLSessionDataTask = NSURLSession.sharedSession().dataTaskWithURL(url) { (data:NSData?, response:NSURLResponse?, error:NSError?) -> Void in
let userTokenDataDictionary:NSDictionary = (try! NSJSONSerialization.JSONObjectWithData(data!, options: NSJSONReadingOptions.MutableContainers)) as! NSDictionary
if userTokenDataDictionary ["token"] != nil {
userAccessToken = (userTokenDataDictionary["token"] as? String)!
NSUserDefaults.standardUserDefaults().setValue(userAccessToken, forKey: "userAccessToken")
NSUserDefaults.standardUserDefaults().synchronize()
}
if userTokenDataDictionary ["error"] != nil {
userAccessToken = (userTokenDataDictionary["error"] as? String)!
print(userAccessToken)
}
print("Token for New User:\(userAccessToken)")
self.finishedGettingData()
}
task.resume()
}
func checkIfUserHasUsedListenerApp() {
let accessToken = NSUserDefaults.standardUserDefaults().stringForKey("userAccessToken")!
let url:NSURL = NSURL(string: "url")!
print(url)
let task:NSURLSessionDataTask = NSURLSession.sharedSession().dataTaskWithURL(url) { (data:NSData?, response:NSURLResponse?, error:NSError?) -> Void in
let adDataDict:NSDictionary = (try! NSJSONSerialization.JSONObjectWithData(data!, options: NSJSONReadingOptions.MutableContainers)) as! NSDictionary
if adDataDict ["used_ListenerApp"] != nil {
let responseCode = adDataDict.valueForKey("used_ListenerApp") as! Bool
print(responseCode)
if responseCode == false {
Mixpanel.sharedInstanceWithToken("abc")
let mixpanel = Mixpanel.sharedInstance()
mixpanel.track("New User Signed Up", properties: ["distinct_id":userEmailForMixpanel])
}
if responseCode == true {
return
}
}
}
task.resume()
}
func finishedGettingData() {
self.checkIfUserHasUsedListenerApp()
Mixpanel.sharedInstanceWithToken("abc")
let mixpanel = Mixpanel.sharedInstance()
mixpanel.track("User Logged In", properties: ["distinct_id":userEmailForMixpanel])
if let actualdelegate = self.delegate {
actualdelegate.gotData()
}
}
}
It's only crashing for some users, not all. I even tried creating a loop that generates a bunch of user data to run it through this code, and I couldn't replicate the crash.
Any advice would be appreciated a lot.
UPDATE
It looks like it has to do with Facebook not returning an email address. I'll keep looking though.
I figured it out. Part of the problem was that Facebook wasn't returning an email address for some users, so I checked for that and if it didn't return an email, I created one with their Facebook ID to get them through the login process (fbid#facebook.mywebsite.com).
Another part of the problem was that some users had logged into my website using an email address that was also assigned to their Facebook account, and tried logging in with Facebook on the app. I fixed this by having it merge their Facebook info with their existing account during the authorization process if an existing account is found.
I've written a function that should return a value but the value comes from a closure. The problem is if I try to return a value from inside the closure it treats this as being the return value from the completion handler.
private func loadData() throws -> [Item] {
var items = [Item]()
let jsonUrl = "http://api.openweathermap.org/data/2.5/forecast/daily?units=metric&cnt=7&q=coventry,uk"
print(jsonUrl)
let session = NSURLSession.sharedSession()
guard let shotsUrl = NSURL(string: jsonUrl) else {
throw JSONError.InvalidURL(jsonUrl)
}
session.dataTaskWithURL(shotsUrl, completionHandler: {(data, response, error) -> Void in
do {
let json = try NSJSONSerialization.JSONObjectWithData(data!, options: [])
print(json)
guard let days:[AnyObject] = (json["list"] as! [AnyObject]) else {
throw JSONError.InvalidArray
}
for day in days {
guard let timestamp:Double = day["dt"] as? Double else {
throw JSONError.InvalidKey("dt")
}
print(timestamp)
let date = NSDate(timeIntervalSince1970: NSTimeInterval(timestamp))
guard let weather:[AnyObject] = day["weather"] as? [AnyObject] else {
throw JSONError.InvalidArray
}
guard let desc:String = weather[0]["description"] as? String else {
throw JSONError.InvalidKey("description")
}
guard let icon:String = weather[0]["icon"] as? String else {
throw JSONError.InvalidKey("icon")
}
guard let url = NSURL(string: "http://openweathermap.org/img/w/\(icon).png") else {
throw JSONError.InvalidURL("http://openweathermap.org/img/w/\(icon).png")
}
guard let data = NSData(contentsOfURL: url) else {
throw JSONError.InvalidData
}
guard let image = UIImage(data: data) else {
throw JSONError.InvalidImage
}
guard let temp:AnyObject = day["temp"] else {
throw JSONError.InvalidKey("temp")
}
guard let max:Float = temp["max"] as? Float else {
throw JSONError.InvalidKey("max")
}
let newDay = Item(date: date, description: desc, maxTemp: max, icon: image)
print(newDay)
items.append(newDay)
}
return items // this line fails because I'm in the closure. I want this to be the value returned by the loadData() function.
} catch {
print("Fetch failed: \((error as NSError).localizedDescription)")
}
})
}
Add a completion handler (named dataHandler in my example) to your loadData function:
private func loadData(dataHandler: ([Item])->()) throws {
var items = [Item]()
let jsonUrl = "http://api.openweathermap.org/data/2.5/forecast/daily?units=metric&cnt=7&q=coventry,uk"
print(jsonUrl)
let session = NSURLSession.sharedSession()
guard let shotsUrl = NSURL(string: jsonUrl) else {
throw JSONError.InvalidURL(jsonUrl)
}
session.dataTaskWithURL(shotsUrl, completionHandler: {(data, response, error) -> Void in
do {
let json = try NSJSONSerialization.JSONObjectWithData(data!, options: [])
print(json)
guard let days:[AnyObject] = (json["list"] as! [AnyObject]) else {
throw JSONError.InvalidArray
}
for day in days {
guard let timestamp:Double = day["dt"] as? Double else {
throw JSONError.InvalidKey("dt")
}
print(timestamp)
let date = NSDate(timeIntervalSince1970: NSTimeInterval(timestamp))
guard let weather:[AnyObject] = day["weather"] as? [AnyObject] else {
throw JSONError.InvalidArray
}
guard let desc:String = weather[0]["description"] as? String else {
throw JSONError.InvalidKey("description")
}
guard let icon:String = weather[0]["icon"] as? String else {
throw JSONError.InvalidKey("icon")
}
guard let url = NSURL(string: "http://openweathermap.org/img/w/\(icon).png") else {
throw JSONError.InvalidURL("http://openweathermap.org/img/w/\(icon).png")
}
guard let data = NSData(contentsOfURL: url) else {
throw JSONError.InvalidData
}
guard let image = UIImage(data: data) else {
throw JSONError.InvalidImage
}
guard let temp:AnyObject = day["temp"] else {
throw JSONError.InvalidKey("temp")
}
guard let max:Float = temp["max"] as? Float else {
throw JSONError.InvalidKey("max")
}
let newDay = Item(date: date, description: desc, maxTemp: max, icon: image)
print(newDay)
items.append(newDay)
}
dataHandler(items)
} catch {
print("Fetch failed: \((error as NSError).localizedDescription)")
}
}).resume()
}
do {
try loadData { itemsArray in
print(itemsArray)
}
} catch {
print(error)
}
I've tested it in a Playground and it works without errors: