Can't get Facebook data fast enough - ios

I'm trying to get Facebook's user name and photo in my code. I'm able to do both, but the problem is the segue is called before the data return to me! So when I go to the next screen, the variables are nil! Here is my code:
override func viewDidLoad() {
super.viewDidLoad()
//MARK: - FB Button customization:
if (FBSDKAccessToken.currentAccessToken() == nil){
print("Not logged in")
}
else{
print("This is where you perform a segue.")
fetchProfile()
performSegueWithIdentifier("nextfb", sender: self)
}
}
func fetchProfile() {
print("fetch profile")
let parameters = ["fields": "email, name, picture.type(large)"]
FBSDKGraphRequest(graphPath: "me", parameters: parameters).startWithCompletionHandler { (connection, result, error) -> Void in
if error != nil {
print(error)
return
}
if let name = result["name"] as? String {
self.nome = name
print(name)
print(self.nome)
}
if let email = result["email"] as? String {
self.email = email
print(email)
}
// pega a imagem do usuário:
if let data = result["picture"]?!["data"]
{
if let url = data!["url"] as? String
{
let profilePictureURL = url
let imgURL = NSURL(string: profilePictureURL)
let imageData = NSData(contentsOfURL: imgURL!)
let image = UIImage(data: imageData!)
print(image)
self.foto = image!
}
}
}
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "nextfb" {
print("\nO NOME É: \(nome)\n")
let vc = segue.destinationViewController as! HomeViewController
vc.name = self.nome
//vc.photo = self.foto
}
}
The second View Controller prints a nil value for the name AND THEN the first View Controller prints the name and the image! (when I'm already in the second View Controller)
How can I pass those variables via segue properly?? Any help is much appreciated!

You are calling performSegueWithIdentifier("nextfb", sender: self) in the wrong place. You are checking the existence of your access token, and if you aren't logged in, you are invoking the segue.
Instead of that, you should do this:
if let data = result["picture"]?!["data"]
{
if let url = data!["url"] as? String
{
let profilePictureURL = url
let imgURL = NSURL(string: profilePictureURL)
let imageData = NSData(contentsOfURL: imgURL!)
let image = UIImage(data: imageData!)
print(image)
self.foto = image!
performSegueWithIdentifier("nextfb", sender: self)
}
}
This way you only go to the next controller when your data has successfully populated on your view controller.

Related

Pass data from a Parent ViewController to Child VC without Storyboards - Swift 5

I couldn't find a way to pass the data to the Child VC once i get the data through a UITextField from the Github API
protocol GithubManagerDelegate {
func didUpdateGithub(_ githubManager: GithubManager, github: GithubModel)
func didFailWithError(error: Error)
}
struct GithubManager {
let profileUrl = "https://api.github.com/users"
let clientId = // deleted this
let secretId = // deleted this
var delegate: GithubManagerDelegate?
func fetchProfile(profileName: String) {
let urlString = "\(profileUrl)/\(profileName)?client_id=\(clientId)&client_secret=\(secretId)"
performRequest(with: urlString)
}
func performRequest(with urlString: String) {
if let url = URL(string: urlString) {
let session = URLSession(configuration: .default)
let task = session.dataTask(with: url) { (data, response, error) in
if error != nil {
self.delegate?.didFailWithError(error: error!)
return
}
if let safeData = data {
if let github = self.parseJSON(safeData) {
self.delegate?.didUpdateGithub(self, github: github)
print(github)
}
}
}
task.resume()
}
}
func parseJSON(_ githubData: Data) -> GithubModel? {
let decoder = JSONDecoder()
do {
let decodeData = try decoder.decode(GithubData.self, from: githubData)
let name = decodeData.name ?? "The user does not have a name"
let login = decodeData.login ?? "No username by this name." // TODO: Change to give a alert
let avatarUrl = decodeData.avatar_url
let blog = decodeData.blog ?? "No Blog"
let bio = decodeData.bio ?? "No Bio"
let location = decodeData.location ?? "No location"
let followers = decodeData.followers
let following = decodeData.following
let github = GithubModel(githubName: login, githubUser: name, githubAvatar: avatarUrl, githubBio: bio, githubLocation: location, githubBlog: blog, githubFollowers: followers, githubFollowing: following)
return github
} catch {
delegate?.didFailWithError(error: error)
return nil
}
}
}
In the Parent VC in the textFieldDidEndEditing I take the input text and use that to fetch the information from the GithubAPI
if let username = searchTextField.text {
DispatchQueue.main.async {
self.githubManager.fetchProfile(profileName: username)
}
}
Then in my Child VC I use the GithubManagerDelegate, where I use DispatchQueue where, to populate the labels with the information. But the information is empty, because I can't pass the to the child once the data is recieved.
func didUpdateGithub(_ githubManager: GithubManager, github: GithubModel) {
DispatchQueue.main.async {
self.usernameLabel.text = github.githubName
}
}
The way I go from the ParentVC to ChildVC:
navigationController?.pushViewController(profileVC, animated: true)
Hopefully I made myself clear what the problem is...
when you try to pass data between parent ViewController and child ViewController you should add child not navigation to the child.
in child ViewController : var passingName: String?
For Example
let child = #YourChildViewController insitiate it.
self.addChild(child)
child.passingName = passingName
self.view.addSubview(child.view)
child.didMove(toParent: self)
I hope this solution helps you

How can i check a server response value before performing the segue

Here is my code in UploadModel.swift
func uploadSpecialistWithId(login: String, password: String) {
let requestUrl = URL(string: "http://localhost:3000/api/register/specialist")!
var request = URLRequest(url: requestUrl)
request.httpMethod = "POST"
let postParams = "login=\(login)&password=\(password)"
print(postParams)
request.httpBody = postParams.data(using: String.Encoding.utf8)
let task = URLSession.shared.dataTask(with: request) {(data, response, error)
in
if(error != nil) {
print("error -> \(error!.localizedDescription)")
} else {
print("data uploaded")
self.parseJSONSpecialistResponse(data: data!)
}
}
task.resume()
}
func parseJSONSpecialistResponse(data: Data) {
var jsonResult = NSDictionary()
let specialist = Specialist()
do {
jsonResult = try JSONSerialization.jsonObject(with: data, options: .allowFragments) as! NSDictionary
} catch let error as NSError {
print("error -> \(error.localizedDescription)")
}
let items = NSMutableArray()
if let login = jsonResult["login"] as? String,
let specialistId = jsonResult["_id"] as? String {
specialist.login = login
specialist.specialistId = specialistId
print("Added \(specialist.itemDescribtion())")
items.add(specialist)
} else {
let errorCode = jsonResult["code"] as! Int
print("Erorr with code -> \(errorCode)")
items.add(errorCode)
}
DispatchQueue.main.async(execute: { () -> Void in
self.delegate.itemsUploaded(items: items)
})
}
But when i was trying to reg the specialist with existing login the segue had performed before i handled the server response. What should I change to solve this problem? Here is my code in ViewController.swift
func itemsUploaded(items: NSArray) {
if let errorCode = items[0] as? Int {
if errorCode == 11000 {
warningLabel.text = "This login is already used"
error = true
}
}
if let specialist = items[0] as? Specialist {
self.specialistId = specialist.specialistId!
print("Value after itemsUploaded --> \(self.specialistId)")
}
}
#IBAction func doneButtonTouched(_ sender: Any) {
uploadModel.uploadSpecialistWithId(login: loginTextField.text!, password: passwordTextField.text!)
if(error == false) {
print("Segue perform value -> \(specialistId)")
performSegue(withIdentifier: "regToRegCompleteSegue", sender: self)
}
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
let destination = segue.destination as! RegistrationCompleteViewController
print("Segue prepare value -> \(specialistId)")
destination.specialistId = self.specialistId
}
I understand that i have this issue because of asynchronously of dataTask. But why does it not working when the self.delegate is already in the main queue?
EDIT: Adding a completionHandler didn't solve my problem, but Kamran's offer is working.
You should perform segue when you are done with response by moving the below lines in itemsUploaded as below,
#IBAction func doneButtonTouched(_ sender: Any) {
uploadModel.uploadSpecialistWithId(login: logiTextField.text!, password: passwordTextField.text!)
}
func itemsUploaded(items: NSArray) {
if let errorCode = items[0] as? Int {
if errorCode == 11000 {
warningLabel.text = "This login is already used"
error = true
}
}
if let specialist = items[0] as? Specialist {
self.specialistId = specialist.specialistId!
print("Value after itemsUploaded --> \(self.specialistId)")
}
if(error == false) {
print("Segue perform value -> \(specialistId)")
performSegue(withIdentifier: "regToRegCompleteSegue", sender: self)
}
}
You should call performSegue inside the itemsUploaded, once it will only be called when the data is ready.
Another way that you could do this is by using #escaping in your function parseJSONSpecialistResponse to know when it is completed. Inside the block of the completion, you can then call performSegue.

Swift: Setting name, photo, and cover from Facebook JSON to variables is not working.

I'm new to using the Facebook API, and I'm trying to make a profile page with the user's name, profile picture and cover photo all taken from Facebook. I was able to parse through the JSON data provided and get the name, cover url and picture url, but when I try and set them to IBOutlets that are in another class, it doesn't change anything. The UILabel and UIImageViews remain the same.`
class ViewController: UIViewController, BWWalkthroughViewControllerDelegate, FBSDKLoginButtonDelegate {
var profilePageViewController : profilePage!
var profileVC = profilePage()
override func viewDidLoad() {
super.viewDidLoad()
let stb = UIStoryboard(name: "Main",bundle: nil)
profilePageViewController = stb.instantiateViewController(withIdentifier: "profile") as! profilePage
profileVC = profilePageViewController
}
// MARK: FACEBOOK DELEGATE
func loginButtonDidLogOut(_ loginButton: FBSDKLoginButton!) {
print("Logged out of Facebook")
}
func loginButton(_ loginButton: FBSDKLoginButton!, didCompleteWith result: FBSDKLoginManagerLoginResult!, error: Error!) {
if error != nil{
print(error)
return
}
}
func showFacebookInformation(){
FBSDKGraphRequest(graphPath: "/me", parameters: ["fields": "id, name, picture.type(large),cover"]).start(completionHandler: { (connction, result, error) in
if(error != nil){
print("Failed to start Graph API request", error)
return
}
print(result)
let result = result as! Dictionary<String,Any>
// GETTING COVER PHOTO URL
let cover = result["cover"] as! Dictionary<String,Any>
let coverSource = cover["source"] as! String
// GETTING NAME
let name = result["name"] as! String
// GETTING PROFILE PICTURE
let picture = result["picture"] as! Dictionary<String,Any>
let data = picture["data"] as! Dictionary<String,Any>
let url = data["url"] as! String
print("The profile picture URL is: ",url)
print("The cover photo url is: ",coverSource)
print("The name is: ",name)
DispatchQueue.main.async {
self.profileVC.profilePicture.sd_setImage(with: URL(string:url))
self.profileVC.userName.text = name
}
})
}
#IBAction func logUserIn(){
FBSDKLoginManager().logIn(withReadPermissions: ["public_profile"], from: self) { (result, error) in
if(error != nil){
print("Facebook Login failed: ",error)
return
}
else{
self.profileVC.loadViewIfNeeded()
self.showFacebookInformation()
}
}
}
}
}
I can see that you are trying to set UILabel and UIImageView from the Viewcontroller where you have instantiated rather pass the data to the to profile viewcontroller and set values there in its viewdidload or in viewwillappear methods. It is happening because the layout has not been prepared yet.

Not able to get the cover , profile photo to display in screen

I am currently working on facebook login app.And i am trying to display user cover photo and user profile pic.
But here i was not able to pic the cover, profile photo and not able to populate in my image views.
here my code :
func getFacebookGraphAndSegue(token:String) {
guard let req = FBSDKGraphRequest(graphPath: "me", parameters: ["fields":"email,name, first_name, last_name,id,cover,picture.width(100).height(100)"], tokenString: token, version: nil, httpMethod: "GET") else {
print("could not get Facebook user info")
return
}
req.start(completionHandler: { (connection, result, error) in
if(error == nil) {
print("result \(result)")
guard let userInfo = result as? [String: AnyObject] else {print("bad result");return}
let name = userInfo["name"] as? String ?? ""
// let cover = (userInfo["cover"] as? UIImage!)["source"]{
// var coverUrl = cover as? String
//
// }
self.performSegue(withIdentifier: "loginSegue", sender: userInfo)
}
else {
print("error \(error)")
}
})
}
//--------------------------------------------------------------------------------------------
// MARK: - Segue
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "loginSegue" {
if let vc = segue.destination as? UserViewController {
if let info = sender as? [String: AnyObject] {
let email = info["email"] as? String ?? ""
let name = info["name"] as? String ?? ""
let firstName = info["first_name"] as? String ?? ""
let lastName = info["last_name"] as? String ?? ""
let lbl:String = "\nNAME:\(name)"
print(lbl)
vc.info = lbl
}
}
}
}
the commented line in above function is i tried to get the cover photo as well as profile pic and below i need to display.
In my next screen only i will display user name, profile pic,cover photo :
#IBOutlet weak var labelUserInfo: UILabel!
#IBOutlet weak var coverpageImage: UIImageView!
#IBOutlet weak var profilePicImage: UIImageView!
var info:String = ""
override func viewDidLoad() {
super.viewDidLoad()
self.labelUserInfo.text = info
}
#IBAction func onLogout(_ sender: UIButton) {
//Facebook Logout
print("on Facebook Logout Tapped")
let loginManager = LoginManager()
loginManager.logOut()
self.performSegue(withIdentifier: "logouSegue", sender: nil)
}
please help me out. How can i get the cover , profile pic to display in my screen / image view.
thanks

Performing Segue after Login

So I'm designing an application where, like most apps, takes users to the "home page" after a successful login. However, I can't quite figure out how to get it to work. The code for my Login page is as follows:
import UIKit
class LoginVC: UIViewController {
#IBOutlet weak var usernameTxt: UITextField!
#IBOutlet weak var passwordTxt: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
//#IBAction func userLogin(_ sender: AnyObject) {
#IBAction func userLogin(_ sender: AnyObject) {
// if textboxes are empty
if usernameTxt.text!.isEmpty || passwordTxt.text!.isEmpty {
// red placeholders
usernameTxt.attributedPlaceholder = NSAttributedString(string: "Username", attributes: [NSForegroundColorAttributeName: UIColor.red])
passwordTxt.attributedPlaceholder = NSAttributedString(string: "Password", attributes: [NSForegroundColorAttributeName: UIColor.red])
} else {
// shortcuts
let username = usernameTxt.text!.lowercased()
let password = passwordTxt.text!
// send request to mysql db
// Create a user in the mySQL db
// the exclamation at the end means we insist to launch it
// url to php file
let url = NSURL(string: "https://cgi.soic.indiana.edu/~team7/login.php")!
// request to the file
let request = NSMutableURLRequest(url: url as URL)
// method to pass data to this file via the POST method
request.httpMethod = "POST"
// what occurs after the question mark in the url
// body to be appended to url from values in textboxes
let body = "username=\(username)&password=\(password)"
// appends body to request that will be sent
request.httpBody = body.data(using: String.Encoding.utf8)
// launching
URLSession.shared.dataTask(with: request as URLRequest, completionHandler: { (data:Data?, response:URLResponse?, error:Error?) -> Void in
if error == nil {
// get main queue in code process to communicate back
DispatchQueue.main.async(execute: {
// do this unless some error which is caught by catch
do {
// get json result
let json = try JSONSerialization.jsonObject(with: data!, options: .mutableContainers) as? NSDictionary
// guard let is the same thing as if let
// asign json to new variable in secure way
// original guard let used
guard let parseJSON = json else {
print("Error while parsing")
return
}
// get id from parseJSON dictionary
let id = parseJSON["id"] as? String
// if there is some id value
if id != nil && response != nil {
print(parseJSON)
// successfully logged in
//let userID = parseJSON["id"] as! String
//let userN = parseJSON["username"] as! String
//let eMail = parseJSON["email"] as! String
//print(parseJSON["username"] ?? String.self)
//let myVC = self.storyboard?.instantiateViewController(withIdentifier: "RetrievalVC") as! RetrievalVC
//myVC.id_Outlet.text = userID
//myVC.full_Outlet.text = userN
//myVC.email_Outlet.text = eMail
//
//self.navigationController?.pushViewController(myVC, animated: true)
}
} catch {
print("Caught an error \(error)")
}
})
// if unable to process request
} else {
print("error: \(error)")
}
}).resume()
//performSegue(withIdentifier: "loginSuccess", sender: LoginVC.self)
}
}
}
I am trying to use
performSegue(withIdentifier: "loginSuccess", sender: LoginVC.self)
In order to perform the segue but I'm not sure where in the code it should go.
Any suggestions or changes I need to make to the code?
It depends on back end logic.I assume that parseJSON["id"] is returned only if user is verified. So you can use this
let id = parseJSON["id"] as? String
// if there is some id value
if id != nil {
// perform segue here
}
You can perform a segue when error is nil and you are response contains data...
if id != nil && response != nil {
performSegue(withIdentifier: "loginSuccess", sender: LoginVC.self)
}

Resources