I am working on an app that allows you to 'Like' posts. I was implementing the like button, but I got an error that I cannot seem to fix.
I searched in another posts, but I'm unsure of how to fix it.
This is the code I'm using to implement the like button. Do I need to import something into my project? Or unwrap at certain point?
Any help is appreciate it.
override func tableView(tableView: UITableView?, cellForRowAtIndexPath indexPath: NSIndexPath?) -> UITableViewCell {
let cell:PostTableViewCell = tableView!.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath!) as! PostTableViewCell
let post = self.objectAtIndexPath(indexPath!) as PFObject
cell.postTextView.alpha = 0
cell.usernameLabel.alpha = 0
cell.timestampLabel.alpha = 0
cell.postTextView.text = post.objectForKey("content") as! String
var dataFormatter:NSDateFormatter = NSDateFormatter()
dataFormatter.dateFormat = "yyyy-MM-dd HH:mm"
cell.timestampLabel.text = dataFormatter.stringFromDate(post.createdAt!)
// to get username from the post
var showUsername:PFQuery = PFUser.query()!
//the objectID is the same as the user in the two different tables
showUsername.whereKey("objectId", equalTo: post.objectForKey("user")!.objectId!!)
showUsername.findObjectsInBackgroundWithBlock{
(objects: [AnyObject]?, error: NSError?) -> Void in
if error == nil{
let user = (objects as! [PFUser]).last
cell.usernameLabel.text = user!.username
UIView.animateWithDuration(0.5, animations: {
cell.postTextView.alpha = 1
cell.usernameLabel.alpha = 1
cell.timestampLabel.alpha = 1
})
}
}
return cell
}
func objectAtIndexPath(indexPath: NSIndexPath) -> PFObject {
return self.timelineData[indexPath.row] as! PFObject
}
#IBAction func likeButton(sender: UIButton) {
//disables the like button so it can't be pressed again
sender.enabled = false
sender.userInteractionEnabled = false
sender.alpha = 0.5
//get the point in the table view that corresponds to the button that was pressed
//in my case these were a bunch of cells each with their own like button
let hitPoint = sender.convertPoint(CGPointZero, toView: self.tableView)
let hitIndex = self.tableView.indexPathForRowAtPoint(hitPoint)
let object = self.objectAtIndexPath(hitIndex!) as PFObject
//this is where I incremented the key for the object
object.incrementKey("likes")
object.saveInBackground() //still gives me error here
self.tableView.reloadData()
NSLog("Top Index Path \(hitIndex?.row)")
}
Update2: Added a photo of the error
Since the view controller is neither an NSFetchedResultsController or a PFQueryTableViewController, you'll have to implement objectAtIndexPath: yourself.
A hint about the code you need is in cellForRowAtIndexPath':
let post:PFObject = self.timelineData.objectAtIndex(indexPath!.row) as! PFObject
Dispensing with the objectAtIndex method on array, just index into the array at the row:
func objectAtIndexPath(indexPath: NSIndexPath) -> PFObject {
return self.timelineData[indexPath.row] as! PFObject
}
Call it wherever the old code appears like this (in likeButton)...
let object = self.objectAtIndexPath(hitIndex) as! PFObject
or, in cellForRowAtIndexPath:...
let post = self.objectAtIndexPath(indexPath) as!PFObject
etc.
Related
In my viewController, which is a PFQueryTableViewController,I am trying to create a like button feature for my posts within a TableViewCell that is enabled initially, but after the user likes the post we have a PFRelation(userLike) for that in Parse. When a user likes a post on the app, after reloading the app again, the user can like it again. I don't want the user to like it again. When reloading the app, I would like to have the like button disable itself after its pressed, while checking Parse for that Relation of the current user to see if they have liked the post already. Is there something I need to do in my code to disable the user from liking the same post again? I've pasted my code below to get a better idea
The commented statements are what I have tried to use in order to implement that feature.
#IBAction func likeButton(sender: UIButton) {
//let object: PFObject = self.objects?[sender.tag] as! PFObject
let hitPoint = sender.convertPoint(CGPointZero, toView: self.tableView)
let hitIndex = self.tableView.indexPathForRowAtPoint(hitPoint)
let object = objectAtIndexPath(hitIndex)
if sender.enabled == true{
disableButton(sender)
object!.incrementKey("count", byAmount: 1)
self.userLike?.addObject(object!)
globalLikeList.append(object!)
} else {
enableButton(sender)
object!.incrementKey("count", byAmount: -1)
self.userLike?.removeObject(object!)
if let index = globalLikeList.indexOf(object!) {
globalLikeList.removeAtIndex(index)
}
}
// let hitPoint = sender.convertPoint(CGPointZero, toView: self.tableView)
// let hitIndex = self.tableView.indexPathForRowAtPoint(hitPoint)
// let object = objectAtIndexPath(hitIndex)
// self.userLike?.addObject(object)
// tweet.addObject(object)
object!.saveInBackground()
self.tableView.reloadData()
PFUser.currentUser()?.saveInBackground()
NSLog("Top Index Path \(hitIndex?.row)")
}
We also initialized a global array of likes to store those "like" objects
var globalLikeList: [PFObject] = []
And also here is what we did in our tableView. Here is where I believe I've configured the like button:
override func tableView(tableView: UITableView?, cellForRowAtIndexPath indexPath: NSIndexPath?, object: PFObject!) -> PFTableViewCell? {
let cell = tableView!.dequeueReusableCellWithIdentifier("tweetCell", forIndexPath: indexPath!) as! tweet
if let tweet : PFObject = self.tweets.objectAtIndex(indexPath!.row) as! PFObject {
if let likeScore = object[("count")] as? Int {
cell.likeCount.text = "\(likeScore)"
}
if globalLikeList.contains(object!) {
cell.likeButton.selected = true
} else {
cell.likeButton.selected = false
}
cell.likeButton.tag = indexPath!.row
}
}
return cell
}
After you check to see if you've liked the post already, you have cell.likeButton.selected = true. I think you want cell.likeButton.enabled = false
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
// ...
}
I am trying to reload my table view using
self.tableView.reloadData()
It works properly if I'm loading static datasource using array. Everything work properly.
But when I try to use my query function with parse, it loads a new cell but the contents of the tableview cell doesn't change. If I re-open the app, the cells will update properly.
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cellIdentifier = "EmpPostTVCellIdentifier"
let cell: EmpPostTVCell? = tableView.dequeueReusableCellWithIdentifier(cellIdentifier, forIndexPath: indexPath) as? EmpPostTVCell
//If datasource
if dataSource.isEmpty{
fetchDataFromParse()
print("no posts")
}
let itemArr:PFObject = self.dataSource[indexPath.row]
cell?.companyPostLabel.text = (PFUser.currentUser()?.objectForKey("companyName")!.capitalizedString)! as String
cell?.occupationPostLabel.text = itemArr["occupation"]!.capitalizedString as String
cell?.countryPostLabel.text = itemArr["country"]!.capitalizedString as String
let companyImage: PFFile?
companyImage = PFUser.currentUser()?.objectForKey("profileImageEmployer") as? PFFile
companyImage?.getDataInBackgroundWithBlock({ (data, error) -> Void in
if error == nil{
cell?.companyLogoImage.image = UIImage(data: data!)
}
})
let dateArr = createdByDate[indexPath.row]
let strDate = Settings.dateFormatter(dateArr)
cell?.closingDateLabel .text = strDate
return cell!
}
I am using pull to refresh my tableviews contents using this code
func refresh(sender:AnyObject)
{
dispatch_async(dispatch_get_main_queue(), { () -> Void in
self.fetchDataFromParse()
self.tableView.reloadData()
self.refreshControl?.endRefreshing()
})
}
with or without the dispatch_asynch function the results remains the same. It just add new tableviewcell but the contents in it does not change. Any ideas guys?
edit 1 :
func fetchDataFromParse() {
// MARK: - JOB POST QUERY
if PFUser.currentUser()?.objectId == nil{
PFUser.currentUser()?.saveInBackgroundWithBlock({ (success, error) -> Void in
let query = PFQuery(className: "JobPost")
//creating a pointer
let userPointer = PFUser.objectWithoutDataWithObjectId(PFUser.currentUser()?.objectId)
query.whereKey("postedBy", equalTo: userPointer)
query.orderByDescending("createdAt")
let objects = query.findObjects()
for object in (objects as? [PFObject])!{
//print(object.objectId)
self.dataSource.append(object)
self.createdByDate.append((object.objectForKey("closingDate") as? NSDate)!)
print(self.dataSource)
print(self.createdByDate)
}
})
} else {
let query = PFQuery(className: "JobPost")
//creating a pointer
let userPointer = PFUser.objectWithoutDataWithObjectId(PFUser.currentUser()?.objectId)
query.whereKey("postedBy", equalTo: userPointer)
query.orderByDescending("createdAt")
let objects = query.findObjects()
for object in (objects as? [PFObject])!{
//print(object.objectId)
self.dataSource.append(object)
self.createdByDate.append((object.objectForKey("closingDate") as? NSDate)!)
print(self.dataSource)
print(self.createdByDate)
}
}//end of PFUser objectID == nil else clause
}
Let's see the content of the fetchDataFromParse() function where I presume you're filling the self.dataSource array
Try to call self.tableview.reloadData() when fetchDataFromParse() is finished.
Check whether your dataSource array is empty at the end of your fetchDataFromParse method
PFUser.currentUser()?.saveInBackgroundWithBlock is an asynchronus method. So your tableView cell is having no data.
I am having issues setting with Parse data and a UITableView. Everytime I run the application, whether on the simulator or my phone, the app stops working and the only thing the console shows is (lldb). When checking the debugger, only following two lines of code are highlighted.
findTimelineData.findObjectsInBackgroundWithBlock{
and
self.timelineData = array as NSMutableArray
Both have the error: Thread 1: EXC_BREAKPOINT (code=EXC_I386_BPT,subcode=0x0) and im not quite sure what that means...
Here are snippets of my code:
override func viewDidAppear(animated: Bool)
{
self.loadData()
}
func loadData()
{
timelineData.removeAllObjects()
var findTimelineData : PFQuery = PFUser.query()
findTimelineData.findObjectsInBackgroundWithBlock{
(objects: [AnyObject]!, error: NSError!)-> Void in
if error == nil
{
println("No error")
if let object = objects as? [PFObject!]
{
for object in objects
{
self.timelineData.addObject(object)
}
}
let array : NSArray = self.timelineData.reverseObjectEnumerator().allObjects
self.timelineData = array as NSMutableArray
self.tableView.reloadData()
}
}
}
and these would be the cells that i'm trying to load, but the code never gets this far...
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell
{
println("loading cell")
let postCell : LocationTableViewCell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as LocationTableViewCell
let post : PFObject = self.timelineData.objectAtIndex(indexPath.row) as PFObject
postCell.imageView.image = post.objectForKey("currentLocation") as? UIImage
postCell.userInfo.text = post.objectForKey("FirstLastName") as? String
// Configure the cell...
return postCell
}
for the full code: http://pastebin.com/MN7qcFhq
this worked for me:
in your code just only replace
self.timelineData = array as NSMutableArray
to
self.timelineData = NSMutableArray(array: array)
Aren't you missing an s in:
if let object = objects as? [PFObject!]
{
for object in objects
{
self.timelineData.addObject(object)
}
}
Should be:
if let objects = objects as? [PFObject!]
{
for object in objects
{
self.timelineData.addObject(object)
}
}
Hey Buddy I got you on this one. I had the same issues but I got help and got it figured out.
This is your Load Function
Try this
func loadData(){
timelineData.removeAll(keepCapacity: false)
var findTimelineData:PFQuery = PFQuery(className:"Sweets")
findTimelineData.findObjectsInBackgroundWithBlock
{
(objects:[AnyObject]! , error:NSError!) -> Void in
if error == nil
{
self.timelineData = objects.reverse() as [PFObject]
//let array:NSArray = self.timelineData.reverseObjectEnumerator().allObjects
println(objects)
// self.timelineData = array as NSMutableArray
self.tableView.reloadData()
}
}
}
Then go to your
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell
And change
let sweet:PFObject = self.timelineData.objectAtIndex(indexPath.row) as PFObject
To This
let sweet: PFObject = self.timelineData[indexPath.row] as PFObject
Whats going on is you are mixing some Obj C with Swift , the code you are following was probably written in Beta so make sure you go back and learn the differences. If you are still having problems check all your conditions if(error != nil) should not be in your code.
I noticed there are many questions on this in Obj-C but I hardly remember Obj-C and each of the answers was specific to the question. Here I get this error: "No index path for table cell being reused" sometimes when the app refreshes. I notice that when I don't refresh but I leave and reopen the table view the formatting is ruined.
Here is my "refresh" method used in a few places:
#IBAction func loadData(){
timeLineData.removeAllObjects()
//pulls the data from the server
var findTimeLineData: PFQuery = PFQuery(className: "Sweets")
findTimeLineData.findObjectsInBackgroundWithBlock{
(objects:[AnyObject]!, error: NSError!) -> Void in
if !error{
for object:PFObject! in objects{
self.timeLineData.addObject(object)
}
let tempArray: NSArray = self.timeLineData.reverseObjectEnumerator().allObjects
self.timeLineData = tempArray as NSMutableArray
//reloads the data in the table view
self.tableView.reloadData()
}
}
}
And the tableview method:
override func tableView(tableView: UITableView!, cellForRowAtIndexPath indexPath: NSIndexPath!) -> UITableViewCell? {
let cell: SweetTableViewCell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as SweetTableViewCell
let sweet: PFObject = self.timeLineData.objectAtIndex(indexPath.row) as PFObject
//part of the animation
cell.sweetTextView.alpha = 0
cell.userNameLabel.alpha = 0
cell.timestampLabel.alpha = 0
cell.sweetTextView.text = sweet.objectForKey("content") as String
//add the date
var dateFormatter: NSDateFormatter = NSDateFormatter()
dateFormatter.dateFormat = "HH:mm yyyy-MM-dd"
cell.timestampLabel.text = dateFormatter.stringFromDate(sweet.createdAt)
//finds the sweeter associated with a pointer
var findSweeter: PFQuery = PFUser.query()
findSweeter.whereKey("objectId", equalTo: sweet.objectForKey("sweeter").objectId)
findSweeter.findObjectsInBackgroundWithBlock{
(objects: [AnyObject]!, error: NSError!)-> Void in
if !error{
let user: PFUser = (objects as NSArray).lastObject as PFUser
cell.userNameLabel.text = user.username
}
}
//adds animation
UIView.animateWithDuration(1, animations: {
cell.sweetTextView.alpha = 1
cell.userNameLabel.alpha = 1
cell.timestampLabel.alpha = 1
})
return cell
}
Any idea what is causing the error?
What's causing the problem for you: when you scroll your table, the TableView dequeues the cell that your async call to parse server is trying to manipulate.
You can overcome this problem by:
1- in your Sweets table on Parse, store PFUser object as a pointer
2- in your loadData function, fetch user from the query by includeKey method of PFQuery
If you change according to this, you won't have to query the PFUser every time the cellForRowAtIndexPath is called.