Facebook Graph Request using Swift3 - - swift

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
}
}

Related

Swift: listen to url property then download photo

I run this code in viewDidLoad, but Profile.currentProfile does not have the photoUrl yet, so it is nil and this code never runs
private func getProfilePicture() {
if let photoURLString = Profile.currentProfile?.photoUrl {
if let photoURL = URL(string: photoURLString) {
if let photoData = try? Data(contentsOf: photoURL) {
self.profilePhotoView.image = UIImage(data: photoData)
self.letsGoButton.isEnabled = true
}
}
} else {
self.profilePhotoView.image = UIImage(named: "default-profile-icon")
}
}
How can I wait until the photoUrl is not nil, then run this code? Thanks
Rik
(edit) this is how profile is set. This is called before the viewController is instantiated.
func copyProfileFieldsFromFB(completionHandler: #escaping ((Error?) -> Void)) {
guard AccessToken.current != nil else { return }
let request = GraphRequest(graphPath: "me",
parameters: ["fields": "email,first_name,last_name,gender, picture.width(480).height(480)"])
request.start(completionHandler: { (_, result, error) in
if let data = result as? [String: Any] {
if let firstName = data["first_name"] {
Profile.currentProfile?.firstName = firstName as? String
}
if let lastName = data["last_name"] {
Profile.currentProfile?.lastName = lastName as? String
}
if let email = data["email"] {
Profile.currentProfile?.email = email as? String
}
if let picture = data["picture"] as? [String: Any] {
if let imageData = picture["data"] as? [String: Any] {
if let url = imageData["url"] as? String {
Profile.currentProfile?.photoUrl = url
}
}
}
}
completionHandler(error)
})
}
Normally, you'll want to use completion handlers to keep track of asynchronous activities. So in your viewDidLoad() you could call something like
Profile.currentProfile?.getPhotoURL { urlString in
if let photoURL = URL(string: photoURLString) {
if let photoData = try? Data(contentsOf: photoURL) {
self.profilePhotoView.image = UIImage(data: photoData)
self.letsGoButton.isEnabled = true
}
}
}
And on your Profile class it would look something like this:
func getPhotoURL(completion: #escaping (urlString) -> Void) {
// get urlString
completion(urlString)
}
You can add private var profileUrl and use didSet observing with it:
... //e.g. your controller
private var profileUrl: URL? {
didSet {
if let url = profileUrl {
getProfilePicture(from: url)// update your func
}
}
}

How to get user email id from Facebook

Why can't I get user mail id for Facebook? I have attached my code below. I have doubt in login and registration using API
func getFacebookUserInfo() {
if(FBSDKAccessToken.current() != nil)
{
//print permissions, such as public_profile
print(FBSDKAccessToken.current().permissions)
let graphRequest = FBSDKGraphRequest(graphPath: "me", parameters: ["fields" : "id, first_name, last_name, email"])
let connection = FBSDKGraphRequestConnection()
connection.add(graphRequest, completionHandler: { (connection, result, error) -> Void in
let data = result as! [String : AnyObject]
self.firstnameLabel.text = data["first_name"] as? String
self.lastnameLabel.text = data["last_name"] as? String
guard let result = result as? NSDictionary else{ return }
let email = result["email"] as? String
self.googleemail = email!
let FBid = data["id"] as? String
let url = NSURL(string: "https://graph.facebook.com/\(FBid)/picture?type=large&return_ssl_resources=1")
self.imageView.image = UIImage(data: NSData(contentsOf: url! as URL)! as Data)
self.validateUserData()
})
connection.start()
}
}
facebook button click
#IBAction func btnFacebookClick(_ sender: Any)
{
// GeneralClass.startProgress()
loginManager.logIn(readPermissions: [ ReadPermission.publicProfile,ReadPermission.email], viewController: self) { (loginResult) in
switch loginResult {
case .failed(let error):
self.loginManager.logOut()
print(error)
case .cancelled:
self.loginManager.logOut()
print("User cancelled login.")
case .success(let grantedPermissions, let declinedPermissions, let accessToken):
print(grantedPermissions)
print(declinedPermissions)
print("Logged in!")
self.getFBUserData(accessToken: accessToken)
}
}
}
get data
func getFBUserData(accessToken: AccessToken)
{
let request = GraphRequest(graphPath: "me",
parameters: ["fields" : "first_name,middle_name,last_name,name,link,email,id,picture" as AnyObject],
accessToken:accessToken,
httpMethod: .GET)
request.start { (httpResponse, result) in
switch result {
case .success(let response):
let responseDictionary = response.dictionaryValue
print("USER DATA:---\(responseDictionary! as NSDictionary))")
let dicData = responseDictionary! as NSDictionary
if ((dicData["email"]) != nil) {
let email = dicData["email"] as! String
let user_firstName = dicData["first_name"] as! String
let imageUrl = (((dicData["picture"] as! NSDictionary)[KEY_DATA] as! NSDictionary)["url"] as! String)
}
case .failed(let error):
self.loginManager.logOut()
print(error)
}
}
}
Seems like the email value is nil. Set nil checking before assigning the value.
if let email = result["email"] as? String {
self.googleemail = email!
}
Hope this will help.

store facebook information into Firebase Database using Facebook IOS swift SDK

I'm trying to store facebook user data into firebase database but I keep getting the error "Cannot convert Any? to expected type String"
((FBSDKAccessToken.current()) != nil){
FBSDKGraphRequest(graphPath: "me", parameters: ["fields": "id, name, picture.type(large), email"]).start(completionHandler: { (connection, result, error) -> Void in
if (error == nil && result != nil) {
guard let fbData = result as? [String:Any] else { return }
let fbid = fbData["id"]
let name = fbData["name"]
self.ref.child("users").child(fbid).setValue([
"id": fbid,
"name": name
])
}
})
I also want to store the picture url into the database. How can I do this?
Using Facebook IOS Swift SDK and Firebase
Try my I implement this function. This is from the production app and it works well for us. I also recommend uploading profile image in Firebase storage or other storage, because after a while the profile image url is not valid.
class func getAllFacebookData(success: ((_ result: [String : Any]) -> Void)?, fail: ((_ error: Error) -> Void)?) {
guard !isGetDataFromFacebook else { return }
DispatchQueue.global(qos: .background).async {
guard let tokenString = FBSDKAccessToken.current()?.tokenString else { return }
guard let req = FBSDKGraphRequest(graphPath: "me", parameters: ["fields": "name,age_range,birthday,gender,email,first_name,last_name,picture.width(1000).height(1000),work,education,hometown,location, friends"], tokenString: tokenString, version: nil, httpMethod: "GET") else { return }
req.start { (connection, result, error) in
if error == nil {
guard let _result = result as? [String : Any] else { return }
let _picture = _result["picture"] as? [String : Any]
let _pictureData = _picture?["data"] as? [String : Any]
let _isSilhouette = _pictureData?["is_silhouette"] as? Bool
let userPref = UserDefaults.standard
userPref.set(_isSilhouette, forKey: "UserHasSilhouetteImage")
userPref.synchronize()
debugPrint("facebook result", _result)
isGetDataFromFacebook = true
syncUserInfoInDatabase(_result)
success?(_result)
} else {
debugPrint("request", error!)
fail?(error!)
}
}
}
}
fileprivate class func syncUserInfoInDatabase(_ userInfo: [String : Any]) {
let realmManager = RealmManager()
guard let currentUser = realmManager.getCurrentUser() else { return }
guard let userInfoModel = createUserInfoModel(userInfo) else { return }
do {
let realm = try Realm()
try realm.write {
currentUser.info = userInfoModel
}
} catch {
debugPrint("realm syncUserInfoInDatabase error", error.localizedDescription)
}
savePhoto(userInfo)
let firebaseDatabaseGeneralManager = FirebaseDatabaseGeneralManager()
firebaseDatabaseGeneralManager.updateCurrentUser(success: nil, fail: nil)
// crete a personal settings
let firUserSettingsDatabaseManager = FIRUserSettingsDatabaseManager()
firUserSettingsDatabaseManager.createStartPeopleFilterSettings(success: nil, fail: nil)
let userSearchLocationModel = UserSearchLocationModel()
userSearchLocationModel.userID = currentUser.id
userSearchLocationModel.birthdayTimeStamp = currentUser.birthdayTimeStamp
userSearchLocationModel.gender = currentUser.gender
switch currentUser.gender {
case UserPeopleFilterSettings.FilterGenderMode.female.description:
userSearchLocationModel.genderIndex = UserPeopleFilterSettings.FilterGenderMode.female.index
case UserPeopleFilterSettings.FilterGenderMode.male.description:
userSearchLocationModel.genderIndex = UserPeopleFilterSettings.FilterGenderMode.male.index
default: break
}
let firPeopleSearchDatabaseManager = FIRPeopleSearchDatabaseManager()
firPeopleSearchDatabaseManager.saveUserSearchLocationModel(userSearchLocationModel, success: nil, fail: nil)
}
private class func savePhoto(_ userInfo: [String : Any]) {
if let pictureDict = userInfo["picture"] as? [String : Any], let pictureDataDict = pictureDict["data"] as? [String : Any] {
if let urlPath = pictureDataDict["url"] as? String {
let firImageDatabaseManager = FIRImageDatabaseManager()
firImageDatabaseManager.saveProfileImage(urlPath, fileName: nil, isFacebookPhoto: true, realmSaved: nil)
}
}
}

Swift version of Facebook iOS SDK and how to access data in Response

I have taken over a Swift project and need to add Facebook login functionality. I am getting it to mostly work but am having a problem with this sample code here (https://developers.facebook.com/docs/swift/graph):
import FacebookCore
struct MyProfileRequest: GraphRequestProtocol {
struct Response: GraphResponseProtocol {
init(rawResponse: Any?) {
// Decode JSON from rawResponse into other properties here.
}
}
var graphPath = "/me"
var parameters: [String : Any]? = ["fields": "id, name"]
var accessToken = AccessToken.current
var httpMethod: GraphRequestHTTPMethod = .GET
var apiVersion: GraphAPIVersion = .defaultVersion
}
let connection = GraphRequestConnection()
connection.add(MyProfileRequest()) { response, result in
switch result {
case .success(let response):
print("Custom Graph Request Succeeded: \(response)")
print("My facebook id is \(response.dictionaryValue?["id"])")
print("My name is \(response.dictionaryValue?["name"])")
case .failed(let error):
print("Custom Graph Request Failed: \(error)")
}
}
connection.start()
I'm getting an error on compiling the for the line with the dictionaryValue optional saying /Users/jt/a-dev/tabfb/tabfb/LoginViewController.swift:72:31: Value of type 'MyProfileRequest.Response' has no member 'dictionaryValue' . How would I access the user name or id using this?
I faced this problem today as well. I got the user id and name inside MyProfileRequest
struct Response: GraphResponseProtocol {
init(rawResponse: Any?) {
// Decode JSON from rawResponse into other properties here.
guard let response = rawResponse as? Dictionary<String, Any> else {
return
}
if let name = response["name"],
let id = response["id"] {
print(name)
print(id)
}
}
}
EDIT: I redesigned my code like this to use the values in .success(let response) case
struct Response: GraphResponseProtocol {
var name: String?
var id: String?
var gender: String?
var email: String?
var profilePictureUrl: String?
init(rawResponse: Any?) {
// Decode JSON from rawResponse into other properties here.
guard let response = rawResponse as? Dictionary<String, Any> else {
return
}
if let name = response["name"] as? String {
self.name = name
}
if let id = response["id"] as? String {
self.id = id
}
if let gender = response["gender"] as? String {
self.gender = gender
}
if let email = response["email"] as? String {
self.email = email
}
if let picture = response["picture"] as? Dictionary<String, Any> {
if let data = picture["data"] as? Dictionary<String, Any> {
if let url = data["url"] as? String {
self.profilePictureUrl = url
}
}
}
}
}
And in the success case you can get the values like this:
let connection = GraphRequestConnection()
connection.add(MyProfileRequest()) { response, result in
switch result {
case .success(let response):
print("My facebook id is \(response.id!)") //Make sure to safely unwrap these :)
print("My name is \(response.name!)")
case .failed(let error):
print("Custom Graph Request Failed: \(error)")
}
}
connection.start()
import FBSDKLoginKit //FBSDKLoginKit installs automatically when you install FacebookCore through CocoaPods
///Inside your view controller
func loginButton(_ loginButton: FBSDKLoginButton!, didCompleteWith result: FBSDKLoginManagerLoginResult!, error: Error!) {
/// DEFAULT
//fired when fb logged in through fb's default login btn
if error != nil {
print(error)
return
}
showDetails()
}
fileprivate func showDetails(){
FBSDKGraphRequest(graphPath: "/me", parameters: ["fields": "id, name, first_name, last_name, email, gender"]).start { (connection, result, err) in
////use link for more fields:::https://developers.facebook.com/docs/graph-api/reference/user
if err != nil {
print("Failed to start graph request:", err ?? "")
return
}
let dict: NSMutableDictionary = result as! NSMutableDictionary
print("The result dict of fb profile::: \(dict)")
let email = dict["email"] as! String!
print("The result dict[email] of fb profile::: \(email)")
let userID = dict["id"] as! String
print("The result dict[id] of fb profile::: \(userID)")
// self.profileImage.image = UIImage(named: "profile")
let facebookProfileUrl = "http://graph.facebook.com/\(userID)/picture?type=large"
}
}
//make sure you add read permissions for email and public profile
override func viewDidLoad(){
super.viewDidLoad()
loginButtonFromFB.delegate = self //inherit FBSDKLoginButtonDelegate to your class
loginButtonFromFB.readPermissions = ["email", "public_profile"]
}

Cannot retrieve email from facebook using swift 3 and ios 10

Hello i am trying to retrieve my email from facebook as i am playing the facebook ios sdk using swift. IOS platform is 10, swift 3 and Xcode 8. I followed tutorials online but having trouble retrieving email.
below is my code:
if FBSDKAccessToken.current() == nil {
print("I got token")
let fbButton = FBSDKLoginButton()
fbButton.readPermissions = ["public_profile", "email", "user_friends"]
view.addSubview(fbButton)
fbButton.center = view.center
fbButton.delegate = self
self.fetchprofile()
}
else {
print("Dont have token")
let loginView : FBSDKLoginButton = FBSDKLoginButton()
self.view.addSubview(loginView)
loginView.center = self.view.center
loginView.readPermissions = ["public_profile", "email", "user_friends"]
loginView.delegate = self
}
func loginButton(_ loginButton: FBSDKLoginButton!, didCompleteWith result: FBSDKLoginManagerLoginResult!, error: Error!) {
if error != nil {
print(error.localizedDescription)
return
}
print("I'm in")
fetchprofile()
}
func fetchprofile() {
print("Getting profile")
let parameters = ["fields": "email"]
let graphRequest:FBSDKGraphRequest = FBSDKGraphRequest(graphPath: "me", parameters: parameters, httpMethod: "GET")
graphRequest.start(completionHandler: {(connection, result, error) -> Void in
if error != nil {
print("Error retrieving details: \(error?.localizedDescription)")
return
}
guard let result = result as? [String:[AnyObject]], let email = result["email"] else {
return
}
print("Email: \(email)\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n")
self.view.backgroundColor = UIColor.red
})
}
and in my appdelegate.swift file :
//have both google and facebook signin. Google works but facebook doesn't
func application(_ app: UIApplication, open url: URL, options: [UIApplicationOpenURLOptionsKey : Any] = [:]) -> Bool {
return GIDSignIn.sharedInstance().handle(url,
sourceApplication: options[UIApplicationOpenURLOptionsKey.sourceApplication] as! String,
annotation: options[UIApplicationOpenURLOptionsKey.annotation]) ||
FBSDKApplicationDelegate.sharedInstance().application(app, open: url, sourceApplication: options[UIApplicationOpenURLOptionsKey.sourceApplication] as! String, annotation: options[UIApplicationOpenURLOptionsKey.annotation])
}
I am able to log in and log out but not able to retrieve email.
UPDATE Actually when i actually pass print(email) i can see it on the console as an optional statement. I'm having trouble displaying it without optional statment
I have solved the problem in this way:
func fetchProfile(){
FBSDKGraphRequest(graphPath: "/me", parameters: ["fields" : "email, name, id, gender"])
.start(completionHandler: { (connection, result, error) in
guard let result = result as? NSDictionary, let email = result["email"] as? String,
let user_name = result["name"] as? String,
let user_gender = result["gender"] as? String,
let user_id_fb = result["id"] as? String else {
return
}
})
}
This solution worked well for me without the "/" in the graphPath parameter!
FBSDKGraphRequest(graphPath: "me" .....