Adding a table row object to a PFRelation in Swift with Parse - ios

I'm adding a friendship relation for current user to the row which resembles another user in user table using table view controller.
I cannot find a way of setting the selected row as a variable, in this instance the object would be called user. The way I currently have throws up an error on the objectAtIndex part.
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
var cell:UITableViewCell = tableView.cellForRowAtIndexPath(indexPath)!
cell.accessoryType = UITableViewCellAccessoryType.Checkmark
var relation : PFRelation = PFUser.currentUser().relationForKey("friendship")
let user:PFObject = self.userArray.objectAtIndex(indexPath.row) as PFObject
relation.addObject(user)
PFUser.currentUser().saveInBackgroundWithBlock { (succeed:Bool, error: NSError!) -> Void in
if error != nil {
NSLog("Unable to save")
}
}
}
The error that is thrown underlines objectAtIndex and says, "String does not have a member named 'objectAtIndex'."
I've looked at numerous tutorials and read through lots of documentation and can't find a fix. Any help would be great.
Thanks,
Chris
userArray is defined here..
var userArray: [String] = []
override func viewDidLoad() {
super.viewDidLoad()
var query = PFUser.query()
query.whereKey("username", notEqualTo: PFUser.currentUser().username)
var users = query.findObjects()
for user in users {
userArray.append(user.username)
}
}

Related

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.

Parse getObjectInBackgroundWithId returns with no matches

I am trying to query my Parse "User" table using a objectId to get a specific row.
However I kept getting this error:
2016-01-08 22:03:50.476 ParseStarterProject-Swift[6278:3821859] [Error]: No results matched the query. (Code: 101, Version: 1.11.0)
Why is parse saying there is no results matched when the userObjectId var is clearly found in the objectId column of the Parse "User" table?
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
var cell = tableView.cellForRowAtIndexPath(indexPath)
//place checkmark
cell!.accessoryType = UITableViewCellAccessoryType.Checkmark
//getting selected user parse objectId
var userObjectId = userStructArray[indexPath.row].userId
print(userObjectId) //prints hvPpO9XXtY
var userQuery = PFQuery(className: "User")
userQuery.getObjectInBackgroundWithId(userObjectId) { (userObject, error) -> Void in
if error == nil && userObject != nil {
print(userObject)
} else {
//print(error)
}
}
}
Thanks for your help.
Change this line
var userQuery = PFQuery(className: "User")
To
var userQuery = PFQuery(className: "_User")

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

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.

How to reload a tableview only once at the end of a parse query?

Essentially, I have built a tableviewcontroller with a PFQuery to load information for each individual cell and store it in a array. There are two calls to parse, one to store usernames into an array, and another one to check if the current user is "following" them.
var query = PFUser.query()
query.findObjectsInBackgroundWithBlock {
(objects: [AnyObject]!, error: NSError!) -> Void in
self.users.removeAll(keepCapacity: true) // users is an array declared globally
for object in objects {
var user:PFUser = object as PFUser
var isFollowing:Bool
if user.username != PFUser.currentUser().username {
self.users.append(user.username)
isFollowing = false
var query = PFQuery(className:"followers")
query.whereKey("follower", equalTo:PFUser.currentUser().username)
query.whereKey("following", equalTo:user.username)
query.findObjectsInBackgroundWithBlock {
(objects: [AnyObject]!, error: NSError!) -> Void in
if error == nil {
for object in objects {
isFollowing = true
}
self.following.append(isFollowing)
self.tableView.reloadData()
} else {
// Log details of the failure
println(error)
}
}
}
}
}
If I call reloadData() at another place, the table will not display correctly. This makes my table loading inefficient..since the table reloads with every check for the "isfollowing" variable. How would I make it reload only once? After all the "isfollowing" variable have been appended? Can you please outline the solution step by step so I can get a general sense of direction and what to learn.
EDIT2:
In the code above I query to check which users the currentUser is following. If they are following them, I append "true" to the array "following". The code below is what I'm doing to checkmark the tableviewcells to show that they are following other users. With my original code, all the cells become checkmarked after scrolling up and down a couple of times, why?
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var cell:UITableViewCell = self.tableView.dequeueReusableCellWithIdentifier("cell") as UITableViewCell
if following.count > indexPath.row {
if following[indexPath.row] == true {
cell.accessoryType = UITableViewCellAccessoryType.Checkmark
}
}
cell.textLabel.text = users[indexPath.row]
return cell
}
EDIT: Wrong code posted originally.
Use findObjects to call synchronously and put it in our own queue may helps.
// queue is declared as an instance property
let queue = dispatch_queue_create(nil, DISPATCH_QUEUE_SERIAL)
var query = PFUser.query()
dispatch_async(queue) {
dispatch_async(dispatch_get_main_queue()) {
self.users.removeAll(keepCapacity: true) // users is an array declared globally
}
for user in query.findObjects() as [PFUser] {
var isFollowing = false
if user.username != PFUser.currentUser().username {
dispatch_async(dispatch_get_main_queue()) {
self.users.append(user.username)
}
var query = PFQuery(className:"followers")
query.whereKey("follower", equalTo:PFUser.currentUser().username)
query.whereKey("following", equalTo:user.username)
// call findObjects synchronously
if let objects = query.findObjects() {
if !objects.isEmpty {
isFollowing = true
}
dispatch_async(dispatch_get_main_queue()) {
self.following.append(isFollowing)
}
}
}
}
// notify table view to reload
dispatch_async(dispatch_get_main_queue()) {
self.tableView.reloadData()
}
}
You can create a UITableViewCell subclass and override its prepareForReuse to reset accessoryType.
class MyTableView: UITableViewCell {
override func prepareForReuse() {
accessoryType = .None
}
}
In your code, you didn't handle the case for following[indexPath.row] != true. If you don't want to subclass UITableViewCell, you can also reset the checkmark there.

Resources