var first_name = ""
func problemFunc() {
FBRequestConnection.startForMeWithCompletionHandler { (connection: FBRequestConnection!, result: AnyObject!, error: NSError!) -> Void in
if let fbGraphUserDict = result as? Dictionary<String, AnyObject>{
first_name = fbGraphUserDict["first_name"] as NSString
println(first_name)
}
}
}
PFFacebookUtils.logInWithPermissions(permissions, {
(user: PFUser!, error: NSError!) -> Void in
if user == nil {
NSLog("Uh oh. The user cancelled the Facebook login.")
} else if user.isNew {
NSLog("User signed up and logged in through Facebook!")
} else {
NSLog("User logged in through Facebook!")
problemFunc() // error is here
}
})
This code is inside an #Ibaction button. I cannot build because the call to problemFunc() triggers the error message in the title of this post. If I move the first_name var definition inside the problemFunc it will work ok. But I need it out, because another function will need to access its value.
I'm really not sure at what causes this problem, if you have a clue, please help.
Use a closure instead of a function:
var first_name = ""
let problemFunc = { () -> () in
FBRequestConnection.startForMeWithCompletionHandler { (connection: FBRequestConnection!, result: AnyObject!, error: NSError!) -> Void in
if let fbGraphUserDict = result as? Dictionary<String, AnyObject>{
first_name = fbGraphUserDict["first_name"] as NSString
println(first_name)
}
}
}
PFFacebookUtils.logInWithPermissions(permissions, {
(user: PFUser!, error: NSError!) -> Void in
if user == nil {
NSLog("Uh oh. The user cancelled the Facebook login.")
} else if user.isNew {
NSLog("User signed up and logged in through Facebook!")
} else {
NSLog("User logged in through Facebook!")
problemFunc() // error is here
}
})
Here are the basic principles in play: (from Apple's docs: https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Closures.html#//apple_ref/doc/uid/TP40014097-CH11-ID103)
"Global and nested functions, as introduced in Functions, are actually special cases of closures. Closures take one of three forms:
Global functions are closures that have a name and do not capture any values.
Nested functions are closures that have a name and can capture values from their enclosing function.
Closure expressions are unnamed closures written in a lightweight syntax that can capture values from their surrounding context."
ie this is ok
func someFunc() {
func nestFunc() {}
}
but this is not
func someFunc() {
func nestFunc() {
func nestedFunc2() { }
}
}
If you look at this in Xcode the third function (func nestedFunc2) will give you the error "Cannot reference a local function with capture from another local function"
The top function (func someFunc) is a global scope function and those work like regular functions/methods.
The second function (func nestFunc) is a nested function which is a named closure one level deep that can capture the scope of its parent global function.
Nested functions, can capture the scope of a global function but not the scope of another nested function.
That's why we need a closure i.e.
func someFunc() {
func nestFunc() {
let strictClosure = { () -> () in
//this is where you write the code
}
}
}
#fluidsonic answer should solve the problem. However note that you're doing some spaghetti code, because you are modifying a variable captured by a closure, and executed in the context of another function. That's hard to track if you need to debug, and more generally hard to follow when and how that variable is modified.
A more linear and better readable flow is to define problemFunc as a function taking a function as parameter, and calling that function rather than directly setting the value in the first_name variable:
let problemFunc = { (callback: (String -> Void) -> ()) in
FBRequestConnection.startForMeWithCompletionHandler { (connection: FBRequestConnection!, result: AnyObject!, error: NSError!) -> Void in
if let fbGraphUserDict = result as? Dictionary<String, AnyObject>{
let first_name = fbGraphUserDict["first_name"] as NSString
callback(first_name) // << here you call the callback passing the `first_name` local variable
println(first_name)
}
}
}
and do the actual assignment to first_name in a closure you define when calling problemFunc:
PFFacebookUtils.logInWithPermissions(permissions, {
(user: PFUser!, error: NSError!) -> Void in
if user == nil {
NSLog("Uh oh. The user cancelled the Facebook login.")
} else if user.isNew {
NSLog("User signed up and logged in through Facebook!")
} else {
NSLog("User logged in through Facebook!")
problemFunc { (name: String) -> Void in
first_name = name
}
}
})
Related
Okay, so I'm trying to build an iOS app that relies on Firebase (To work with its android version)
I started with creating a repository for each actor in my app and a general repository to manage them all
Each repository manages the observers of this actor. An example:
Inside the PagesRepository, this is a function that retrieves all the pages from Firebase and returns it inside a completionHandler:
//MARK: Gets the whole pages list
func getPagesList(completionHandler: #escaping (_ pages: [Page]?, _ error: NSError?) -> Void) {
func displayError(error: String) {
print(error)
completionHandler(nil, self.getErrorFromString(error))
}
pagesRef.observe(DataEventType.value) { pagesSnapshot in
guard pagesSnapshot.exists() else {
displayError(error: "Pages snapshot doesn't exist")
return
}
var pagesList = [Page]()
for pageSnapshot in pagesSnapshot.children {
pagesList.append(Page(snapshot: pageSnapshot as! DataSnapshot))
}
completionHandler(pagesList, nil)
}
}
And then I call it from the ViewController like this:
repository.getPagesList { (pages, error) in
guard error == nil else {
return
}
//Do processing
}
I know this may be a lot to take in, but my problem is that every time I call the function, it creates a new observer but doesn't cancel the old one... So, the completionHandler is called multiple times with different values
How should I manage this problem?
(Sorry for being complicated and a little unclear, I'm just really lost)
It seems like you only want to observe the value once so I would use this instead:
func getPagesList(completionHandler: #escaping (_ pages: [Page]?, _ error: NSError?) -> Void) {
func displayError(error: String) {
print(error)
completionHandler(nil, self.getErrorFromString(error))
}
pagesRef.observeSingleEvent(of: .value, with: { (pagesSnapshot) in
var pagesList = [Page]()
for pageSnapshot in pagesSnapshot.children {
pagesList.append(Page(snapshot: pageSnapshot as! DataSnapshot))
}
completionHandler(pagesList, nil)
}) { (error) in
// Display error
}
}
So, I created a typealias to store a completion handler, to later pass into a function called submitTokenToBackend using Stripe's iOS library. Here is my code:
// MARK: - Create Completion Handlers
typealias CompletionHandler = (_ token: AnyObject?, _ error: NSError?) -> Void
// MARK: - Submit Token To Backend
func submitTokenToBackend(completionHandler: CompletionHandler) {
}
// MARK: - STPPaymentCardTextFieldDelegate
func paymentCardTextFieldDidChange(_ textField: STPPaymentCardTextField) {
print("Card number: \(textField.cardParams.number) Exp Month: \(textField.cardParams.expMonth) Exp Year: \(textField.cardParams.expYear) CVC: \(textField.cardParams.cvc)")
self.buyButton.isEnabled = textField.isValid
}
// MARK: Initialize Card Params
let cardParams = STPCardParams()
func cardParamsFunc() {
cardParams.number = "4242424242424242"
cardParams.expMonth = 10
cardParams.expYear = 2018
cardParams.cvc = "123"
STPAPIClient.shared().createToken(withCard: cardParams){ (token, error) in
if let error = error {
print(error.localizedDescription)
} else if let token = token {
// HERE'S WHERE I'M GETTING ERRORS
self.submitTokenToBackend(completionHandler: CompletionHandler) -> Void {
if let error = error {
print(error.localizedDescription)
} else {
print("Show receipt page")
}
}
}
}
}
I am getting these weird errors, now, in Swift 3 concerning my completion handler not having expected types. Not an isolated incident, either. Any thoughts?
Almost all thing you need is described in Rob Napier's answer.
I'll try to show you a little more concrete code...
You can define the completion handler and pass it to submitTokenToBackend(completionHandler:) like this:
let theCompletionHandler: CompletionHandler = {token, error in
if let error = error {
print(error.localizedDescription)
} else {
print("Show receipt page")
}
}
self.submitTokenToBackend(completionHandler: theCompletionHandler)
With removing intermediate let-constant, you can write it in this way:
self.submitTokenToBackend(completionHandler: {token, error in
if let error = error {
print(error.localizedDescription)
} else {
print("Show receipt page")
}
})
Using the trailing closure feature of Swift, the above code can be shortened to:
self.submitTokenToBackend {token, error in
if let error = error {
print(error.localizedDescription)
} else {
print("Show receipt page")
}
}
Your code is far from any of above three.
self.submitTokenToBackend(completionHandler: CompletionHandler) -> Void {
This is a declaration, not a method call. You can't pass a typealias as a parameter. And -> Void does not make sense here at all. You almost certainly meant
self.submitTokenToBackend {
If you have further questions on this, however, you need to provide code we can compile (see mcve) and list the exact errors. "Weird errors" is not particularly helpful for debugging.
Im not sure, but... Where is 'CompletionHandler' implementation? As I understand, you just declarate some like block in Objective - C ('typedef void (^completionHandler)(id token);'), but don't use it.
I am trying to log in and save a user to Parse using PFFacebookUtils. Note, I am using the FBSDKLoginButton, and I do NOT want to use the PFLoginViewController, I need a much more custom login. Here is my code:
func loginButton(loginButton: FBSDKLoginButton!, didCompleteWithResult result1: FBSDKLoginManagerLoginResult!, error: NSError!) {
if error == nil {
// this block of code is reached fine.
PFFacebookUtils.logInInBackgroundWithReadPermissions(["public_profile"], block: { (user, error2) -> Void in
// both error2 and user both print out to nil here
print(error2)
print(user)
if let newUser = user {
// inside this block of code is never reached
}
})
}
}
what could be the problem?
I've been stuck on how to implement a custom Twitter login with Parse for my Swift app for a while now. Most resources I've seen for Twitter login with iOS are for Objective-C, and I've tried changing the suggested solutions into Swift, but no luck so far. This is my current code:
#IBAction func twitterLogIn(sender: AnyObject) {
PFTwitterUtils.logInWithBlock( { (user: PFUser?, error: NSError?) -> Void in
// apparently both user and currentUser are nil...
if PFTwitterUtils.isLinkedWithUser(PFUser.currentUser()) {
var info: NSURL! = NSURL(string: "https://api.twitter.com/1.1/account/settings.json")
var request: NSMutableURLRequest = NSMutableURLRequest(URL: info)
NSURLConnection.sendAsynchronousRequest(request, queue: NSOperationQueue.mainQueue(), completionHandler: { (response: NSURLResponse!, data: NSData!, connectionError: NSError!) -> Void in
if let d = data {
var dict: NSDictionary = NSJSONSerialization.JSONObjectWithData(d, options: NSJSONReadingOptions.MutableLeaves, error: nil) as! NSDictionary
// do stuff with user's profile info
} else {
println("Apparently no Twitter response.")
}
})
} else {
println("Apparently failed login.")
}
})
}
This prints out Apparently failed login. I've also tried this:
PFTwitterUtils.logInWithBlock( { (user: PFUser?, error: NSError?) -> Void in
if let user = user {
if user.isNew {
println("User signed up and logged in with Twitter.")
// put information in user object
user.save()
} else {
println("User logged in with Twitter.")
}
} else {
println("User was nil.")
}
})
It printed out User was nil. And I also tried this:
if let twitter = PFTwitterUtils.twitter() {
if twitter.userId != nil && twitter.screenName != nil && twitter.authToken != nil && twitter.authTokenSecret != nil {
PFTwitterUtils.logInWithTwitterId(twitter.userId!, screenName: twitter.screenName!, authToken: twitter.authToken!, authTokenSecret: twitter.authTokenSecret!, block: { (user: PFUser?, error: NSError?) -> Void in
if let user = user {
if user.isNew {
println("User signed up and logged in with Twitter.")
// put info in user object
user.save()
} else {
println("User logged in with Twitter.")
}
} else {
println("User was nil. Something went wrong. Did the user cancel the login?")
}
})
} else {
println("Couldn't access a Twitter account on this device.")
}
} else {
println("Couldn't find a Twitter account on this device.")
}
Which printed Couldn't access a Twitter account on this device. The only other option I know of right now is to use the built-in login view controller, but I'd rather use a custom one. How can I implement a custom Twitter login function?
Edit: I tried another method that didn't work yet again:
PFTwitterUtils.logInWithBlock( { (user: PFUser?, error: NSError?) -> Void in
// apparently both user and currentUser are nil...
println(2)
if !(PFTwitterUtils.isLinkedWithUser(user)) {
println(3)
PFTwitterUtils.linkUser(user!, block: { (succeeded: Bool, error: NSError?) -> Void in
println(4)
if succeeded {
println("User linked with Twitter.")
}
})
}
println(5)
if let u = user {
println(6)
if u.isNew {
println("User signed up and logged in with Twitter.")
// put info in user object
var info: NSURL! = NSURL(string: "https://api.twitter.com/1.1/account/settings.json")
println(7)
var request: NSMutableURLRequest = NSMutableURLRequest(URL: info)
println(8)
NSURLConnection.sendAsynchronousRequest(request, queue: NSOperationQueue.mainQueue(), completionHandler: { (response: NSURLResponse!, data: NSData!, connectionError: NSError!) -> Void in
println(9)
if let d = data {
println(10)
var dict: NSDictionary = NSJSONSerialization.JSONObjectWithData(d, options: NSJSONReadingOptions.MutableLeaves, error: nil) as! NSDictionary
// do stuff with this data
} else {
println("Apparently no Twitter response.")
}
})
} else {
println("User logged in with Twitter.")
}
} else {
println("User was nil.")
}
})
This prints up to 3 and then complains of a nil optional value, most likely user.
Edit: I've been testing Fabric as a possible solution and although it provides a good Twitter login method, I can't figure out how to use it with Parse, specifically, how to create and handle a corresponding user object as I can't access the user's password. I'd much prefer to use PFTwitterUtils for the best integration with Parse, but I'm running out of options for that. Can anyone help?
I'm working on integrating touchID into my application. The process was fairly simple, but even just using my dummy data, it takes about 5 seconds after it authenticated my fingerprint, before it performs it's task.
Here's my code:
func requestFingerprintAuthentication() {
let context = LAContext()
var authError: NSError?
let authenticationReason: String = "Login"
if context.canEvaluatePolicy(LAPolicy.DeviceOwnerAuthenticationWithBiometrics, error: &authError) {
context.evaluatePolicy(LAPolicy.DeviceOwnerAuthenticationWithBiometrics, localizedReason: authenticationReason, reply: {
(success: Bool, error: NSError?) -> Void in
if success {
println("successfull signin with touchID")
self.emailInputField.text = "john.doe#gmail.com"
self.passwordInputField.text = "password"
self.signIn(self.signInButton)
} else {
println("Unable to Authenticate touchID")
}
})
}
}
even with the dummy data, it takes waaay too long.
When I login normally, by typing the email and the password into my inputfields, the signIn() function runs instantly.
To see if it was a problem with that. I tried replacing that, with 2 lines that simply takes me to the correct viewController. But it still takes several seconds after it's authenticated my fingerprint.
I know it's not the phone, nor touchID. Cause it runs my println("successfull signin with touchID") immediately. It's what comes after that, that for some reason takes several seconds for it to run?
Any help explaining this would be greatly appreciated!
The documentation states:
This method asynchronously evaluates an authentication policy.
You are running UI code on a thread that is not the main. Wrap your code to get it to perform on the main thread:
func requestFingerprintAuthentication() {
let context = LAContext()
var authError: NSError?
let authenticationReason: String = "Login"
if context.canEvaluatePolicy(LAPolicy.DeviceOwnerAuthenticationWithBiometrics, error: &authError) {
context.evaluatePolicy(LAPolicy.DeviceOwnerAuthenticationWithBiometrics, localizedReason: authenticationReason, reply: {
(success: Bool, error: NSError?) -> Void in
if success {
NSOperationQueue.mainQueue().addOperationWithBlock({ () -> Void in
println("successfull signin with touchID")
self.emailInputField.text = "john.doe#gmail.com"
self.passwordInputField.text = "password"
self.signIn(self.signInButton)
})
} else {
println("Unable to Authenticate touchID")
}
})
}
}