Comments not Displayed in tableView - ios

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

Related

Unexpectedly found nil while unwrapping an optional value when loading tableView

I've been on stack for a while now but never needed to ask a question as I've always found the answers after some searching, but now I'm stuck for real. I've been searching around and going through some trial and error for an answer and I keeping getting the same error. I'm basically making a profile page with a tableView on the bottom half of the screen. The top half is loading fine filling in the current user's information. All connections to the view controller and cell view controller seem good. The table view, however, will appear with no data and crash while loading with the fatal error:
unexpectedly found nil while unwrapping an optional value.
I also believe the cellForRowAtIndexPath is not being called at all because "test" is not printing to the logs.
I'm using the latest versions of Swift and Parse.
I'm relatively new to swift so I'll go ahead and post my entire code here and any help at all is appreciated.
import UIKit
import Parse
import ParseUI
class profileViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
#IBOutlet var tableView: UITableView!
#IBOutlet var profilePic: UIImageView!
#IBOutlet var userName: UILabel!
#IBOutlet var userBio: UILabel!
var image: PFFile!
var username = String()
var userbio = String()
var content = [String]()
#IBAction func logout(sender: AnyObject) {
PFUser.logOut()
let Login = storyboard?.instantiateViewControllerWithIdentifier("ViewController")
self.presentViewController(Login!, animated: true, completion: nil)
}
override func viewDidLoad() {
super.viewDidLoad()
profilePic.layer.borderWidth = 1
profilePic.layer.masksToBounds = false
profilePic.layer.borderColor = UIColor.blackColor().CGColor
profilePic.layer.cornerRadius = profilePic.frame.height/2
profilePic.clipsToBounds = true
tableView.delegate = self
tableView.dataSource = self
self.tableView.rowHeight = 80
self.hideKeyboardWhenTappedAround()
if let nameQuery = PFUser.currentUser()!["name"] as? String {
username = nameQuery
}
if PFUser.currentUser()!["bio"] != nil {
if let bioQuery = PFUser.currentUser()!["bio"] as? String {
userbio = bioQuery
}
}
if PFUser.currentUser()!["icon"] != nil {
if let iconQuery = PFUser.currentUser()!["icon"] as? PFFile {
image = iconQuery
}
}
self.userName.text = username
self.userBio.text = userbio
if image != nil {
self.image.getDataInBackgroundWithBlock { (data, error) -> Void in
if let downIcon = UIImage(data: data!) {
self.profilePic.image = downIcon
}
}
}
// Do any additional setup after loading the view.
var postsQuery = PFQuery(className: "Posts")
postsQuery.whereKey("username", equalTo: username)
postsQuery.findObjectsInBackgroundWithBlock( { (posts, error) -> Void in
if error == nil {
if let objects = posts {
self.content.removeAll(keepCapacity: true)
for object in objects {
if object["postText"] != nil {
self.content.append(object["postText"] as! String)
}
self.tableView.reloadData()
}
}
}
})
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
// #warning Potentially incomplete method implementation.
// Return the number of sections.
return 1
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// #warning Incomplete method implementation.
// Return the number of rows in the section.
print(content.count)
return content.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let profCell = self.tableView.dequeueReusableCellWithIdentifier("profCell", forIndexPath: indexPath) as! profTableViewCell
print("test")
profCell.userPic.layer.borderWidth = 1
profCell.userPic.layer.masksToBounds = false
profCell.userPic.layer.borderColor = UIColor.blackColor().CGColor
profCell.userPic.layer.cornerRadius = profCell.userPic.frame.height/2
profCell.userPic.clipsToBounds = true
profCell.userPic.image = self.profilePic.image
profCell.name.text = self.username
profCell.content.text = content[indexPath.row]
return profCell
}
}
I let it sit for a few days and I came back to realize a very dumb mistake I made. I working with around 15 view controllers right now and realized I had a duplicate of the one I posted above with the same name. I now understand why you say working with storyboards is very sticky. Though, I did not need it, I appreciate the help and I can say I learned a few things.
You probably need to register the class you are using for the custom UITableViewCell:
self.tableView.registerClass(profTableViewCell.self, forCellReuseIdentifier: "profCell")
Unless you're using prototyped cells in IB, this registration isn't done automatically for you.
As such when you call the dequeue method (with the ! forced unwrap) you're going to have issues. The dequeueReusableCellWithIdentifier:forIndexPath: asserts if you didn't register a class or nib for the identifier.
when you register a class, this always returns a cell.
The older (dequeueReusableCellWithIdentifier:) version returns nil in that case, and you can then create your own cell.
You should use a ? during the as cast to avoid the crash, although you'll get no cells!
One other reminder, you should always use capitals for a class name, ProfTableViewCell not profTableViewCell, it's just good pratice.
Much more information here in the top answer by iOS genius Rob Mayoff: Assertion failure in dequeueReusableCellWithIdentifier:forIndexPath:
You have to create a simple NSObject Class with image, username and userbio as optional values. Then you have to declare in your profileviewcontroller a var like this:
var allProfiles = [yourNSObjectClass]()
In your cellForRowAtIndexPath add:
let profile = yourNSObjectClass()
profile = allProfiles[indexPath.row]
cell.username.text = profile.username
And go on.
Use also this:
dispatch_async(dispatch_get_main_queue(), {
self.tableView.reloadData()
})
instead of this:
self.tableView.reloadData()

Selecting data to be loaded into table view Swift

I have a table view controller and above the cell is a segmented control. The segmented control has 3 options. Past Posts, Current Posts, and Future Posts. I am trying to figure out how to load the specific data into the table view depending on what index is selected on the segmented control.
For example if Past Posts is selected I want to load the Past Post data from Parse Server into the table view. Or of Future Posts is selected load the Future Posts date from Parse Server into the table view.
I am not at all sure how to load the "selected" data, then remove and load different data if the index changes. Any help is much appreciated!
Also, I know how to fetch data from Parse Server. I only mention that to explain where my data is coming from.
I would do something creating a controller that performs the fetch, the parsing, and returns a closure with the associated identifier if it ever changes, you can still use this approach. Something along these lines.
UPDATE
With help from Rob's answer I wanted to put a little context into my answer for completeness.
typealias PostsCompletionClosure = (requestIdentifier : String, posts : [Post])->Void
class PostController {
func fetchPastPosts(requestIdentifier : String,
completion : PostsCompletionClosure,
queue : dispatch_queue_t?) {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)) {
let queryParams = ["status" : "past"]
self.performQuery(queryParams, completion: { (requestID, posts) in
dispatch_async(queue != nil ? queue : dispatch_get_main_queue()) {
completion(requestIdentifier : requestIdentifier, posts : posts)
}
})
}
}
func fetchCurrentPosts(requestIdentifier : String,
completion : PostsCompletionClosure,
queue : dispatch_queue_t?) {
// Same as Above
}
func fetchFuturePosts(requestIdentifier : String,
completion : PostsCompletionClosure,
queue : dispatch_queue_t?) { {
// Same as Above
}
private func performQuery(queryParams: [String : String],
completion : PostsCompletionClosure) {
let query = PFQuery(className: "Posts")
for {key, value) in queryParams {
query.whereKey(key, equalTo: value)
}
query.findObjectsInBackgroundWithBlock { objects, error in
guard let error == nil else {
// Handle Error
return
}
if let results = objects as? [Post] {
dispatch_get_main_queue()) {
completion(requestIdentifier : requestIdentifier, posts : posts)
}
})
}
}
You can even create a post request queue for the segment requests, and cancel all prior operations if you are about to start a new one, thus never even giving it an opportunity to reload your data in the first place.
Here is a possible approach on how to implement the viewController :)
class ViewController: UIViewController, UITableViewDataSource {
#IBOutlet weak var tableView: UITableView!
#IBOutlet var segnmentControl: UISegmentedControl!
var posts: [Post]?
var activeRequestId: String = ""
// This should prolly be injected or a singleton
let postsController = PostController()
override func viewDidLoad() {
super.viewDidLoad()
didSelectSelegment(segnmentControl)
}
#IBAction func didSelectSelegment(sender: UISegmentedControl) {
posts = nil
tableView.reloadData()
activeRequestId = "\(sender.selectedSegmentIndex)"
switch sender.selectedSegmentIndex {
case 0:
self.postsController.fetchPastPosts(activeRequestId, completion: { (requestIdentifier, posts) in
self.reloadDataWith(requestIdentifier, posts : [Post])
})
case 1:
self.postsController.fetchCurrentPosts(activeRequestId, completion: { (requestIdentifier, posts) in
self.reloadDataWith(requestIdentifier, posts : [Post])
})
case 2:
self.postsController.fetchFuturePosts(activeRequestId, completion: { (requestIdentifier, posts) in
self.reloadDataWith(requestIdentifier, posts : [Post])
})
default:
fatalError("unexpected segment index")
}
}
func reloadDataWith(requestIdentifier : String,
posts : [Post]) {
if self.requestIdentifier == requestIdentifier {
self.posts = posts
self.tableView.reloadData()
}
}
}
The basic idea would be that as the segmented control changes, you would initiate a PFQuery that would populate your model, and then trigger the reloading of the table. For example, something like:
class ViewController: UIViewController, UITableViewDataSource {
#IBOutlet weak var tableView: UITableView!
var posts: [Post]?
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func didChangeValueForSegmentedControl(sender: UISegmentedControl) {
// first empty the table
posts = nil
tableView.reloadData()
// prepare new query
let query = PFQuery(className: "Posts")
switch sender.selectedSegmentIndex {
case 0:
query.whereKey("status", equalTo: "past")
case 1:
query.whereKey("status", equalTo: "current")
case 2:
query.whereKey("status", equalTo: "future")
default:
fatalError("unexpected segment index")
}
// now perform query
query.findObjectsInBackgroundWithBlock { objects, error in
guard error == nil else {
// report error
return
}
guard let searchResults = objects as? [Post] else {
// handle situation where results were not an array of `Post` objects
return
}
self.posts = searchResults
self.tableView.reloadData()
}
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return posts?.count ?? 0
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as! PostCell
let post = posts![indexPath.row]
// configure `cell` using `post`
return cell
}
}
Now, those whereKey clauses are certainly not right, and that would change depending how your object model was set up, but this illustrates the basic idea. Initiate PFQuery on the basis of which segmented control was selected and then update the results accordingly.
Now, this all makes a lot of assumptions (that you've defined your table view and specified the view controller as its data source; that you've hooked up the outlet for the table view; that you've hooked up the IBAction for valueChanged on the segmented control; that you've defined a cell prototype with a custom cell type; etc.), but it illustrates the key parts of the solution.

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