1

So I'm making a news feed similar to the one in Yik Yak in which users post content, and other users can upvote or downvote that post. I'm integrating this function with Parse, and I've successfully made upvoting and downvoting functions that upload to Parse (following the tutorial here: http://shrikar.com/parse-backed-uitableview-in-ios-8-build-yik-yak-clone-part-2/). Unfortunately I'm struggling to figure out how to allow a user to only upvote/downvote once.

My initial idea was to create locally stored Booleans for if the user has upvoted or downvoting, and use if/else statements to determine if the user can upvote/downvote again. This has lead me to the following code (IBActions connected to the buttons for upvoting and downvoting):

class TableViewController: PFQueryTableViewController {

var hasUpvoted: Bool?
var hasDownvoted: Bool?

@IBAction func yellUpvote(sender: AnyObject) {
      let hitPoint = sender.convertPoint(CGPointZero, toView: self.tableView)
    let hitIndex = self.tableView.indexPathForRowAtPoint(hitPoint)
    let object = objectAtIndexPath(hitIndex)
    object!.incrementKey("voteCount")
    object!.saveInBackground()
    self.tableView.reloadData()
    NSLog("Top Index Path \(hitIndex?.row)")



    if (hasUpvoted == true) {
        println("Do nothing, A")
    }


    if (hasUpvoted == false) {
        if (hasDownvoted == true){
            println("Add two points, B")
            hasUpvoted = true
            hasDownvoted = false
        }


        }

        if (hasUpvoted == nil){
            if (hasDownvoted == true){
                println("Add 2 pts, E")
                hasUpvoted = true
                hasDownvoted = false
            }

            if (hasDownvoted == nil){
                println("Add 1 point, G")
                hasUpvoted = true
            }
    }


}


@IBAction func yellDownvote(sender: AnyObject) {

        let hitPoint = sender.convertPoint(CGPointZero, toView: self.tableView)
    let hitIndex = self.tableView.indexPathForRowAtPoint(hitPoint)
    let object = objectAtIndexPath(hitIndex)
    object!.incrementKey("voteCount", byAmount: -1)
    object!.saveInBackground()
    self.tableView.reloadData()
    NSLog("Bottom Index Path \(hitIndex?.row)")

    if (hasDownvoted == true) {
        println("do nothing, H")
    }


    if (hasDownvoted == false) {


        if (hasUpvoted == true){
            println("downvote 2, J")
            hasDownvoted = true
            hasUpvoted = false
        }

    }

    if (hasDownvoted == nil){
        if (hasUpvoted == true){
            println ("downvote 2, L")
            hasDownvoted = true
            hasUpvoted = false
        }



        if (hasUpvoted == nil) {
            println ("downvote 1, N")
            hasDownvoted = true
            hasUpvoted = false
        }
    }
}

As I quickly realized, these Booleans are not exclusive to the given cell in the TableView, so if I upvoted post A, and wanted to upvote post B, the Booleans would be set as if I'd already upvoted post B. The second issue is that these would be reset to nil upon a user closing the app, allowing them to vote multiple times on the same post.

My next thought was to use the following code to make the Booleans exclusive to the given cell:

    let hitPoint = sender.convertPoint(CGPointZero, toView: self.tableView)
    let hitIndex = self.tableView.indexPathForRowAtPoint(hitPoint)

I can't figure out how to do this though. Further, this wouldn't solve the issue of the user being able to simply close the app in order to regain the ability to vote again.

So, does anyone have a solution as to how to allow a user to only vote once?

clfougner
  • 173
  • 1
  • 11
  • I would suggest you create a vote object in Parse that records the id of the user and the id of the object that they have voted on then you can check this table for each object and determine whether to enable the voting button on each table row – Paulw11 Jul 09 '15 at 05:33
  • Hey man I'm working on this tutorial as well, but I cannot seem to be able to get the comments to work. Did it work for you? Or any ideas on how can I improve it? I'm stuck there – Bruno Recillas Aug 28 '15 at 00:00

1 Answers1

1

Ok, so I've found an acceptable solution. This integrates Parse, and only allows the user to upvote and to retract an upvote (as opposed to also downvoting). Downvoting can however be implemented following a similar approach to the one I have chosen (albeit with quite a lot more if/else statements).

class TableViewController: PFQueryTableViewController {

var hasUpvoted: Bool?

@IBAction func postUpvote(sender: AnyObject) {

    let hitPoint = sender.convertPoint(CGPointZero, toView: self.tableView)
    let hitIndex = self.tableView.indexPathForRowAtPoint(hitPoint)
    let object = objectAtIndexPath(hitIndex)

    var hasVotedQuery = PFQuery(className:"Posts")
    hasVotedQuery.getObjectInBackgroundWithId(object!.objectId!) {
        (post: PFObject?, error: NSError?) -> Void in
        if error != nil {
            println(error)
        } 
        else if let post = post {

            var usersHaveUpvotedArray = post["usersHaveUpvoted"] as? NSArray
            println(usersHaveUpvotedArray)
            var userObjectId = PFUser.currentUser()?.objectId as String!

            let contained = contains(usersHaveUpvotedArray! as! Array, userObjectId)

            if (contained == true) {
                post.removeObject(userObjectId, forKey: "usersHaveUpvoted")
                post.saveInBackground()
                object!.incrementKey("voteCount", byAmount: -1)
                object!.saveInBackground()
                self.tableView.reloadData()
            }

            if (contained == false) {
                post.addUniqueObject(userObjectId, forKey: "usersHaveUpvoted")
                post.saveInBackground()
                object!.incrementKey("voteCount")
                object!.saveInBackground()
                self.tableView.reloadData()
            }
        }
    }
}

The IBAction is linked to the upvote button. "Posts" is the Parse class containing the posts. "usersHaveUpvoted" is an array containing the objectIds of users that have upvoted the given post. Note that my code will cause the app to crash if the usersHaveUpvoted array is nil. My solution to this is automatically placing a value in the usersHaveUpvoted array when uploading the post. There's probably a more elegant way to solve that issue though.

The only other problem though is the fact that if the user presses the upvote button two times quickly enough, it's possible to upvote twice before the phone receives the message that the user already has upvoted. There is likely a clever way to do this using the local data store.

clfougner
  • 173
  • 1
  • 11
  • Hi, Ive been trying to figure out your same issue! I was wondering if you managed to get this voting successfully working? I tried the code you added in you answer above, but I just get `nil` printed in the logs... I have posted a question here, http://stackoverflow.com/questions/35878086/make-a-voting-system-with-parse-in-swift, can you help me at all? thanks in advance – Nick89 Mar 09 '16 at 21:48