-2

I am making an application where users can post/comment/ and like posts. I would like to implement a thumbs up/down similar to YouTube. I currenlty have two IBActions for like and dislike buttons. My question is, how to deselect the button when the other button is selected and decrease/increase the value of each button? Vice versa? Here is the following code.

@IBAction func dislikeButton(sender: UIButton) {



    let selected: Bool = !sender.selected

    var likeCount: Int = Int((sender.titleLabel?.text)!)!

    if selected {
        //upvote
        likeCount++
        print("inc \(likeCount)")

    } else {
        //downvote, but do not allow negative values
        if likeCount == 0{
            likeCount = 0
        } else {
            likeCount--
        }
        print("dec \(likeCount)")
    }
    sender.setTitle(String(likeCount), forState: UIControlState.Normal)
    sender.selected = !sender.selected


}





@IBAction func likeButton(sender: UIButton) {

    let selected: Bool = !sender.selected

    var dislikeCount: Int = Int((sender.titleLabel?.text)!)!

    if selected {

        //upvote
        dislikeCount++
        print("inc \(dislikeCount)")

    } else {
        //downvote, but do not allow negative values
        if dislikeCount == 0{
            dislikeCount = 0
        } else {
            dislikeCount--
        }
        print("dec \(dislikeCount)")
    }
    sender.setTitle(String(dislikeCount), forState: UIControlState.Normal)
    sender.selected = !sender.selected

}

1 Answers1

1

It's usually better design to separate the model from the views. That is, it's better design (in your case) to store the user's vote (like/dislike/none) and the vote counts directly, instead of implicitly as the button states and labels.

class VotableItem {

    // Votes: -1 for dislike, 1 for like, 0 for no vote
    var vote: Int = 0 // initialize to user's existing vote, retrieved from the server
    var likeCount: Int = 0 // initialize to existing like count, retrieved from the server
    var dislikeCount: Int = 0 // initialize to existing dislike count, retrieved from the server

You'll also need outlets connected to the like and dislike buttons, to update them:

    @IBOutlet var likeButton: UIButton!
    @IBOutlet var dislikeButton: UIButton!

When the user clicks the Like button, there are two possible actions. If the existing vote was “like” (stored as 1), she wants to undo her “like” vote and set her vote back to “none” (stored as 0). Otherwise, the existing vote was either “none” or “dislike” (stored as -1), and she wants to undo her existing vote and set her vote to “like”.

Similarly, when the user clicks the Dislike button, there are two possible actions. If her existing vote was “dislike”, she wants to undo it, setting it to “none”. Otherwise, she wants to undo her existing “like” or “none” vote, and set her vote to “dislike”.

Note that undoing a “none” vote has no effect, but the logic is simpler if we allow undoing it (by doing nothing).

Since the actions of the Like and Dislike buttons are so similar, we can factor out the common behavior. Connect the buttons to these actions:

    @IBAction func dislikeButtonWasClicked(sender: UIButton) {
        buttonWasClickedForVote(-1)
    }

    @IBAction func likeButtonWasClicked(sender: UIButton) {
        buttonWasClickedForVote(1)
    }

Then implement the buttonWasClickedForVote(_:) function:

    private func buttonWasClickedForVote(buttonVote: Int) {
        if buttonVote == vote {
            // User wants to undo her existing vote.
            applyChange(-1, toCountForVote: vote)
            vote = 0
        }

        else {
            // User wants to force vote to toggledVote.
            // Undo current vote, if any.
            applyChange(-1, toCountForVote: vote)
            // Apply new vote.
            vote = buttonVote
            applyChange(1, toCountForVote: vote)
        }

        updateUserInterfaceFromModel()
    }

I'm using a helper function to update the counts. Note that it does nothing if the vote is 0 (meaning “none”):

    private func applyChange(change: Int, toCountForVote vote: Int ) {
        if vote == -1 { dislikeCount += change }
        else if vote == 1 { likeCount += change }
    }

And we need another function to make the UI state match the model:

    private func updateUserInterfaceFromModel() {
        likeButton.selected = vote == 1
        likeButton.setTitle("\(likeCount)", forState: .Normal)
        dislikeButton.selected = vote == -1
        dislikeButton.setTitle("\(dislikeCount)", forState: .Normal)
    }

Note that the updateUserInterfaceFromModel function doesn't depend on the current state of the user interface. That means you can use it during initialization too. Just set vote, likeCount, and dislikeCount (presumably based on data from the server). Then, in viewDidLoad, call updateUserInterfaceFromModel.

rob mayoff
  • 375,296
  • 67
  • 796
  • 848
  • Hey really appreciate the answer! how can I store this in Parse database? – suitescripter123 Oct 27 '15 at 04:26
  • You'll need to read the Parse documentation for that, and maybe follow a Parse tutorial to learn the API. – rob mayoff Oct 27 '15 at 04:27
  • Thanks. I tried your code but getting an error here at : the last function: func updateUserInterfaceFrommodel().. im getting the fatal error: unexpected found nil wihle unwrapping an Optional value... help is appreciated! – suitescripter123 Oct 27 '15 at 04:43
  • I suspect you didn't connect the `likeButton` and `dislikeButton` outlets in your storyboard. – rob mayoff Oct 27 '15 at 04:49
  • Thanks a lot. If I were to store the like/dislike counts in Parse, would I store the the first 3 variables declared (vote, likeCount, and dislikeCount)? or just the vote? – suitescripter123 Oct 27 '15 at 15:57
  • You would need to store all three. I haven't used Parse, but I think you'd want to use [the `incrementKey:byAmount` method](http://blog.parse.com/learn/engineering/increment-operators/) to update each counter, to avoid race conditions. – rob mayoff Oct 27 '15 at 16:01
  • Thanks, can i contact you if i have questions? This is really what i've been looking for for the past few weeks and you're the only one to answer! i'd really appreciate if i can message you somewhere – suitescripter123 Oct 27 '15 at 16:10
  • Sorry, but I only answer questions on stack overflow. – rob mayoff Oct 27 '15 at 16:14
  • Hey rob, I tried calling this in View Did load but couldn't get it to work.. in view did load i put int updateUserInterfaceFromModel() is there anything else i need to do? – suitescripter123 Oct 28 '15 at 14:37
  • Hi Rob, in which function would i use the incrementKey:byAmount method in? would it be under the 'button was clicked for vote'? – suitescripter123 Nov 03 '15 at 17:18
  • Presumably you'd want to use it in `applyChange(_:,toCountForVote:)` to update your `PFObject`. – rob mayoff Nov 03 '15 at 17:23