Parse getDataInBackgroundWithBlock not fetching in order - ios

I am fetching a string, NSDate and a PFFile from my Parse class to populate the collection view cells
All the cells load with image, date, info correctly. The info and date are ordered correctly (by ascending date). But every now and then when I build some of the images will be in a different cell. Im really scratching my head with this. Im guessing it has something to do with how I'm calling mixPhoto.getDataInBackgroundWithBlock({
I did try and use dispatch_async(dispatch_get_main_queue())
Still no luck... Heres my code, anyone got any ideas?
#IBOutlet weak var collectionView1: UICollectionView!
var mixPhotoArray : Array<UIImage> = []
var mixInfoArray: Array <String> = []
var mixDateArray: Array <NSDate> = []
override func viewDidLoad() {
super.viewDidLoad()
collectionView1.delegate = self;
collectionView1.dataSource = self;
self.queryParseMethod()
self.getImageData()
// Uncomment the following line to preserve selection between presentations
// self.clearsSelectionOnViewWillAppear = false
// Do any additional setup after loading the view.
}
func getImageData() {
var query = PFQuery(className: "musicMixes")
query.orderByAscending("date")
query.findObjectsInBackgroundWithBlock { (objects: [AnyObject]!, error: NSError!) -> Void in
for object in objects {
let mixPhoto = object["mixPhoto"] as PFFile
mixPhoto.getDataInBackgroundWithBlock({
(imageData: NSData!, error: NSError!) -> Void in
if (error == nil) {
dispatch_async(dispatch_get_main_queue()) {
let image = UIImage(data:imageData)
//image object implementation
self.mixPhotoArray.append(image!)
println(self.mixPhotoArray[0])
self.collectionView1.reloadData()
}
}
else {
println("error!!")
}
})//getDataInBackgroundWithBlock - end
}
}//for - end
}
func queryParseMethod() {
var query = PFQuery(className: "musicMixes")
query.orderByAscending("date")
query.findObjectsInBackgroundWithBlock { (objects: [AnyObject]!, error: NSError!) -> Void in
if error == nil {
for object in objects {
let mixPhoto = object["mixPhoto"] as PFFile
let mixInfo = object["info"] as String
let dateForText = object["date"] as NSDate
//self.collectionView1.reloadData()
self.mixDateArray.append(dateForText)
self.mixInfoArray.append(mixInfo)
self.collectionView1.reloadData()
}//for - end
}
}
} // end of queryParseMethod
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
/*
// MARK: - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
// Get the new view controller using [segue destinationViewController].
// Pass the selected object to the new view controller.
}
*/
// MARK: UICollectionViewDataSource
func numberOfSectionsInCollectionView(collectionView: UICollectionView) -> Int {
//#warning Incomplete method implementation -- Return the number of sections
return 1
}
func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
//#warning Incomplete method implementation -- Return the number of items in the section
println("I have \(mixPhotoArray.count) Images")
return mixInfoArray.count
}
//func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell:StreamCollectionViewCell = collectionView1.dequeueReusableCellWithReuseIdentifier(reuseIdentifier, forIndexPath: indexPath) as StreamCollectionViewCell
cell.mixImage.image = mixPhotoArray[indexPath.item]
cell.infoLabel.text = mixInfoArray[indexPath.item]
// NSDate array into cell
var dateFormatter = NSDateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd"
cell.mixDateLabel.text = dateFormatter.stringFromDate(mixDateArray[indexPath.item])
return cell
}

Like Wain said, I believe the main issue is that since your images are downloading at different speeds, they're not necessarily being appended to your array in order. Instead of recommending that you use a dictionary though, here's what I would recommend to circumvent that problem while still using an array:
// Declare your mixPhotoArray such that it can store optionals
var mixPhotoArray : Array<UIImage?> = []
func getImageData() {
var query = PFQuery(className: "musicMixes")
query.orderByAscending("date")
query.findObjectsInBackgroundWithBlock { (objects: [AnyObject]!, error: NSError!) -> Void in
// Initialize your array to contain all nil objects as
// placeholders for your images
self.mixPhotoArray = [UIImage?](count: objects.count, repeatedValue: nil)
for i in 0...objects.count - 1 {
let object: AnyObject = objects[i]
let mixPhoto = object["mixPhoto"] as PFFile
mixPhoto.getDataInBackgroundWithBlock({
(imageData: NSData!, error: NSError!) -> Void in
if (error == nil) {
dispatch_async(dispatch_get_main_queue()) {
let image = UIImage(data:imageData)
// Replace the image with its nil placeholder
// and do so using the loop's current index
self.mixPhotoArray[i] = image
println(self.mixPhotoArray[i])
self.collectionView1.reloadData()
}
}
else {
println("error!!")
}
})
}
}
}
Then within collectionView:cellForItemAtIndexPath, you can set the image conditionally so that it only appears once its ready:
if mixPhotoArray[indexPath.item] != nil {
cell.mixImage.image = mixPhotoArray[indexPath.item]
}

You are storing your data in 2 arrays, mixPhotoArray and mixInfoArray, but you can't guarantee that they will both be in the same order. Images are different sizes so they will download at different speeds. You also shouldn't really be trying to download more than 4 at once so your current scheme isn't great.
Instead, you should have an array of dictionaries or custom classes which hold all of the details and which, when each image is downloaded, is updated with that image.
Obviously this means that you need to know which one is associated with the image you've just downloaded so you need to capture this dictionary / instance in the block so you can update it.
You could do it in 2 arrays as you are, so long as you capture an index where the image should be an insert the image to the array in the correct place.

Related

Comments not Displayed in tableView

In the app I'm working on, it allows users to post on the main timeline and other users can comment on that user's post, so I've been trying to display the comments in the tableView, but it's not showing. I have already confirmed that the data is being posted to parse, so on that end it's working as expected, but when it comes to display the comments, I cannot seem to get it to work. I'm using this function to display the comments:
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var cell = tableView.dequeueReusableCellWithIdentifier("commentCell", forIndexPath: indexPath) as! CommentTableViewCell
cell.commentLabel?.text = comments![indexPath.row]
return cell
}
is anything wrong with my code? or is there another way to display the comments?
where is the code to retrieve the comments? make sure you are calling "self.tableView.reloadData()" after the for loop.
the way I generally retrieve information from parse is like so:
func query() {
var query = PFQuery(className: "comments")
query.orderByDescending("createdAt")
query.findObjectsInBackgroundWithBlock { (caption2: [AnyObject]?, erreur: NSError?) -> Void in
if erreur == nil {
// good
for caption in caption2! {
self.comments.append(caption["<YOUR COLUMN NAME WHERE COMMENT IS STORED IN PARSE HERE>"] as! String)
}
self.tableView.reloadData()
}
else {
// not good
}
}
}
Add this function to your class. Then change this:
func reply() {
post?.addObject(commentView!.text, forKey: "comments")
post?.saveInBackground()
if let tmpText = commentView?.text {
comments?.append(tmpText)
}
commentView?.text = ""
println(comments?.count)
self.commentView?.resignFirstResponder()
self.commentTableView.reloadData()
}
to this:
func reply() {
post?.addObject(commentView!.text, forKey: "comments")
post?.saveInBackground()
if let tmpText = commentView?.text {
comments?.append(tmpText)
}
commentView?.text = ""
println(comments?.count)
self.commentView?.resignFirstResponder()
self.query
}
It turned out that I was missing:
UITableViewDataSource
in my class, so this fixed it:
class DetailViewContoller: UIViewController, UITableViewDelegate, UITableViewDataSource, UITextViewDelegate {
...
...
...
override func viewDidLoad() {
super.viewDidLoad()
commentTableView.delegate = self
commentTableView.dataSource = self

Retrive all objects from Parse and then print in a Custom TableView?

I tried everything and I cant solve this, maybe im too tired to see, idk.
What i want is to retrive all objects from parse and list them in a tableview. So each row in the tableview must represent a row in the Parse Class. Objective: Show all the restaurants available.
Right now i can get all the objects from the Parse Class, but shows the same title on all table rows.
Here is the output (as you can see, always show the same name: "Renato" because its the last one that is retrived)
My code:
import UIKit
import Parse
class ListaTableViewController: UITableViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Uncomment the following line to preserve selection between presentations
// self.clearsSelectionOnViewWillAppear = false
// Uncomment the following line to display an Edit button in the navigation bar for this view controller.
// self.navigationItem.rightBarButtonItem = self.editButtonItem()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
// MARK: - Table view data source
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
// #warning Potentially incomplete method implementation.
// Return the number of sections.
return 3
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// #warning Incomplete method implementation.
// Return the number of rows in the section.
return 1
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> ListaTableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath) as! ListaTableViewCell
var query = PFQuery(className:"Restaurantes")
query.findObjectsInBackgroundWithBlock {
(objects: [AnyObject]?, error: NSError?) -> Void in
if error == nil {
// The find succeeded.
println("Successfully retrieved \(objects!.count) Restaurantes.")
// Do something with the found objects
if let objects = objects as? [PFObject] {
for object in objects {
cell.textCell.text = object["nome"] as? String
println(object.objectId)
}
}
} else {
// Log details of the failure
println("Error: \(error!) \(error!.userInfo!)")
}
}
//cell.textCell.text = "Hello world"
cell.imageBg.image = UIImage(named: "www.maisturismo.jpg")
return cell
}
}
Println Output
You are currently iterating through the whole objects array which will show always the last
for object in objects {
cell.textCell.text = object["nome"] as? String
}
You need to do it like this
if let objects = objects as? [PFObject] {
cell.textCell.text = objects[indexPath.row]["nome"] as? String
}
Also you should take another "way" of using the UITableViewController Subclass... Take a look, I quickly wired you up some code to see how you should do it...
https://gist.github.com/DennisWeidmann/740cbed1856da856926e

Retrieve Profile Image

I am trying to retrieve user profile image from parse. I have a collection view and I am retrieving all images people posted. I want to show each users profile image in the cell as well. I was using the below code
override func viewDidLoad() {
super.viewDidLoad()
let query = PFQuery(className: "Posts")
query.includeKey("pointName")
query.findObjectsInBackgroundWithBlock{(question:[AnyObject]?,error:NSError?) -> Void in
if error == nil
{
if let allQuestion = question as? [PFObject]
{
self.votes = allQuestion
self.collectionView.reloadData()
}
}
}
// Wire up search bar delegate so that we can react to button selections
// Resize size of collection view items in grid so that we achieve 3 boxes across
loadCollectionViewData()
}
/*
==========================================================================================
Ensure data within the collection view is updated when ever it is displayed
==========================================================================================
*/
// Load data into the collectionView when the view appears
override func viewDidAppear(animated: Bool) {
loadCollectionViewData()
}
/*
==========================================================================================
Fetch data from the Parse platform
==========================================================================================
*/
func loadCollectionViewData() {
// Build a parse query object
}
/*
==========================================================================================
UICollectionView protocol required methods
==========================================================================================
*/
func numberOfSectionsInCollectionView(collectionView: UICollectionView) -> Int {
return 1
}
func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return self.votes.count
}
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier("newview", forIndexPath: indexPath) as! NewCollectionViewCell
let item = self.votes[indexPath.row]
// Display "initial" flag image
var initialThumbnail = UIImage(named: "question")
cell.postsImageView.image = initialThumbnail
if let pointer = item["uploader"] as? PFObject {
cell.userName!.text = item["username"] as? String
print("username")
}
if let profile = item["uploader"] as? PFObject,
profileImageFile = profile["profilePicture"] as? PFFile {
cell.profileImageView.file = profileImageFile
cell.profileImageView.loadInBackground { image, error in
if error == nil {
cell.profileImageView.image = image
}
}
}
if let votesValue = item["votes"] as? Int
{
cell.votesLabel?.text = "\(votesValue)"
}
// Fetch final flag image - if it exists
if let value = item["imageFile"] as? PFFile {
println("Value \(value)")
cell.postsImageView.file = value
cell.postsImageView.loadInBackground({ (image: UIImage?, error: NSError?) -> Void in
if error != nil {
cell.postsImageView.image = image
}
})
}
return cell
}
However I found out that it sets profile image to the current user and not the user who posted the image. How can I do this? Thank you
UPDATE
so In parse my post class is
so I know who uploaded it but I don't know how to retrieve the profile image for this specific user.
You need to use a pointer that will point to the user who created the object. The profile photo should be in the user class. You then include the pointer in your query and that will return the user data.
okay, this might help you in objective-C
PFUser *user = PFUser *user = [PFUser currentUser];
[user fetchIfNeededInBackgroundWithBlock:^(PFObject *object, NSError *error) {
_profileImage.file = [object objectForKey:#"profilePicture"];
}];

Parse, iOS, includeKey query does not retrieve attribute of pointer object

I'm quite new to working with Parse and I'm building a todo list as part of a CRM. Each task in the table view shows the description, due date, and client name. The description and due date are in my Task class, as well as a pointer to the Deal class. Client is a string in the Deal class. I'm able to query the description and due date properly, but I am not able to retrieve the client attribute from within the Deal object by using includeKey. I followed the Parse documentation for includeKey.
The description and due date show up properly in the resulting table view, but not the client. The log shows client label: nil and the printed task details include <Deal: 0x7ff033d1ed40, objectId: HffKOiJrTq>, but nothing about the client attribute. How can I retrieve and assign the pointer object's attribute (client) to my label within the table view? My relevant code is below. Thank you in advance.
Edit: I've updated my code with func fetchClients() based on this SO answer, but I'm still not sure whether my function is complete or where to call it.
class TasksVC: UITableViewController {
var taskObjects:NSMutableArray! = NSMutableArray()
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
println("\(PFUser.currentUser())")
self.fetchAllObjects()
self.fetchClients()
}
func fetchAllObjects() {
var query:PFQuery = PFQuery(className: "Task")
query.whereKey("username", equalTo: PFUser.currentUser()!)
query.orderByAscending("dueDate")
query.addAscendingOrder("desc")
query.includeKey("deal")
query.findObjectsInBackgroundWithBlock { (tasks: [AnyObject]!, error:NSError!) -> Void in
if (error == nil) {
var temp:NSArray = tasks! as NSArray
self.taskObjects = temp.mutableCopy() as NSMutableArray
println(tasks)
self.tableView.reloadData()
} else {
println(error?.userInfo)
}
}
}
func fetchClients() {
var task:PFObject = PFObject(className: "Task")
var deal:PFObject = task["deal"] as PFObject
deal.fetchIfNeededInBackgroundWithBlock {
(deal: PFObject!, error: NSError!) -> Void in
let client = deal["client"] as NSString
}
}
//MARK: - Tasks table view
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.taskObjects.count
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = self.tableView.dequeueReusableCellWithIdentifier("TaskCell", forIndexPath: indexPath) as TaskCell
var dateFormatter:NSDateFormatter = NSDateFormatter()
dateFormatter.dateFormat = "M/dd/yy"
var task:PFObject = self.taskObjects.objectAtIndex(indexPath.row) as PFObject
cell.desc_Lbl?.text = task["desc"] as? String
cell.date_Lbl.text = dateFormatter.stringFromDate(task["dueDate"] as NSDate)
cell.client_Lbl?.text = task["client"] as? String
var clientLabel = cell.client_Lbl?.text
println("client label: \(clientLabel)")
return cell
}
}
If the deal column is a pointer then includeKey("deal") will get that object and populate it's properties for you. There is no need to perform a fetch of any type on top of that.
You really should be using Optionals properly though:
if let deal = task["deal"] as? PFObject {
// deal column has data
if let client = deal["client"] as? String {
// client has data
cell.client_Lbl?.text = client
}
}
Alternatively you can replace the last if let with a line like this, which handles empty values and uses a default:
cell.client_Lbl?.text = (deal["client"] as? String) ?? ""
In your posted cellForRowAtIndexPath code you are trying to read client from the task instead of from the deal: task["client"] as? String.

Selected Images Not Displaying In Order

I am building a Vote/Instagram-type app, where a user selects a non-user-made photo which is pushed into a Timeline.
I have built out the selection screen and button, and the Timeline. For some reason, I can only guess that it has something to do with the chronology of saving the PFObject (the chose photo), the Timeline is displaying the photo chosen BEFORE the currently chosen photo.
The following block is the 'Select' button on the 'SelectScreenVC':
#IBAction func selectNext(sender: UIButton) {
let imageData = UIImagePNGRepresentation(lgImgURL.image)
let imageFile = PFFile(name: "\(cName.text!)", data: imageData)
var user = PFUser.currentUser()
user["nextChosen"] = "\(cName.text!)"
user["imageFile"] = imageFile
var userNextPhoto = PFObject(className: "UserNextPhoto")
userNextPhoto["username"] = PFUser.currentUser().username
userNextPhoto["cName"] = "\(cName.text!)" as String
userNextPhoto["imageFile"] = imageFile
userNextPhoto.saveInBackgroundWithBlock { (success: Bool!, error: NSError!) -> Void in
if error == nil {
println("User \(PFUser.currentUser().username) chose: \(self.cName.text!)")
} else {
println(error)
}
}
self.navigationController?.popToViewController(timeLineVC, animated: true)
}
This the TimeLineVC. I have the function which creates the timeline array and I skipped some of the TableView methods, except for the cellForRowAtIndexPath.
class TimeLineViewController: UITableViewController {
var timelineData:NSMutableArray = NSMutableArray()
override func viewDidLoad() {
super.viewDidLoad()
loadData()
}
func loadData() {
timelineData.removeAllObjects()
var findTimelineData:PFQuery = PFQuery(className: "UserNextPhoto")
findTimelineData.findObjectsInBackgroundWithBlock { (objects: [AnyObject]!, error: NSError!) -> Void in
if error == nil {
for object in objects {
self.timelineData.addObject(object)
}
} else {
NSLog("Error: %# %#", error, error.userInfo!)
}
let array: NSArray = self.timelineData.reverseObjectEnumerator().allObjects
self.timelineData = NSMutableArray(array: array)
self.tableView.reloadData()
}
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("timelineCell", forIndexPath: indexPath) as TimelineTableViewCell
let userNextPhoto:PFObject = self.timelineData.objectAtIndex(indexPath.row) as PFObject
let nextPhoto:PFObject = self.timelineData.objectAtIndex(indexPath.row) as PFObject
cell.usernameLabel.text = PFUser.currentUser().username
cell.cName.text = (userNextPhoto["cName"] as String)
return cell
}
I appreciate any help or insight!
That is what is going on: the new photo has not finished uploading when you pop the view controller. You can either wait for the photo to finish uploading (which is not a great UX), or you can pass back a reference to the newly-saved object back up to the popped view controller which can then display the new photo right away without waiting for the upload to finish.

Resources