Facebook Friends List in TableView: Think need Async Programming? - facebook

I am trying to get a friend list from Facebook, i have the call request inside the tableview cellforrowatindexpath but i also have it in a separate function too. Thought to try both ways but i get the same result.
If i do a print from inside the completion block i do get results, however, if i try to print friendProfile outside the completion block it returns nil and therefore when i create the table cells they unwrap nil and the code fails. I think it is because the completion block is not returning data until after i call to create the cells. I am reading, and perhaps this has to do with Asynchronous programming? Or needing to wait till data is returned from Facebook? Or will the completion handler always return nil?
`
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var request = FBSDKGraphRequest(graphPath:"/me/taggable_friends", parameters: ["fields":"user_id,first_name,last_name,name,picture.type(large)"]);
request.startWithCompletionHandler ({ (connection : FBSDKGraphRequestConnection!, result : AnyObject!, error : NSError!) -> Void in
if error == nil {
//print("Friends are : \(result)")
let jsonResult = result
self.friendsArray = jsonResult["data"] as! NSArray
var index = 0
for data in self.friendsArray {
let fn = data["first_name"]! as! String
let ln = data["last_name"]! as! String
let id = data["id"]! as! String
let picture = data["picture"] as! NSDictionary
let parsePic = picture["data"] as! NSDictionary
let url = parsePic["url"] as! String
self.friendProfile .updateValue((fn + " " + ln), forKey: id)
self.friendProfilePic .updateValue(url, forKey: id)
self.friendProfileID . updateValue(id, forKey: index)
index++
}
} else {
print("Error Getting Friends \(error)");
}
//if i do a print(friendprofilId) in here it works
})
//but a print(friendprofilId) here returns nil
let cellidentifier = "SettingsTableViewCell"
let cell = tableView.dequeueReusableCellWithIdentifier(cellidentifier, forIndexPath: indexPath) as! SettingsTableViewCell
let id = self.friendProfileID[indexPath.row]
print(self.friendProfileID)
if (id != nil) {
let name = self.friendProfile[id!]
let url = self.friendProfilePic[id!]
// cell.imageView?.image = returnFriendProfileImage(url!)
cell.textLabel!.text = name
}
return cell
}
`

You need to call tableView.reloadData() in the place where you have it commented that it works. Also, don't retrieve your data in cellForRowAtIndexPath. Just call the function you had in viewDidLoad

An asynchronous function call with return immediately, while the completion block will complete once it receives a response from the web server. In your case, you call startWithCompletionHandler, which returns immediately and configure your cell before the completion block receives a response from Facebook's server. It makes sense that friendProfilId is still null since you haven't received the data from Facebook.

Related

How would I parse all of the data from a paged API at once in Swift 4?

I'm making an app with a tableview and search controller using the rick and morty API, https://rickandmortyapi.com/api/character/. The API is paged and I'm able to parse the data from the first page and display it to my tableview. I'm also able get the other pages of the API when I scroll through the tableview. I can't seem to figure out how to parse all the data from the pages at once. When I use the search controller I can't search for all the characters, until I scroll through the tableView to get all of the characters. I want to be able to search for any character without having to scroll through the tableView first. How would I parse all the data from the different pages at once and display the data to the tableview? Any help is appreciated, thank you!
This is my current code for parsing the data and getting the other pages when scrolling through the tableView
func getIntitalRickAndMortyData(){
downloadedDataArray = []
//here first page is next page
nextPageUrl = "https://rickandmortyapi.com/api/character/"
getRickAndMortyData()
filteredCharacterArray = downloadedDataArray
}
func getRickAndMortyData() {
//construct the url, use guard to avoid nonoptional
guard let urlObj = URL(string: nextPageUrl) else
{ return }
//fetch data
URLSession.shared.dataTask(with: urlObj) {[weak self](data, response, error) in
//to avoid non optional in JSONDecoder
guard let data = data else { return }
do {
//decode object
let downloadedRickAndMorty = try JSONDecoder().decode(PagedCharacters.self, from: data)
self?.downloadedDataArray.append(contentsOf: downloadedRickAndMorty.results)
self?.nextPageUrl = downloadedRickAndMorty.info.next
self?.filteredCharacterArray = (self?.downloadedDataArray)!
self?.currentPage += 1
DispatchQueue.main.async {
self?.tableView.reloadData()
}
//print(self?.aryDownloadedData as Any)
} catch {
print(error)
}
}.resume()
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let count = self.downloadedDataArray.count
if count > 1 {
let lastElement = count - 1
if indexPath.row == lastElement {
//call get api for next page
getRickAndMortyData()
}
}
guard let cell = tableView.dequeueReusableCell(withIdentifier: "rickandmortyCell") as? CharacterTableViewCell else { return UITableViewCell() }
let results: Results
if isFiltering() {
results = filteredCharacterArray[indexPath.row]
} else {
results = downloadedDataArray[indexPath.row]
}
cell.selectionStyle = .none
cell.nameLabel.text = results.name
cell.statusLabel.text = results.status
cell.genderLabel.text = results.gender
cell.originLabel.text = results.origin.name
cell.lastlocationLabel.text = results.location.name
let id = String(results.id)
cell.idLabel.text = id
return cell
}
}
I've tried doing it using a while loop and keeping a current page count and incrementing it, but nothing displays and I get this in my console "XPC connection interrupted"
func getAllRickAndMortyData() {
while currentPage <= 25 {
getRickAndMortyData()
}
}

Swift tableview load more data

I am implementing a method to load more results returned from my sql server when the table view reaches the bottom.
The issue I am facing is that the getData() method is called in a loop and I cant figure out why.
The log repeats the output below until the application is terminated:
Index Path:12
Last Element: 12
Index Path:12
Last Element: 12
I suspect its one of those which is creating the loop but I can't work out what to change.
Any assistance with this is much appreciated
This is my willDisplayCell method:
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
let lastElement = id.count - 1
if indexPath.row == lastElement {
print("Index Path:\(indexPath.row)")
print("Last Element: \(lastElement)")
limitf = "\(lastElement)"
getData()
}
}
and the function I am using to get the data:
func getData(){
let defaults = UserDefaults.standard
let userID = defaults.string(forKey: "id")
if(limitf == ""){
id.removeAll()
firstname.removeAll()
lastname.removeAll()
fbid.removeAll()
image.removeAll()
totalratings.removeAll()
mobile.removeAll()
}
let url = NSURL(string: "https://www.****/getapplications.php?&jobid=\(jobid)&limitf=\(limitf)")
// print(url!)
let task = URLSession.shared.dataTask(with: url as! URL) { (data, response, error) -> Void in
if let urlContent = data {
do {
if let jsonResult = try JSONSerialization.jsonObject(with: urlContent, options: []) as? [[String:AnyObject]] {
var i = 0
while i < jsonResult.count {
self.id.append(jsonResult[i]["id"]! as! String)
self.firstname.append(jsonResult[i]["firstname"]! as! String)
self.lastname.append(jsonResult[i]["lastname"]! as! String)
self.fbid.append(jsonResult[i]["fbid"]! as! String)
self.image.append(jsonResult[i]["image"]! as! String)
self.totalratings.append(jsonResult[i]["totalratings"]! as! String)
self.mobile.append(jsonResult[i]["mobile"]! as! String)
i = i + 1
}
}
} catch {
print("JSON serialization failed")
}
} else {
print("ERROR FOUND HERE")
}
DispatchQueue.main.async {
self.tableView.reloadData()
}
}
task.resume()
}
}
If you only have 12 records on the server, and the 12th record is visible on screen, then every time it is displayed, getData() will be called. The server will probably return no more records (so the count will remain 12) and the call to .reloadData() will cause the 12 records to be displayed again, which will call getData() again on the 12th, and so on ...
You should not call reloadData() when no new records were received.
Your problem is you load self.tableView.reloadData() in getData().
This method means it will run those methods in tableView delegate once again.
As long as you put self.tableView.reloadData() away, this loop will end.
You are calling getData() in willDisplayCell for the last cell being displayed and then clear the contents and reload the table. This means the last cell is always called to reload the data - which is why it keeps looping.
What you should do is detect as you are doing when the last row is loaded, but when you call getData(), rather than clearing out the existing data, you should append the new data rows and use insertRowsAtIndexPaths to update your tableview.

UICollectionViewCell only showing when a certain criteria is met

I do not know if this is possible but, I am trying to return a cell using CollectionView cellForItem only if a certain criteria is met.
Here is my function:
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "FriendsRecentCell", for: indexPath) as! FriendsRecentCell
var fObj = PFObject(className: FRIENDS_CLASS_NAME)
fObj = recentArray[indexPath.row]
// Get User Pointer
let userPointer = fObj[FRIENDS_IS_FRIEND_WITH] as! PFUser
userPointer.fetchIfNeededInBackground(block: { (user, error) in
if error == nil {
// Get heys sent to you as currentUser
let query = PFQuery(className: HEYS_CLASS_NAME)
query.whereKey(HEYS_RECEIVER, equalTo: PFUser.current()!)
query.whereKey(HEYS_SENDER, equalTo: userPointer)
query.countObjectsInBackground { (count, error) in
if count != 0 {
// Get User fullname
cell.userLabelRecent.text = "\(userPointer[USER_FULLNAME]!)"
// Get Avatar
cell.avatarImageRecent.image = UIImage(named: "avatar")
cell.avatarImageRecent.layer.cornerRadius = cell.avatarImageRecent.bounds.size.width/2
let imageFile = userPointer[USER_AVATAR] as? PFFile
imageFile?.getDataInBackground(block: { (imageData, error) in
if error == nil {
if let imageData = imageData {
cell.avatarImageRecent.image = UIImage(data:imageData)
}}})
} else {
}}
}})
return cell
}
I only want the cell to return if count != 0. Is there any way to go about doing this? I am completely lost.
At the point at which this method is called, you've already told the collection view that there's a cell that exists at that index path. Now that you've done so, you have to supply a cell; there's no way not to – the method signature is non-optional.
If you only want a cell to appear under certain circumstances, then you have to handle it earlier in the process. When collectionView(_:numberOfItemsInSection:) is called, you need to determine if the cell should be shown and return the correct number of items. Then you'll only be asked for those cells.

How do I fix this error"Could not cast value of type 'UIImageView' () to 'PFImageView' " to display images on query?

I'm trying to retrieve users profile pictures(File) from Parse onto my PFQueryTableViewController. The code I've written doesn't give me errors but every time I run my app crashes and says "Could not cast value of type UIImageView to PFImageView. I'm new to coding and don't know what that means. Is there something I have to fix in my code? I just want to retrieve the users image to display on my query.
Here is my code below for my FeedViewController:
override func tableView(tableView: UITableView?, cellForRowAtIndexPath indexPath: NSIndexPath?, object: PFObject!) -> PFTableViewCell? {
let cell = tableView!.dequeueReusableCellWithIdentifier("BubbleCell", forIndexPath: indexPath!) as! Bubbles
if let userPost : PFObject = self.posts.objectAtIndex(indexPath!.row) as! PFObject {
/ if let pic = object["photo"] as? PFFile {
// Make sure your imageView is a PFImageView
let imageView = cell.userImage as! PFImageView
// I assume this is the placeholder image while your load your image files from Parse
imageView.image = UIImage(named: "image.png")
imageView.file = pic
// Load the image file however you see fit
imageView.loadInBackground(nil)
}
Here is my code for my PostViewController:
#IBAction func postPressed(sender: AnyObject) {
let testObj = PFObject(className: "Test")
testObj["photo"] = PFUser.currentUser()?.valueForKey("photo") as! PFFile
testObj.saveInBackgroundWithBlock { (success:Bool, error :NSError?) -> Void in
if error == nil
{
print("***///detail is saved///***")
self.dismissViewControllerAnimated(true, completion: nil)
}
else {
self.alert()
}
}

Parse get data with block not assigning value to variable in scope

Having issues to assign uiImageFile = image!. If It try to assign self.myImage = image! where myImage is a global variable it works.
Is it something possible to be done?
The code is retrieving images ok, also the cell will take an image if pointed directly. Just this bridge that is not working. And it only do not work for the image.
Also the following test line println("TESTSTRING\(indexPath.row)") just above the return is being able to get and print value from testString = "\(indexPath.row)" that is inside getDataInBackgroundWithBlock.
Sorry about the question title. Not sure how to resume the issue in a single sentence.
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cellIdentifier = "Cell"
let cell = tableView.dequeueReusableCellWithIdentifier(cellIdentifier, forIndexPath: indexPath) as! HobbieFeedTableViewCell
let object: PFObject = self.timelineData.objectAtIndex(indexPath.row) as! PFObject
var myText = object.objectForKey("postText") as? String
let userImageFile = object.objectForKey("postImage") as? PFFile
var uiImageFile = UIImage()
var testString = String()
println(userImageFile, indexPath.row)
if userImageFile != nil {
userImageFile?.getDataInBackgroundWithBlock({ (imageData:NSData?, error:NSError?) -> Void in
if error == nil {
if let myImageData = imageData {
let image = UIImage(data:myImageData)
self.myImage = image!
uiImageFile = image!
testString = "\(indexPath.row)"
}
}
}, progressBlock: { (percent: Int32) -> Void in
})
}
cell.cellTitle.text = myText
cell.cellImage.image = uiImageFile
// cell.cellImage.image = myImage
println("TESTSTRING\(indexPath.row)")
return cell
}
cell.cellImage.image = uiImageFile
gets executed before uiImageFile has been retrieved. This is because the
getDataInBackgroundWithBlock
returns right away before the retrieval is done.
You can fix it by: (1) Retrieve all images into a local array in the viewDidLoad function (2) Use push notification on completion on retrieval to trigger a tableview.reload