swift PFQueryTableViewController - remove duplicate objects before presenting cells - ios

I have two Classes in Parse, one contains details of images that users have liked ("Liked"), the second class lists users that are following other users ("Follows").
In my PFQueryViewController I am able to create a query in queryForTable() that does the following:
//get username of users being followed
let query:PFQuery = PFQuery(className:"Follows")
query.whereKey("fromUser", equalTo: (PFUser.currentUser()?.username)!)
//get images of all followed users
let imageQuery:PFQuery = PFQuery(className: "Liked")
imageQuery.whereKey("username", matchesKey: "toUser", inQuery: query)
//get images current user liked
let userImagesQuery:PFQuery = PFQuery(className: "Liked")
userImagesQuery.whereKey("username", equalTo: (PFUser.currentUser()?.username)!)
//combine liked images of following and current user
let combinedQuery:PFQuery = PFQuery.orQueryWithSubqueries([imageQuery,userImagesQuery])
if(objects?.count == 0)
{
combinedQuery.cachePolicy = PFCachePolicy.CacheThenNetwork
}
combinedQuery.orderByDescending("createdAt")
return combinedQuery
This works where I am able to display the liked images of the user, and the users being followed, in cellForRowAtIndexPath by querying the imageID of the object for each cell. However, multiple users can like the same image, so if a user and someone they are following like the same image, the image will appear twice in the table.
Is there a way to force the query to ignore objects when it finds a duplicate based on the ImageID key?
I am aware this can be done by executing the query and iterating through the objects, however this wont work in this scenario as I need the duplicates to be removed before the query is returned in queryForTable().

I managed to solve this by using objectsDidLoad(). This captures the objects before they are sent to the table, therefore I was able to iterate through each object and delete any duplicates.
I store all the loaded objects into a new array and remove an object if a duplicate is found. I then make the object in cellForRowAtIndexPath the corresponding object in the array.
override func objectsDidLoad(error: NSError?) {
super.objectsDidLoad(error)
displayedImages.removeAll()
imageObjects = NSMutableArray(array: objects!)
for item in imageObjects {
if displayedImages.count == 0 {
let image = item["imageid"] as! NSNumber
displayedImages.insert(image, atIndex: 0)
}else{
var count = 0
let image = item["imageid"] as! NSNumber
for imageToCheck in displayedImages {
if imageToCheck != image {
count++
}
}
if count != displayedImages.count {
imageObjects.removeObject(item)
}else{
displayedImages.insert(image, atIndex: 0)
}
}
}
self.tableView.reloadData()
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of rows
return imageObjects.count
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath, object: PFObject?) -> PFTableViewCell? {
let cell = tableView.dequeueReusableCellWithIdentifier("Cell") as! FeedTableViewCell
if let pfObject:PFObject = self.imageObjects.objectAtIndex(indexPath.row) as? PFObject {
//display images as before

Related

Tableview Like Snapchat Send Feature (Multiple Selection and Swipeable Label)

In my Xcode Project I will like to have a similar view like Snapchat's "Send To..." screen (I have attached a screenshot). I have already made a tableview and populate it and have allowed multiple selection on. I am currently having trouble with two things:
1) Multiple Selection: I can select an cell I want, but when I tap on the search bar and start typing, all my previous selections go away. I am assuming that I need to add all of the names in a array and somehow communicate the array with the table so it shows if this username is in the array then make it selected in the tableview. But I am not sure how to do that. How can I do this?
2) Sending to Bottom Bar (blue in photo): As you may know, in Snapchat as you press on which users you want to send the snap to, their names get added to the bar at the bottom, as you fill up the bar, it because swipe able where you can horizontally scroll through the names you have added. I can append the names to an array and show the array in a label like theirs, but I do not know how to make it so a user can horizontally scroll through it.How do I implement this same feature?
Feel free to answer ANY of the questions! You do not need to do all of them, I just need them answered. Here's my code so far:
class User {
var userID:String?
var userFullName:String?
var userUsername:String?
var userProfileImage:PFFile?
var isPrivate:Bool
init(userID : String, userFullName : String, userUserName : String, userProfileImage : PFFile, isPrivate : Bool) {
self.userID = userID
self.userFullName = userFullName
self.userUsername = userUserName
self.userProfileImage = userProfileImage
self.isPrivate = isPrivate
}
}
var userArray = [User]()
func loadFriends() {
//STEP 1: Find friends
let friendsQuery = PFQuery(className: "Friends") //choosing class
friendsQuery.whereKey("friendOne", equalTo: PFUser.current()?.objectId ?? String()) //finding friends
friendsQuery.limit = self.page //number of users intitally showing
friendsQuery.findObjectsInBackground (block: { (objects, error) -> Void in
if error == nil { //if no error
//clean up
self.friendsArray.removeAll(keepingCapacity: false)
//STEP 2: Find related objects depending on query setting
for object in objects! {
self.friendsArray.append(object.value(forKey: "friendTwo") as! String) //hold array info of friend
}
//STEP 3: Find friend info
let query = PFUser.query()
query?.whereKey("objectId", containedIn: self.friendsArray)
query?.addDescendingOrder("createdAt") //how to order users
query?.findObjectsInBackground(block: { (objects, error) -> Void in
if error == nil {
for object in objects! {
var user : User
let fullname = (object.value(forKey: "fullname") as! String)
let username = (object.object(forKey: "username") as! String)
let profilePhoto = (object.object(forKey: "profilePhoto") as! PFFile)
let objectID = (object.objectId!)
let isPrivate = (object.object(forKey: "isPrivate") as! Bool)
user = User(userID: objectID, userFullName: fullname, userUserName: username, userProfileImage: profilePhoto, isPrivate: isPrivate)
self.userArray.append(user)
}
self.tableView.reloadData()
} else {
print(error!)
}
})
} else {
print(error!)
}
})
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell") as! FriendCell
let user = userArray[indexPath.row]
//add user info to cells
cell.fullnameLabel.text = user.userFullName
cell.usernameLabel.text = user.userUsername
cell.objectID = user.userID!
cell.isPrivate = user.isPrivate
user.userProfileImage?.getDataInBackground (block: { (data, error) in
if error == nil {
cell.profilePhoto.image = UIImage(data: data!)
}
})
})
}
1) Multiple Selection:
You should have a User class (e.g User) that holds user properties instead of maintaining array for each property. Store User object in a Array. User class could be like below:
class User {
var userID:String
var userFullName:String
var userName:String
var userProfileImageUrl:String
init(userID:String,userFullName:String,userName:String,userProfileImageUrl:String) {
self.userID = userID
self.userFullName = userFullName
self.userName = userName
self.userProfileImageUrl = userProfileImageUrl
}
}
You could have a User extension to check if that user is selected or not(e.g isSelected).
import UIKit
import Foundation
private var selectedKey: UInt8 = 0
extension User {
var isSelected:Bool{
get {
return objc_getAssociatedObject(self, &selectedKey) as! Bool
}
set {
objc_setAssociatedObject(self, &selectedKey, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
}
}
}
Now in your func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell check that user.isSelected == true/false and update your selected/deselected image accordingly.
And update the value of isSelected in func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath)
2) Sending to Bottom Bar:
For bottom bar add a UICollectionView as a subview in UIView. Create a class overriding UICollectionViewCell that holds a UILabel. You can add flow layout in UICollectionView.
I have given just an idea to start with.Hope it will help you.
I think, you set bool check for every cell in tableView. If cell load again, it will not show check. Because, It check is false.

Add Item to Specific Table View Row if Available

I am trying to create a tableView of users from my Parse database that are in the same class (at school). All users have to have a username, but not all will have given the app their full name or set a profile picture. I use this code:
let studentsQuery = PFQuery(className:"_User")
studentsQuery.whereKey("objectId", containedIn: studentsArray! as! [AnyObject])
let query2 = PFQuery.orQueryWithSubqueries([studentsQuery])
query2.findObjectsInBackgroundWithBlock {
(results: [PFObject]?, error: NSError?) -> Void in
if error != nil {
// Display error in tableview
} else if results! == [] {
spinningActivity.hideAnimated(true)
print("error")
} else if results! != [] {
if let objects = results {
for object in objects {
if object.objectForKey("full_name") != nil {
let studentName = object.objectForKey("full_name")! as! String
self.studentNameResults.append(studentName)
}
if object.objectForKey("username") != nil {
let studentUsername = object.objectForKey("username")! as! String
self.studentUsernameResults.append(studentUsername)
}
if object.objectForKey("profile_picture") != nil {
let studentProfilePictureFile = object.objectForKey("profile_picture") as! PFFile
studentProfilePictureFile.getDataInBackgroundWithBlock({ (image: NSData?, error: NSError?) in
if error == nil {
let studentProfilePicture : UIImage = UIImage(data: image!)!
self.studentProfilePictureResults.append(studentProfilePicture)
} else {
print("Can't get profile picture")
// Can't get profile picture
}
self.studentsTableView.reloadData()
})
spinningActivity.hideAnimated(true)
} else {
// no image
}
}
}
} else {
spinningActivity.hideAnimated(true)
print("error")
}
}
This code works fine if all of the users have a username, full_name, and a profile_picture. I can't figure out, however, how to get a tableView of the usernames of a user and add a user's name or picture to the user's corresponding tableViewCell only if the user has a picture. Here is how my tableView is configured:
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return studentUsernameResults.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("studentsCell", forIndexPath: indexPath) as! StudentsInClassInformationTableViewCell
cell.studentProfilePictureImageView.layer.cornerRadius = cell.studentProfilePictureImageView.frame.size.width / 2
cell.studentProfilePictureImageView.clipsToBounds = true
cell.studentProfilePictureImageView.image = studentProfilePictureResults[indexPath.row]
cell.studentUsernameLabel.text = studentUsernameResults[indexPath.row]
cell.studentNameLabel.text = studentNameResults[indexPath.row]
return cell
}
The studentProfilePictureResults, studentUsernameResults, and studentNameResults come from arrays of the user's picture, username, and name results pulled from Parse. If a user does not have a profile picture, I get the error, Index is out of range. Obviously, this means that there are, say, three names, three usernames, and only two pictures and Xcode doesn't know how to configure the cell. My question: How can I set a tableView up of a user's username and place their name and profile picture in the same cell, only if they have one?
Trying to store the different attributes in different arrays will be a problem, since as you have found, you end up with problems where a particular user doesn't have an attribute. You could use an array of optionals, so that you could store nil for an absent attribute, but it is much simpler to store the PFObject itself in a single array and accessing the attributes in cellForRowAtIndexPath rather than splitting out the attributes.
Since fetching the photo requires a separate, asynchronous, operation, you can store it separately. Rather than using an array to store the retrieved photos, which would have the same problem of ordering, you can use a dictionary, indexed by the user id; although for a large number of students it would probably be more efficient to use something like SDWebImage to download the photos as required in cellForRowAtIndexPath.
// these are instance properties defined at the top of your class
var students: [PFObject]?
var studentPhotos=[String:UIImage]()
// This is in your fetch function
let studentsQuery = PFUser.Query()
studentsQuery.whereKey("objectId", containedIn: studentsArray! as! [AnyObject])
let query2 = PFQuery.orQueryWithSubqueries([studentsQuery])
query2.findObjectsInBackgroundWithBlock {
(results: [PFObject]?, error: NSError?) -> Void in
guard (error == nil) else {
print(error)
spinningActivity.hideAnimated(true)
return
}
if let results = results {
self.students = results
for object in results {
if let studentProfilePictureFile = object.objectForKey("profile_picture") as? PFFile {
studentProfilePictureFile.getDataInBackgroundWithBlock({ (image: NSData?, error: NSError?) in
guard (error != nil) else {
print("Can't get profile picture: \(error)")
return
}
if let studentProfilePicture = UIImage(data: image!) {
self.studentPhotos[object["username"]!]=studentProfilePicture
}
}
}
spinningActivity.hideAnimated(true)
self.tableview.reloadData()
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if self.students != nil {
return self.students!.count
}
return 0
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("studentsCell", forIndexPath: indexPath) as! StudentsInClassInformationTableViewCell
cell.studentProfilePictureImageView.layer.cornerRadius = cell.studentProfilePictureImageView.frame.size.width / 2
cell.studentProfilePictureImageView.clipsToBounds = true
let student = self.students[indexPath.row]
if let studentPhoto = self.studentPhotos[student["username"]!] {
cell.studentProfilePictureImageView.image = studentProfilePictureResults[indexPath.row]
} else {
cell.studentProfilePictureImageView.image = nil
}
cell.studentUsernameLabel.text = student["username"]!
if let fullName = student["full_name"] {
cell.studentNameLabel.text = fullName
} else {
cell.studentNameLabel.text = ""
return cell
}
A few other pointers;
The use of _ to separate words in field names isn't really used in the iOS world; camelCase is preferred, so fullName rather than full_name
It looks like your Parse query could be more efficient if you had a class field or reference object so that you didn't need to supply an array of other class members.

Using PFQuery inside cellForRowAtIndexPath

I was thinking about PFQuery.
I'm developing an App that shows a Feed to the Users and it also displays a Like counter for each Post (like a Facebook App or Instagram App).
So in my PFQueryTableViewController I have my main query, that basically show all the Posts:
override func queryForTable() -> PFQuery {
let query = PFQuery(className: "Noticias")
query.orderByDescending("createdAt")
return query
}
And I use another query to count the number of Likes on another Class in Parse that contais all the Likes.
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath, object: PFObject?) -> PFTableViewCell? {
var cell = tableView.dequeueReusableCellWithIdentifier("FeedCellIdentifier") as! FeedCell!
if cell == nil {
cell = FeedCell(style: UITableViewCellStyle.Default, reuseIdentifier: "FeedCellIdentifier")
}
let query2 = PFQuery(className:"commentsTable")
query2.whereKey("newsColumn", equalTo: object!)
query2.findObjectsInBackgroundWithBlock {
(objectus: [PFObject]?, error: NSError?) -> Void in
if error == nil {
let quantidade = objectus!.count
let commentQuantidade = String(quantidade)
cell.comentariosLabel.text = commentQuantidade
} else {
// Log details of the failure
print("Error: \(error!) \(error!.userInfo)")
}
}
This way to code works, and I achieve what I want, but! I know that I'm reusing cells, I know that this block of code is called everytime a cell appear.
And I know those facts:
A lot of query requests is sent to Parse Cloud, everytime I scroll the tableview
It's possible to see the values changing, when I'm scrolling the tableview, for example, because I'm reusing the cells a post has a value of my previous cell and then with the new query it's refreshed, this works but not look good for user experience.
So, my main doubt is, is it the right way to code? I think not, and I just want another point of view or an idea.
Thanks.
EDIT 1
As I said I've updated my count method to countObjectsInBackgroundWithBlock instead of findObjectsInBackgroundWithBlock but I'm not able to move the query to the ViewDidLoad, because I use the object to check exactly how many comments each Post have.
EDIT 2
I've embed the query to count the number of comments for each post and printing the results, now I'm think my code is better than the previous version, but I'm not able to pass the result to a label because I'm receiving a error:
Use of unresolved identifier 'commentCount'
I'm reading some documentations about Struct
Follows my updated code bellow:
import UIKit
import Social
class Functions: PFQueryTableViewController, UISearchBarDelegate {
override func shouldAutorotate() -> Bool {
return false
}
var passaValor = Int()
let swiftColor = UIColor(red: 13, green: 153, blue: 252)
struct PostObject{
let post : PFObject
let commentCount : Int
}
var posts : [PostObject] = []
// Initialise the PFQueryTable tableview
override init(style: UITableViewStyle, className: String!) {
super.init(style: style, className: className)
}
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)!
// The className to query on
self.parseClassName = "Noticias"
// The key of the PFObject to display in the label of the default cell style
self.textKey = "text"
// Uncomment the following line to specify the key of a PFFile on the PFObject to display in the imageView of the default cell style
self.imageKey = "image"
// Whether the built-in pull-to-refresh is enabled
self.pullToRefreshEnabled = true
// Whether the built-in pagination is enabled
self.paginationEnabled = true
// The number of objects to show per page
self.objectsPerPage = 25
}
// Define the query that will provide the data for the table view
override func queryForTable() -> PFQuery {
let query = super.queryForTable()
return query
}
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(true)
loadObjects()
}
// In a storyboard-based application, you will often want to do a little preparation before navigation
override func viewDidLoad() {
super.viewDidLoad()
// navigationBarItems()
let query = PFQuery(className:"Noticias")
query.findObjectsInBackgroundWithBlock {
(objects: [PFObject]?, error: NSError?) -> Void in
// The find succeeded.
print("Successfully retrieved \(objects!.count) scores.")
// Do something with the found objects
if let objects = objects {
for object in objects {
let queryCount = PFQuery(className:"commentsTable")
queryCount.whereKey("newsColumn", equalTo: object)
queryCount.countObjectsInBackgroundWithBlock {
(contagem: Int32, error: NSError?) -> Void in
let post = PostObject(object, commentCount:commentCount)
posts.append(post)
print("Post \(object.objectId!) has \(contagem) comments")
}
self.tableView.reloadData()
}
}
}
//Self Sizing Cells
tableView.estimatedRowHeight = 350.0
tableView.rowHeight = UITableViewAutomaticDimension
}
// Define the query that will provide the data for the table view
//override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath, object: PFObject?) -> PFTableViewCell? {
var cell = tableView.dequeueReusableCellWithIdentifier("FeedCellIdentifier") as! FeedCell!
if cell == nil {
cell = FeedCell(style: UITableViewCellStyle.Default, reuseIdentifier: "FeedCellIdentifier")
}
cell?.parseObject = object
if let assuntoNoticia = object?["assunto"] as? String {
cell?.assuntoNoticia?.text = assuntoNoticia
}
if let pontos = object?["pontos"] as? Int {
let pontosPosts = String(pontos)
cell?.pontosLabel?.text = String(pontosPosts)
}
if let zonaLabel = object?["zona"] as? String {
cell?.zonaLabel?.text = zonaLabel
}
if let criticidade = object?["criticidade"] as? String {
if criticidade == "Problema"{
cell.criticidadeNoticia.backgroundColor = UIColor.redColor()
} else {
cell.criticidadeNoticia.backgroundColor = UIColor.greenColor()
}
}
return cell
}
}
And the result of print:
Successfully retrieved 5 scores.
Post wSCsTv8OnH has 4 comments
Post LbwBfjWPod has 0 comments
Post fN4ISVwqpz has 0 comments
Post 1rXdQr2A1F has 1 comments
Post eXogPeTfNu has 0 comments
Better practice would be to query all data on view load saving it into model and then read data from it on table view scroll. When processing query you can show downloading indicator or placeholder data. When query is complete you'll call tableView.reloadData()
You can accomplish this by creating a new variable like this:
var cellModels : [PFObject] = []
In your query2.findObjectsInBackgroundWithBlock:
for object in objectus{
self.cellModels.append(object)
}
self.tableView.reloadData()
And in cellForRowAtIndexPath:
let model = cellModels[indexPath.row]
// configure cell according to model
// something like cell.textLabel.text = model.text
P.S You should take a look at method countObjectsInBackgroundWithBlock if you only need to get count of objects. Because if there're a lot of e.g comments findObjectsInBackgroundWithBlock will return maximum of 1000 objects and still you won't be downloading whole objects, only one number this will speed up query and spare user's cellular plan.
Update: Also if you need to store numbers of comments you can create simple struct like this:
struct PostObject{
let post : PFObject
let commentCount : Int
}
var posts : [PostObject] = []
And when you query for you posts you loop through received objects and populate posts array.
for object in objects{
// create countObjectsInBackgroundWithBlock query to get comments count for object
// and in result block create
let post = PostObject(object, commentCount:commentCount)
posts.append(post)
}
tableView.reloadData()
And in cellForRowAtIndexPath:
let post = posts[indexPath.row]
cell.postCountLabel.text = String(post.commentCount)
// configure cell accordingly
You should do your queries before you present the information in your tableview.

Why are the same comments showing for different user posts? (iOS, Swift, Parse)

Working on a social iPhone app using Swift (with a Storyboard) and Parse where users can create posts and comment on posts similar to the Facebook iOS app and other social network apps.
The app has an initial, master Home Feed page (which displays user posts) and a detail Reply page (which is supposed to display the comments for a particular post that was selected but is showing the same replies for different posts). Both use the PFTableViewController class and each have their own PFTableViewCell implemented in separate swift files as the prototype cells.
When a user taps on ANY post cell in the Home Feed page, it navigates to the Reply page but shows all existing comments (as well as every new comment) for the post. I am trying to have only the comments for a specific post show when the user selects a particular post from the Home Feed page.
Any idea why this is happening? I greatly appreciate your time and help!
Home Feed page:
class HomeTableVC: PFQueryTableViewController,CLLocationManagerDelegate {
var posts: NSMutableArray! = NSMutableArray()
override func tableView(tableView: UITableView?, numberOfRowsInSection section: Int) -> Int {
return posts.count
}
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
self.performSegueWithIdentifier("showReplyViewController", sender: self)
}
override func tableView(tableView: UITableView?, cellForRowAtIndexPath indexPath: NSIndexPath?, object: PFObject!) -> PFTableViewCell? {
let cell = tableView!.dequeueReusableCellWithIdentifier("PostCell", forIndexPath: indexPath!) as! PostTableCell
if let userPost : PFObject = self.posts.objectAtIndex(indexPath!.row) as! PFObject {
cell.name.text = object["userName"] as? String
cell.message.text = object["postMessage"] as? String
let dateUpdated = object.createdAt! as NSDate
let dateFormat = NSDateFormatter()
dateFormat.dateFormat = "h:mm a"
cell.dateTime.text = NSString(format: "%#", dateFormat.stringFromDate(dateUpdated)) as String
cell.message.numberOfLines = 0
cell.message.text = userPost.objectForKey("postMessage") as? String
}
return cell
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if (segue.identifier == "showReplyViewController") {
let indexPath = self.tableView.indexPathForSelectedRow
let postObject = self.objects![indexPath!.row] as! PFObject
//postObject (on LHS) is the PFObject declared in ResponseViewController
if let destinationVC = segue.destinationViewController as? ReplyTableViewController {
destinationVC.postObject = postObject
}
}
}
}
Reply page:
class ReplyTableViewController: PFQueryTableViewController {
var postObject: PFObject?
var replies: NSMutableArray! = NSMutableArray()
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
replies = NSMutableArray()
var replyQuery = PFQuery(className: "Reply")
replyQuery.addAscendingOrder("createdAt")
replyQuery.findObjectsInBackgroundWithBlock {
(objects: [AnyObject]?, error: NSError?) -> Void in
if error == nil {
for object in objects! {
let reply: PFObject = object as! PFObject
self.replies.addObject(reply)
}
let repliesArray: NSArray = self.replies.reverseObjectEnumerator().allObjects
self.replies = NSMutableArray(array: repliesArray)
self.tableView.reloadData()
}
}
}
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
override func tableView(tableView: UITableView?, numberOfRowsInSection section: Int) -> Int {
return replies.count
}
override func tableView(tableView: UITableView?, cellForRowAtIndexPath indexPath: NSIndexPath?, object: PFObject!) -> PFTableViewCell? {
let cell = tableView!.dequeueReusableCellWithIdentifier("replyCell", forIndexPath: indexPath!) as! ReplyTableViewCell
let replyObject: PFObject = self.replies.objectAtIndex(indexPath!.row) as! PFObject
cell.replyMessageLabel.text = replyObject.objectForKey("replyMessage") as? String
var queryUser: PFQuery = PFUser.query()!
queryUser.whereKey("objectId", equalTo: (replyObject.objectForKey("replyUser")?.objectId)!)
queryUser.findObjectsInBackgroundWithBlock {
(objects: [AnyObject]?, error: NSError?) -> Void in
if error == nil {
let user: PFUser = (objects! as NSArray).lastObject as! PFUser
cell.replyAuthorLabel.text = user.username
}
}
return cell
}
}
In your segue you need to tell the destination ViewController which post to show replies for.
Add this to the bottom of your segue (exactly where your comment is):
if let destinationVC = segue.destinationViewController as? ReplyTableViewController{
destinationVC.postObject = postObject
}
And in ReplyTableViewController you need a postObject variable so that the code in the segue works. At the top of your ReplyTableViewController put:
var postObject = PFObject()
It looks like the postObject should be used somewhere in your PFQuery() to filter the replies, but I am not familiar with it.
I found a solution to my own problem!
I have updated the Reply page to use UITableViewController instead of PFTableViewController and updated the storyboard correspondingly (I made the necessary changes in the code and in the Storyboard to comply with the constraints of UITableViewController, etc).
I implemented a PFQuery with the appropriate constraints to fetch all the replies for a given post (only) by writing something similar to the following:
query.whereKey("parent", equalTo: aPost)
// Finds objects *asynchronously* and call the given block with the results.
query.findObjectsInBackgroundWithBlock {
(objects: [AnyObject]?, error: NSError?) -> Void in
// if there is no error, for each object in `objects`,
// assign the given object to a PFObject
// add the object to an array that will store all of the applicable replies for the post
// ...
}

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.

Resources