How to disable user from liking a post after reloading app? - ios

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

Related

Problem with deleting an entity from other ViewController in CoreData

people. Im learning how to work with CoreData and i have a question.
I have a "WordArrayEntity" which have oneToMany relationship with "WordEntity" and Nulify deletion rule.
So at first when my app start im fetching info from my CoreData to special array
var wordEntities:[[WordEntity]] = []
After it my tableView recieves an attribute that it needs.
As i understand i can delete entities by this method
context.delete(wordEntity)
everything works fine when i do this on my tableView.
But, when i move to editor ViewController and try to add new Entity or delete previous nothing happens.
This is my code to move to next view controller
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
if let controller = self.storyboard?.instantiateViewController(withIdentifier: "ShowWordVC") as? ShowWordVC {
controller.word = SingleTonForEntities.shared.wordEntities[indexPath.section][indexPath.row]
controller.wordIndex = indexPath.row
self.present(controller, animated: true)
}
}
This is my method to delet word, which perfectly works at tableView
func deleteWordFromVocabulary(word: WordEntity,indexPath: Int) {
var wordStartsFromLetter = false
if let firstCharacter:Character = word.englishWord?.first {
var index = 0
for array in wordEntities {
if SingleTon.shared.sectionName[index].lowercased() == firstCharacter.lowercased() {
var newArray = array
context.delete(word)
newArray.remove(at: indexPath)
wordEntities[index] = newArray
wordStartsFromLetter = true
}
index += 1
}
}
if wordStartsFromLetter == false {
var newArray = wordEntities[26]
context.delete(word)
newArray.remove(at: indexPath)
wordEntities[26] = newArray
}
saveContext()
}
When i try to save word after editing or a new one i have the following code
#IBAction func saveButtonIsPressed(_ sender: UIButton) {
keyboardDissapears()
guard let englishWord = self.englishWordTextField.text,
!englishWord.isEmpty,
let wordImage = addedImageView.image,
let belarusianWord = self.belarusianWordTextField.text,
!belarusianWord.isEmpty,
let englishDefinition = self.englishDefinitionTextView.text,
!englishDefinition.isEmpty,
let belarusianDefinition = self.belarusianDefinitionTextView.text,
!belarusianDefinition.isEmpty else {
createAndShowAlert()
return
}
if editModeIsActivated == true {
if let wordToDelete = word, let wordIndex = wordIndex {
SingleTonForEntities.shared.deleteWordFromVocabulary(word: wordToDelete, indexPath: wordIndex)
}
}
word!.englishDefinition = englishDefinition
word!.belarusianDefinition = belarusianDefinition
word!.englishWord = englishWord
word!.belarusianWord = belarusianWord
let data = wordImage.pngData()
word!.wordImage = data
SingleTonForEntities.shared.addNewWordToVocabulary(word: self.word)
self.createAndShowDoneProgressAlert()
}
at first im checking if my fields are empty. If they aren't empty and we are in edit mode i delete the "WordEntity" from our context and then from our array.
And then i try to save a new word and add it to context with this method
func addNewWordToVocabulary(word: WordEntity!) {
var wordStartsFromLetter = false
word.englishWord = word.englishWord!.trimmingCharacters(in: .whitespacesAndNewlines)
if let firstCharacter:Character = word.englishWord?.first {
var index = 0
for array in wordEntities {
if SingleTon.shared.sectionName[index].lowercased() == firstCharacter.lowercased() {
var newArray = array
newArray.append(word)
for element in wordEntitesArray {
if element.arrayName == SingleTon.shared.sectionName[index].lowercased() {
element.addToWordEntities(word)
}
}
wordEntities[index] = newArray
wordStartsFromLetter = true
}
index += 1
}
}
if wordStartsFromLetter == false {
var newArray = wordEntities[26]
newArray.append(word)
wordEntities[26] = newArray
}
saveContext()
}
And there is a question. What am i doing wrong?
When i try to add new word to vocabulary - my app crashes.
But when im in edit mode and after it adding a new word to vocabulary - it just returns an empty tableViewCell.
I am new to CoreData and working with for about a week, but i would be glad to here what am i doing wrong and what i should do in such situations.
P.S. Everything worked well with UserDefaults

How to change an array that is inserted on a TableCell from information I have in another ViewController?

I need your help! I don´t know how to change an array that is inserted on a TableCell from information I have in another ViewController. It’s a little bit messed up, but I’m gonna show you by my code.
Here I have a ViewController conformed by many switches that correspond to different categories of coupons, this is the code:
class FiltersViewController: UIViewController {
#IBOutlet weak var restaurantsSwitch: UISwitch!
#IBOutlet weak var sportsSwitch: UISwitch!
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func returnHome(_ sender: Any) {
let vc = self.storyboard!.instantiateViewController(withIdentifier: "home") as! HomeViewController
self.present(vc, animated: false, completion: nil)
}
#IBAction func restaurants(_ sender: UISwitch) {
if restaurantsSwitch.isOn == true{
tuxtlaSwitch.isOn = false
sevillaSwitch.isOn = false
coapaSwitch.isOn = false
coyoacanSwitch.isOn = false
universidadSwitch.isOn = false
polancoSwitch.isOn = false
}
}
#IBAction func sports(_ sender: UISwitch) {
if sportsSwitch.isOn == true{
tuxtlaSwitch.isOn = false
sevillaSwitch.isOn = false
coapaSwitch.isOn = false
coyoacanSwitch.isOn = false
universidadSwitch.isOn = false
polancoSwitch.isOn = false
}
}
}
I’ve only show you two switches at the example with the purpose of not filling this with many code, but there are like 15 switches.
And in the other ViewController, which is connected to this one, the HomeViewController, contains coupons that comes from a JSON, and conforms an array of ten items displayed on a TableViewCell, the code:
class HomeViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
var data : NSArray = []
var mainData : NSArray = []
var couponsImg : [UIImage] = []
var couponsTitle : [String] = []
var couponsDesc : [String] = []
var couponsCat : [String] = []
func getCoupons(){
let miURL = URL(string: RequestConstants.requestUrlBase)
let request = NSMutableURLRequest(url: miURL!)
request.httpMethod = "GET"
if let data = try? Data(contentsOf: miURL! as URL) {
do {
let json = try JSONSerialization.jsonObject(with: data, options: .mutableContainers) as? NSDictionary
let parseJSON = json
let object = parseJSON?["object"] as! NSDictionary
let mainCoupon = object["mainCoupon"] as! NSArray
let coupons = object["coupons"] as! NSArray
self.mainData = mainCoupon
self.data = coupons
self.couponImg1 = (mainCoupon[0] as AnyObject).value(forKey: "urlImage") as! String
self.couponImg2 = (mainCoupon[1] as AnyObject).value(forKey: "urlImage") as! String
self.couponTitle1 = (mainCoupon[0] as AnyObject).value(forKey: "nameStore") as! String
self.couponTitle2 = (mainCoupon[1] as AnyObject).value(forKey: "nameStore") as! String
self.couponDesc1 = (mainCoupon[0] as AnyObject).value(forKey: "promoDescription") as! String
self.couponDesc2 = (mainCoupon[1] as AnyObject).value(forKey: "promoDescription") as! String
self.couponCat1 = (mainCoupon[0] as AnyObject).value(forKey: "category") as! String
self.couponCat2 = (mainCoupon[1] as AnyObject).value(forKey: "category") as! String
self.couponsImg = [couponImage1!, couponImage2!, couponImage3!, couponImage4!, couponImage5!, couponImage6!, couponImage7!, couponImage8!, couponImage9!, couponImage10!]
self.couponsTitle = [couponTitle1, couponTitle2, couponTitle3, couponTitle4, couponTitle5, couponTitle6, couponTitle7, couponTitle8, couponTitle9, couponTitle10]
self.couponsDesc = [couponDesc1, couponDesc2, couponDesc3, couponDesc4, couponDesc5, couponDesc6, couponDesc7, couponDesc8, couponDesc9, couponDesc10]
self.couponsCat = [couponCat1, couponCat2, couponCat3, couponCat4, couponCat5, couponCat6, couponCat7, couponCat8, couponCat9, couponCat10]
} catch {
let error = ErrorModel()
error.phrase = "PARSER_ERROR"
error.code = -1
error.desc = "Parser error in get Notifications action"
}
}
}
#IBAction func showFilters(_ sender: Any) {
let vc = self.storyboard!.instantiateViewController(withIdentifier: "filters") as! FiltersViewController
self.present(vc, animated: false, completion: nil)
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! HomeTableViewCell
cell.couponImg.image = couponsImg[indexPath.row]
cell.couponTitle.text = couponsTitle[indexPath.row]
cell.couponDescription.text = couponsDesc[indexPath.row]
cell.couponCategory.text = couponsCat[indexPath.row]
return cell
}
(Again I’ve only showed you two coupons for the example). The thing is that I need to apply some filters to the coupons on the TableCell. The first time the view appear it shows the 10 coupons correctly, but when I go to the filters an put it some of them ON it doesn’t make a difference, the method I was trying to use was something like this, first have an instance of the FiltersViewController class:
var filters = FilterViewController()
if filters.isMovingToParentViewController == true {
if filters.restaurantsSwitch.isOn == false {
self.couponsImg.remove(at: 0)
self.couponsImg.remove(at: 1)
self.couponsImg.remove(at: 2)
}
if filters.sportsSwitch.isOn == false {
self.couponsImg.remove(at: 3)
self.couponsImg.remove(at: 4)
self.couponsImg.remove(at: 5)
}
}
In the example bellow I’m trying to say that if a have the restaurant switch off, I’m going to delete the corresponding coupons of the restaurant category, and the same with the sports switch. But first of all I don’t know where to include this logic, in which method? And also I don’t know if this instruction is correct for my purposes. Can somebody give me a hand please???
Your logic is not working because you're instantiating a new FilterViewController, different from the FilterViewController associated with you screen.
You can solve this using delegate.
First, create the delegate:
protocol FilterDelegate {
func updateTable() }
Then, In your FilterViewController add this line:
weak var delegate:FilterDelegate?
You HomeViewController have to conform with this delegate, so:
class HomeViewController: FilterDelegate ... {
func updateTable() {
/* GET THE DATA FILTERED HERE */
tableview.reloadData()
}
In your FilterViewController:
#IBAction func returnHome(_ sender: Any) {
let vc = self.storyboard!.instantiateViewController(withIdentifier: "home") as! HomeViewController
self.delegate = vc
self.present(vc, animated: false, completion: nil)
delegate?.updateTable()
}
I think that should work.
EDIT:
Another approach is to create a segue between these two vcs and pass the which filters are active using the "prepare" function . Then you can take this information in your HomeVC and load your table based on the filters in the viewDidLoad function.
1 - Create a object Filters:
class Filters {
var tuxtlaSwitchIsOn: Bool
var sevillaSwitchIsOn: Bool
...
init(tuxtlaSwitchIsOn: Bool, sevillaSwitchIsOn: Bool, ...) {
self.tuxtlaSwitchIsOn = tuxtlaSwitchIsOn
self.sevillaSwitchIsOn = sevillaSwitchIsOn
...
}
}
2 - Add a attribute Filters to your HomeVC
class HomeViewController : ... {
...
var filtersActive: Filters?
...
}
3 - In your FilterViewController instantiate a Filter object indicating which filters are on
4 - In your FilterViewController prepare funs pass the Filter object to HomeVC
5 - In your HomeVC, get the Filter object and filter your data based on it.
Sure here is what you need. So you have a set of array filled with data and you want to apply filter on them. First, you need to create another array for filter results. This is because when user removes the filter, you still want to show the full list. To simplify, say you only have an array Foo: [String]. So you need to create another array called FooFiltered: [String] to hold the search result. Your can leave it empty when the view controller is loaded.
Next, in your filter section, it's recommended to use array filter technology like this post, but it's okay if you want to do it in your way. So all you need to do is to get elements from Foo array that match certain criteria and copy them into FooFiltered array. Here let me show you an example of doing filter manually
func filter() {
FooFiltered = [String]() //Clean up every time before search
for str in Foo {
if str == "criteria" {
FooFiltered.append(str)
}
}
}
Now you have a list of filtered items. You need a flag to tell table view which set of array to display. Say you have a flag called showSearchResult that is set to false originally. When you do the filter, set it to true. So your cellForRow will look like
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if showSearchResult {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell") as! Cell
cell.textField.text = FooFiltered[indexPath.row]
return cell
} else {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell") as! Cell
cell.textField.text = Foo[indexPath.row]
return cell
}
}
You also need to update this flag to all your table view delegate method, like numberOfRowsInSection, etc.
Finally, with these codes, your table view is configured to show full results or filtered results base on the flag and you are setting that flag in the filter() function. The last thing to do is to ask tableView to reload data when the filter is done. So modify your filter function like this and you should be all set.
func filter() {
FooFiltered = [String]() //Clean up every time before search
showSearchResul = true
for str in Foo {
if str == "criteria" {
FooFiltered.append(str)
}
}
self.tableView.reloadData()
}

Adding checkbox functionality to to-do app with Parse backend

I'm having some trouble figuring out of how make the connection between my app and Parse when a user completes a task in my to-do app. I have created the PFObject for it and I have connected the label up and that works great but I am trying to figure out how to work with Bools in Parse.
let obj = PFObject(className: "Tasks")
obj.setValue(task, forKey: "task")
obj.setValue(self.team, forKey: "team")
obj.setValue(self.checked.isChecked, forKey: "done")
obj.saveInBackgroundWithBlock() {success,error in
if error != nil {
print(error)
return
}
if success {
self.loadTasks()
// self.tasks.insert(obj, atIndex: 0)
}
}
}
}))
"checked" is the class instance of CheckBox (all of the brains of the button/checkbox) and "isChecked" is a Bool with a didSet property that changes the checkbox to checked or unchecked based on whether the button is tapped.
The checkbox works great in the app, it just doesn't sync that action.
This is what I have tried so far but to no avail...
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("TaskCell", forIndexPath: indexPath)
// Configure the cell...
cell.selectionStyle = .None
let idx = tasks[indexPath.row]
let task = idx["task"] as! String
if let label = cell.viewWithTag(1) as? UILabel {
label.text = task
}
let done = idx["done"] as! Bool
if let checkBox = cell.viewWithTag(2) as? UIButton {
checked.isChecked = done
}
return cell
}
Any and all help is welcome.
EDIT
I figured out how to create the value and save it to Parse but now I'm having trouble editing that value and sending that new info to Parse. When I refresh the data it just goes back to the default value (false).
let done = idx["done"] as! Bool
if let checkBox = cell.viewWithTag(2) as? CheckBox {
CheckBox.isChecked = done
}
How do I save the new value?

Use of unresolved identifier 'objectAtIndexPath'

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.

Swift: Only allow a user to vote on an item in a TableViewCell once

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?
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.

Resources