Facebook Graph API: Swift SDK not working: GraphRequest - swift

I've been trying to follow Facebooks documentation exactly but can't understand why my code doesn't work, even though it is exactly as in the docs. I am already signing into Facebook on my app, but I need to get the profile data.
Here is my function.
func fetchFacebookUser() {
let connection = GraphRequestConnection()
connection.add(GraphRequest(graphPath: "/me")) { httpResponse, result in
switch result {
case .success(let response):
print("Graph Request Succeeded: \(response)")
case .failed(let error):
print("Graph Request Failed: \(error)")
}
}
connection.start()
}
You'll notice it is exactly like the documentation:
https://developers.facebook.com/docs/swift/graph
But I can't even build the project because I get this error in Xcode:
Contextual closure type '(GraphRequestConnection?, Any?, Error?) -> Void' expects 3 arguments, but 2 were used in closure body
Insert ',<#arg#> '
It has the 'Fix' button but that just breaks the whole thing and Xcode (red warning) complains that none of the cases in the switch statement exist.
I have searched everywhere, the closest solution I got was this SO post: Facebook GraphRequest for Swift 5 and Facebook SDK 5
But that had no answers :(
I'm sure someone has called the Graph API using Swift? What am I missing here?

However i implement facebook login my app which works fine.
On button click I called facebook login using LoginManager.
#IBAction func continueWithFacebook(_ sender: Any) {
let fbLoginManager : LoginManager = LoginManager()
fbLoginManager.logIn(permissions: ["email"], from: self) { (result, error) -> Void in
if (error == nil){
let fbloginresult : LoginManagerLoginResult = result!
if (result?.isCancelled)!{
return
}
if(fbloginresult.grantedPermissions.contains("email")) {
self.getFBUserData()
}
}
}
}
After that I get the data of user using GraphAP
func getFBUserData() {
if((AccessToken.current) != nil){
GraphRequest(graphPath: "me", parameters: ["fields": "id, name, first_name, last_name, picture.type(large), email"]).start(completionHandler: { (connection, result, error) -> Void in
if (error == nil){
guard let userDict = result as? [String:Any] else {
return
}
if let picture = userDict["picture"] as? [String:Any] ,
let imgData = picture["data"] as? [String:Any] ,
let imgUrl = imgData["url"] as? String {
self.uURLString = imgUrl
/*let url = URL(string: imgUrl)*/
print(imgData)
}
if let n = userDict["name"] as? String {
self.name = n
}
if let e = userDict["email"] as? String {
self.email = e
}
self.socialLogIn()
}
})
}
}
Add in your pod file -
pod 'FBSDKCoreKit' and
pod 'FBSDKLoginKit'
Try using like this you will get your derided result.
Happy Coding

Related

Retrieving user email, using FBSDKLoginKit, from an account registered with phone number only: what does "email" result returns?

I'm following iOS Academy tutorial on a Chat App. In said app, when a user logs in for the first time using Facebook, i need to retrieve the associated email and store it in my database. Lurking at YT comments ( here ) i've found out that a FB account can have no associated email if it was registered with the phone number. Quoting the literal comment:
I created my facebook account without an email (only through a phone number) it still registers the authentication however the identifier is missing. It also does not send out information to the database. Incase anyone who has only a phone has tried.
Since i need to pass the retrieved email to my Firebase Realtime database, i want to handle this issue, but i would need to know what happens when i try to retrieve an email using FBSKDLoginKit API if there is no associated email.
Since my FB has an associated email, and apparently there's no way to remove it and leave the field blank, i tried to register a new FB account with my phone number. The problem is that in order to be able to run the app on test mode and log into FB, i would need to validate it on Facebook For Developers, but to log into Developers i need an associated email. So i'm at a dead end and can't test myself.
My question is: does anyone knows what the email result returns in a FB request if there is no associated email?
Here's my code, my guess is that the function hits the return in the guard block at the commented line down below, because the email is equal to nil, but from what i've read on Youtube it seems that only the Database block is skipped while the Firebase authentication succeeds. So maybe it returns an empty string, or something else.
func loginButton(_ loginButton: FBLoginButton, didCompleteWith result: LoginManagerLoginResult?, error: Error?) {
guard let token = result?.token?.tokenString else {
return
}
let facebookRequest = FBSDKLoginKit.GraphRequest(graphPath: "me",
parameters: ["fields" : "email, first_name"], tokenString: token,
version: nil,
httpMethod: .get)
facebookRequest.start { [weak self] _, result, error in
guard let result = (result as? [String: Any]), error == nil else {
self?.alertUserLoginError(message: "An error occurred while processing your request. Please try to sign in using Google.")
return
}
guard let firstName = (result["first_name"] as? String),
let email = (result["email"] as? String) else {
return
}
// Imo here the func returns cause email is equal to nil, but apparently that's not true.
// The database block is skipped, but the auth block down below is not.
DatabaseManager.shared.insertUser(with: chatUser, completion: { success in
// Doing other unrelated stuff
}
// The following block is run apparently, not sure why
let credential = FacebookAuthProvider.credential(withAccessToken: token)
FirebaseAuth.Auth.auth().signIn(with: credential) { authResult, error in
guard authResult != nil, error == nil else {
FBSDKLoginKit.LoginManager().logOut()
return
}
}
}
}
This is the database function in DatabaseManager class, called up above:
public func insertUser(with user: ChatAppUser, completion: #escaping (Bool) -> Void) {
database.child(user.safeEmail).setValue([
"first_name" : user.firstName
],
withCompletionBlock: { error, _ in
guard error == nil else {
completion(false)
return
}
completion(true)
})
}
func FacebookGETDataClicked(_ sender: Any)
{
let fbLoginManager : LoginManager = LoginManager()
fbLoginManager.logIn(permissions: ["email"], from: self) { (result, error) in
if (error == nil){
let fbloginresult : LoginManagerLoginResult = result!
let fbloginresultsss: Set<String> = fbloginresult.grantedPermissions
let arr = [String](fbloginresultsss)
if arr.count > 0 {
if(arr.contains("email"))
{
self.getFBUserData()
fbLoginManager.logOut()
}
}
}
}
}
func getFBUserData(){
var userProfileImage = String()
var useremail = String()
var userFullName = String()
var userID = String()
if((AccessToken.current) != nil){
GraphRequest(graphPath: "me", parameters: ["fields": "id, name, first_name,last_name, picture.type(large), email"]).start(completionHandler: { (connection, result, error) -> Void in
if (error == nil){
let dict = result as! [String : AnyObject]
print(dict)
if let mail = (dict["email"] as? String)
{
useremail = mail
}
if let name = (dict["name"] as? String)
{
userFullName = name
}
if let id = (dict["id"] as? String)
{
userID = id
}
if let pic = dict["picture"] as? NSDictionary
{
let profilePictureObj = pic
let datas = profilePictureObj["data"] as! NSDictionary
userProfileImage = datas["url"] as! String
}
print(userID,useremail,userFullName,userProfileImage)
}
})
}
}

Variable value becomes nil after fbsdk graph request

I am writing a project in swift and I am pretty new to the language.
I set a variable "username"'s value to the result of my request to the facebook sdk, but my variable value becomes nil after the graph request. The variable assignment works fine inside the graph request itself.
Here is my code.
import UIKit
import FBSDKCoreKit
import FBSDKLoginKit
import FBSDKShareKit
class SecondViewController: UIViewController {
var username : String = "X"
let label = UILabel()
override func viewDidLoad() {
super.viewDidLoad()
FBSDKGraphRequest(graphPath: "/me", parameters: ["Fields" : "name"]).start {
(connection, result, err) in
if err != nil {
print("Failed to start request", err)
return
}
var res = result as? [String : Any]
self.username = (res?["name"] as! String)
print(self.username)
}
print(self.username)
}
}
The first print statement that is inside the request works just fine but the second one that is outside of the request prints an empty line.
This was also the case when I wanted to set the text of a label to the value of the username variable. It worked inside the request but didn't do anything otherwise.
i think first perform login which permissions:
loginManager.logIn(withReadPermissions: ["email","public_profile"], from: self) { (FBSDKLoginManagerLoginResult, Error) in
if(Error == nil){
if(FBSDKLoginManagerLoginResult?.isCancelled)!{
SVProgressHUD.showError(withStatus: "You Cancelled To Login With Facebook, \n Try Again Later!")
}else if(FBSDKLoginManagerLoginResult?.token != nil){
SVProgressHUD.showSuccess(withStatus: "Login Successfull")
SVProgressHUD.show(withStatus: loadingText)
if((FBSDKAccessToken.current()) != nil){
FBSDKGraphRequest(graphPath: "me", parameters: ["fields": "id, name, first_name, last_name, email"]).start(completionHandler: { (connection, result, error) -> Void in
if (error == nil){
let resultDic = result as! [String: Any]
let facebookProfileUrl : String! = "http://graph.facebook.com/\((FBSDKLoginManagerLoginResult?.token!.userID)!)/picture?type=large"
var fbId = (FBSDKLoginManagerLoginResult?.token!.userID)! as String
var name = resultDic["name"] as! String
var email = resultDic["email"] as! String
}else{
print(error!.localizedDescription)
}
})
}
}
}
}
as your inside code is working which means you perform above well, i think its working in threading, which means your bottom "print(self.username)" is printed first, then above one which is inside of block,
you can LOG it after completion of Graph API response.

Get Facebook profile picture from URL

I want to upload the profile picture from Facebook to Firebase. I tried this answer: Upload Facebook image URL to Firebase Storage
However, Swift is giving me errors on the third line of code of that answer. The code is:
let dictionary = result as? NSDictionary
let data = dictionary?.object(forKey: "data")
let urlPic = (data?.objectForKey("url"))! as! String
Swift is telling me: Cannot call value of non-function type 'Any?!' after I changed the code to what Swift keeps suggesting me:
let urlPic = ((data as AnyObject).object(ForKey: "url"))! as! String
What is the code to use when I want to retrieve the profile picture from Facebook? My goal is to also store it into Firebase, but that will come after I get the profile picture first.
The answer is in Swift 1.2
I took reference here and implemented also
You can do this:
// accessToken is your Facebook id
func returnUserProfileImage(accessToken: NSString)
{
var userID = accessToken as NSString
var facebookProfileUrl = NSURL(string: "http://graph.facebook.com/\(userID)/picture?type=large")
if let data = NSData(contentsOfURL: facebookProfileUrl!) {
imageProfile.image = UIImage(data: data)
}
}
This is the way I got Facebook id:
func returnUserData()
{
let graphRequest : FBSDKGraphRequest = FBSDKGraphRequest(graphPath: "me", parameters: nil)
graphRequest.startWithCompletionHandler({ (connection, result, error) -> Void in
if ((error) != nil)
{
// Process error
println("Error: \(error)")
}
else
{
println("fetched user: \(result)")
if let id: NSString = result.valueForKey("id") as? NSString {
println("ID is: \(id)")
self.returnUserProfileImage(id)
} else {
println("ID es null")
}
}
})
}

Download high resolution profile image Facebook Graph API

Using Swift 3.0, I can download the current users image using this graph call function:
fileprivate func getCurrenUserHighResImageURL() -> String? {
var photoURLOutput : String?
let graphRequest : FBSDKGraphRequest = FBSDKGraphRequest(graphPath: "me", parameters: ["fields" : "picture.type(large)"])
graphRequest.start(completionHandler: { connection, result , error -> Void in
if ((error) != nil) {
print("Error: \(error)")
} else {
let castedResult = result as? [String : AnyObject]
if let castedResult = castedResult {
if let photURL = castedResult["picture"] as? [String : AnyObject] {
let photoData = photURL["data"] as? [String : AnyObject]
photoURLOutput = photoData?["url"] as? String
if let photoURLOutput = photoURLOutput {
CURRENT_USER_URL.updateChildValues(["highResImageURL" : "\(photoURLOutput)"])
}
}
}
}
})
return photoURLOutput
}
However this only returns a 200 x 200 pixel image. Is there any way to make a graph call to to a higher resolution?
Ive seen people making calls to the graph API using a URL like this: https://graph.facebook.com/userId/picture?width=640&height=640
as mentioned in this post : Facebook Graph Profile Picture Link
But when i attempt to download an image from that URL...
func loadUserImage(fromURL urlString:String?) {
if urlString != nil {
let imgURL: URL = URL(string: urlString!)!
let request: URLRequest = URLRequest(url: imgURL)
let session = URLSession.shared
let task = session.dataTask(with: request, completionHandler: {
(data, response, error) -> Void in
if (error == nil && data != nil) {
func display_image() {
let userImage = UIImage(data: data!)
self.userImage.image = userImage
}
DispatchQueue.main.async(execute: display_image)
}
})
task.resume()
}
}
there is no image to be found. (I know this function works because it works on all other images). When I type the link into my browser directly it get a JSON error back saying i don't have authorization.
Has anyone had any luck make a graph call like this? Is there some syntax I have overlooked which will return a higher res profile image?
This is what I have to request 200x200 on an iPhone 7 and 300x300 on a plus size. However I don't think you get an image back with those exact sizes. It might be slightly larger.
let deviceScale = Int(UIScreen.main.scale)
let width = 100 * deviceScale
let height = 100 * deviceScale
let parameters = ["fields": "first_name, last_name, picture.width(\(width)).height(\(height))"]
In summary, the syntax for the param to request a 400x400 would be picture.width(400).height(400).

Facebook Graph Request using Swift3 -

I am rewriting my graph requests with the latest Swift3. I am following the guide found here - https://developers.facebook.com/docs/swift/graph.
fileprivate struct UserProfileRequest: GraphRequestProtocol {
struct Response: GraphResponseProtocol {
init(rawResponse: Any?) {
// Decode JSON into other properties
}
}
let graphPath: String = "me"
let parameters: [String: Any]? = ["fields": "email"]
let accessToken: AccessToken? = AccessToken.current
let httpMethod: GraphRequestHTTPMethod = .GET
let apiVersion: GraphAPIVersion = .defaultVersion
}
fileprivate func returnUserData() {
let connection = GraphRequestConnection()
connection.add(UserProfileRequest()) {
(response: HTTPURLResponse?, result: GraphRequestResult<UserProfileRequest.Response>) in
// Process
}
connection.start()
However, I am getting this error in the connection.add method:
Type ViewController.UserProfileRequest.Response does not conform to protocol GraphRequestProtocol.
I can't seem to figure this out what to change here. It seems like the developer guide is not up to date on Swift3, but I am not sure that is the issue.
Is anyone able to see what is wrong here?
Thanks.
Browsing on the github issues, i found a solution.
https://github.com/facebook/facebook-sdk-swift/issues/63
Facebook documentation for Swift 3.0 and SDK 0.2.0 is not yet updated.
This works for me:
let params = ["fields" : "email, name"]
let graphRequest = GraphRequest(graphPath: "me", parameters: params)
graphRequest.start {
(urlResponse, requestResult) in
switch requestResult {
case .failed(let error):
print("error in graph request:", error)
break
case .success(let graphResponse):
if let responseDictionary = graphResponse.dictionaryValue {
print(responseDictionary)
print(responseDictionary["name"])
print(responseDictionary["email"])
}
}
}
enjoy.
This code works for me, first I make a login with the correct permissions, then I build the GraphRequest for get the user information.
let login: FBSDKLoginManager = FBSDKLoginManager()
// Make login and request permissions
login.logIn(withReadPermissions: ["email", "public_profile"], from: self, handler: {(result, error) -> Void in
if error != nil {
// Handle Error
NSLog("Process error")
} else if (result?.isCancelled)! {
// If process is cancel
NSLog("Cancelled")
}
else {
// Parameters for Graph Request without image
let parameters = ["fields": "email, name"]
// Parameters for Graph Request with image
let parameters = ["fields": "email, name, picture.type(large)"]
FBSDKGraphRequest(graphPath: "me", parameters: parameters).start {(connection, result, error) -> Void in
if error != nil {
NSLog(error.debugDescription)
return
}
// Result
print("Result: \(result)")
// Handle vars
if let result = result as? [String:String],
let email: String = result["email"],
let fbId: String = result["id"],
let name: String = result["name"] as? String,
// Add this lines for get image
let picture: NSDictionary = result["picture"] as? NSDictionary,
let data: NSDictionary = picture["data"] as? NSDictionary,
let url: String = data["url"] as? String {
print("Email: \(email)")
print("fbID: \(fbId)")
print("Name: \(name)")
print("URL Picture: \(url)")
}
}
}
})
Here is my code like. I use Xcode 8, Swift 3 and it works fine for me.
let parameters = ["fields": "email, id, name"]
let graphRequest = FBSDKGraphRequest(graphPath: "me", parameters: parameters)
_ = graphRequest?.start { [weak self] connection, result, error in
// If something went wrong, we're logged out
if (error != nil) {
// Clear email, but ignore error for now
return
}
// Transform to dictionary first
if let result = result as? [String: Any] {
// Got the email; send it to Lucid's server
guard let email = result["email"] as? String else {
// No email? Fail the login
return
}
guard let username = result["name"] as? String else {
// No username? Fail the login
return
}
guard let userId = result["id"] as? String else {
// No userId? Fail the login
return
}
}
} // End of graph request
Your UserProfileRequest should look like this:
fileprivate struct UserProfileRequest: GraphResponseProtocol {
fileprivate let rawResponse: Any?
public init(rawResponse: Any?) {
self.rawResponse = rawResponse
}
public var dictionaryValue: [String : Any]? {
return rawResponse as? [String : Any]
}
public var arrayValue: [Any]? {
return rawResponse as? [Any]
}
public var stringValue: String? {
return rawResponse as? String
}
}