iOS swift parse update multiple objects - ios

i have 2 objects(parent , child) with cash and value fields for each object .
at pressing a button , it does some calculations and update objects fields to the new values ,I'm able to update the parent fields to the new values but the child values remain as they are.
heres the full code :
#IBOutlet weak var childImage: UIImageView!
#IBOutlet weak var childInfo: UILabel!
#IBOutlet weak var adoptButton: UIButton!
#IBOutlet weak var nextButton: UIButton!
#IBOutlet weak var malesButton: UIButton!
#IBOutlet weak var femalesButton: UIButton!
#IBOutlet weak var statusLabel: UILabel!
// empty array to load ids from parse
var objectIds = [String]()
// counter is the index number of Ids array
var counter = 0
var childCash = 0
var childValue = 0
var childId: String? = ""
var childName = ""
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
loadingMalesIds()
childInfo.hidden = true
adoptButton.hidden = true
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
#IBAction func maleButtonPressed(sender: AnyObject) {
loadingMalesIds()
}
#IBAction func femaleButtonPressed(sender: AnyObject) {
loadingFemalesIds()
}
#IBAction func nextButtonPressed(sender: AnyObject) {
counter++
if (counter <= objectIds.count){
var query = PFQuery(className:"_User")
query.getObjectInBackgroundWithId(objectIds[(counter - 1) ] ) {
(child: PFObject?, error: NSError?) -> Void in
if error == nil && child != nil {
self.childName = child?["username"] as! String
self.childValue = child?["value"] as! Int
var status = child?["status"] as! String
self.childCash = child?["cash"] as! Int
self.childId = (child?.objectId)!
let userImageFile = child?["userImage"] as! PFFile
userImageFile.getDataInBackgroundWithBlock {
(imageData: NSData?, error: NSError?) -> Void in
if error == nil {
if let imageData = imageData {
let image = UIImage(data:imageData)
self.childImage.layer.borderWidth = 2
self.childImage.layer.masksToBounds = true
self.childImage.layer.borderColor = UIColor.whiteColor().CGColor
self.childImage.image = image
}
}
}
self.statusLabel.text = status
self.childInfo.text = "\(self.childName), $\(self.childValue)"
} else {
println(error)
} // end of if error == nil
}
}else{
counter = 0
} //end of if (counter <= objectIds.count)
childInfo.hidden = false
adoptButton.hidden = false
}//end of method
func loadingMalesIds(){
objectIds.removeAll(keepCapacity: false)
var query = PFQuery(className:"_User")
// get Ids with a condition that users are males
query.whereKey("gender", equalTo:"male")
query.findObjectsInBackgroundWithBlock {
(objects: [AnyObject]?, error: NSError?) -> Void in
if error == nil {
// The find succeeded.
if let objects = objects as? [PFObject] {
for object in objects {
self.objectIds.append((object.objectId)! as String)
}
}
} else {
// Log details of the failure
println("Error: \(error!) \(error!.userInfo!)")
}
}
self.malesButton.enabled = false
self.femalesButton.enabled = true
}
func loadingFemalesIds(){
objectIds.removeAll(keepCapacity: false)
var query = PFQuery(className:"_User")
// get Ids with a condition that users are males
query.whereKey("gender", equalTo:"female")
query.findObjectsInBackgroundWithBlock {
(objects: [AnyObject]?, error: NSError?) -> Void in
if error == nil {
// The find succeeded.
if let objects = objects as? [PFObject] {
for object in objects {
self.objectIds.append((object.objectId)! as String)
}
}
} else {
// Log details of the failure
println("Error: \(error!) \(error!.userInfo!)")
}
}
self.malesButton.enabled = true
self.femalesButton.enabled = false
}
#IBAction func adoptPressed(sender: AnyObject) {
// get parent cash and value
let cash = PFUser.currentUser()!["cash"] as? Int
let value = PFUser.currentUser()!["value"] as? Int
let numberOfChildren = PFUser.currentUser()!["numberOfChildren"] as? Int
//check
if (cash < childValue){
noCashAlert()
}else{
var parentNewCash = cash! - childValue
var parentNewValue = value! + childValue
var childNewCash = childCash + (10 * childValue / 100)
//number of children
var newNumberOfChildren = numberOfChildren! + 1
// save parent new cash and value
var queryParent = PFQuery(className:"_User")
//get parent ID
var currentUserId = PFUser.currentUser()?.objectId!
println("current: \(currentUserId)")
// do the saving operation
queryParent.getObjectInBackgroundWithId(currentUserId!) {
(parent: PFObject?, error: NSError?) -> Void in
if error != nil {
println(error)
} else if let parent = parent {
//parent new cash
parent["cash"] = parentNewCash
//parent new value
parent["value"] = parentNewValue
//parent number of children
parent["numberOfChildren"] = newNumberOfChildren
parent.saveInBackground()
}
}//end parent query
var queryChild = PFQuery(className:"_User")
queryChild.getObjectInBackgroundWithId(self.childId!) {
(child: PFObject?, error: NSError?) -> Void in
if error != nil {
println(error)
} else if let child = child {
child["cash"] = childNewCash
child.saveInBackground()
}
}//end child query
}
}
func noCashAlert(){
let message = "Sorry!, You Don't Have Enough Money,You Can Use Money Printer For Some Extra Cash "
let okText = "Ok"
let alert = UIAlertController(title: nil, message: message, preferredStyle: UIAlertControllerStyle.Alert)
let okayButton = UIAlertAction(title: okText, style: UIAlertActionStyle.Cancel, handler: nil)
alert.addAction(okayButton)
self.presentViewController(alert, animated: true, completion: nil)
}
}

We need more information.
One possibility is that childValue is declared as an Int, and its value is less than 100, so that childValue / 100 is 0. Remember integer division vs. floating-point division. You could say instead Double(childValue) / 100.0 to force floating point.
Have you used a debugger or debugging println()s to determine if childNewCash is what you expect?
Another possibility is that child is declared as a struct not a class, but this is very unlikely given the context.
As a minor style point, you probably want:
if let e = error {
println(e)
}
so that you don't get Optional(...) in your output.
This answer should have gone into comments section but for now I'll keep it and just edit...This is impossible to answer without a lot more context. When you say "it's not updating at all" where and when are you looking?
If you're looking in the getObjectInBackgroundWithId completion handler right after setting the cash value, I'm sure you'd get the right value back.
Are you looking in the cloud database? With a background save there's some delay, and you could be looking too early. If there's conflicting saves, the save tasks could be overwriting one another. How about using the optional completion handler for saveInBackground() to make sure you're not checking too soon?

Related

Parse does not return on first query attempt

I have a segment control that determines what is loaded into the table view. I load the table for index 0 in viewDidLoad and it works fine. The segment control is 'selected' to index 0 via storyboard. When the index is switched to index 1 I get the alert message I set "No items owned" and it has me stay at index 0. When I click index 1 for the second time, it finds the items and loads the table I want for index 1. Why does the query for getOwnedCosmetics not return anything the first time the segment is changed to index 1? I print cosmeticIdOwned and it returns [ ]. The second time the array is filled with the objects.
import UIKit
import Parse
class SkinsTableViewController: UITableViewController {
var cosmeticTracker = "Skins"
var cosmeticName = [String]()
var cosmeticImage = [PFFile]()
var cosmeticRarity = [String]()
var cosmeticId = [String]()
var owned = "Owned"
var cosmeticIdOwned = [String]()
#IBOutlet var cosmeticTable: UITableView!
#IBOutlet weak var seg: UISegmentedControl!
#IBAction func segmentChanged(_ sender: UISegmentedControl) {
switch seg.selectedSegmentIndex {
case 0:
getAllCosmetics(cosmetic: cosmeticTracker)
case 1:
getOwnedCosmetics(cosmetic: cosmeticTracker)
default:
break;
}
}
override func viewDidLoad() {
super.viewDidLoad()
getAllCosmetics(cosmetic: cosmeticTracker)
}
#objc func getOwnedCosmetics (cosmetic : String){
if PFUser.current() != nil {
let query = PFQuery(className: (cosmetic + owned))
query.whereKey("theUser", equalTo: PFUser.current()?.objectId as Any)
query.findObjectsInBackground(block: { (objects, error) in
if error != nil {
print(error)
} else if let objects = objects {
self.cosmeticIdOwned.removeAll()
for object in objects {
self.cosmeticIdOwned.append(object["theCosmetic"] as! String)
}
}
})
if cosmeticIdOwned.count > 0 {
getOwnedCosmeticsQuery()
}
else {
self.displayAlert(title: "No items owned", message: "Add \(cosmeticTracker) to your colection")
seg.selectedSegmentIndex = 0
}
}
else {
self.displayAlert(title: "Unable to retrieve data", message: "Please sign in")
seg.selectedSegmentIndex = 0
}
}
func getOwnedCosmeticsQuery (){
let queryOwned = PFQuery(className: cosmeticTracker)
queryOwned.whereKey("objectId", containedIn: cosmeticIdOwned as [AnyObject])
queryOwned.order(byAscending: "Rarity")
queryOwned.addAscendingOrder("Name")
queryOwned.findObjectsInBackground(block: { (objects, error) in
if error != nil {
print(error)
} else if let objects = objects {
self.cosmeticName.removeAll()
self.cosmeticImage.removeAll()
self.cosmeticId.removeAll()
self.cosmeticRarity.removeAll()
for object in objects {
self.cosmeticName.append(object["Name"] as! String)
self.cosmeticImage.append(object["Image"] as! PFFile)
self.cosmeticId.append(object.objectId as String!)
self.cosmeticRarity.append(object["Rarity"] as! String)
self.cosmeticTable.reloadData()
if self.cosmeticIdOwned.count > 99 {
self.getOwnedCosmeticsQueryTwo()
}
}
}
})
}
}
I would like to note if I populate cosmeticIdOwned with an item in viewDidLoad. It will make it to getOwnedCosmeticsQuery and find the item, even though it is not actually 'owned'. When I click the segment for getOwnedCosmetics for the second time, it runs getOwnedCosmeticsQuery with the correct objects it found from getOwnedCosmetics.

Triggering segue from view controller doesn't load the content on the next ViewController properly

I am currently struggling with an issue where when my segue is triggered the content on the next ViewController either disappears, or takes a while to load.
This is my code that triggers the segue:
userCount = count
if userCount != -1{
self.performSegue(withIdentifier: "GoToWelcome", sender: self)
}
if userCount == -1{
self.LoadSign.isHidden = true
self.createAlert(title: "Error!", message: "Incorrect E-Mail, Please try again")
}
This is a screenshot of the two view controllers, when the "Login" button is pressed, the data is checked in the database and if it is correct, the segue is then triggered:
IMAGE
my code for the WelcomeViewController (haven't edited it):
import UIKit
class WelcomeViewController: UIViewController {
#IBOutlet weak var welcomeMessage: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
}
And here is the code for my LoginViewController (it's quite messy i know)
import UIKit
class LoginScreenController: UIViewController, UITextFieldDelegate {
#IBOutlet weak var LoadSign: UIActivityIndicatorView!
#IBOutlet weak var EMail: UITextField!
#IBOutlet var LoginScreenView: UIView!
#IBAction func LoginButton(_ sender: UIButton) {
LoadSign.isHidden = false
let email = EMail.text
let SQLurl = URL(string:"[myUrl]")
let task = URLSession.shared.dataTask(with: SQLurl!) { (data, responce, error) in
if error != nil
{
print("error")
}
else
{
if let content = data
{
do
{
let myJson = try JSONSerialization.jsonObject(with: content, options: JSONSerialization.ReadingOptions.mutableContainers) as! NSArray
var notInDB = false
var count = 0
var myJsonCount = myJson[count] as! NSDictionary
var EmailCount = (myJsonCount["txtEmailAddress"]) as! NSString
var userCount = 0
while email != EmailCount as String && notInDB == false{
count = count+1
myJsonCount = myJson[count] as! NSDictionary
EmailCount = (myJsonCount["txtEmailAddress"]) as! NSString
if count == (myJson.count-1){
print("Error! E-Mail not found in database")
notInDB = true
count = -1
}
}
userCount = count
if userCount != -1{
self.performSegue(withIdentifier: "GoToWelcome", sender: nil)
}
if userCount == -1{
self.LoadSign.isHidden = true
self.createAlert(title: "Error!", message: "Incorrect E-Mail, Please try again")
}
}
catch
{
}
}
}
}
task.resume()
}
override func viewDidLoad() {
super.viewDidLoad()
EMail.delegate = self
EMail.adjustsFontSizeToFitWidth = true
}
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
textField.resignFirstResponder()
return true
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
func createAlert (title: String, message: String)
{
let alert = UIAlertController(title: title, message: message, preferredStyle: UIAlertControllerStyle.alert)
alert.addAction(UIAlertAction(title: "Try Again", style: UIAlertActionStyle.default, handler: { (action) in
alert.dismiss(animated: true, completion: nil)
}))
self.present(alert, animated: true, completion: nil)
}
}
Are there any reasons why this would be happening?
Thank you!
I think the problem might be the fact that you're using UIKit from a different thread. UIKit calls like performSegue (and probably what's being called by self.createAlert though I can't know from the code you've given) should really only be called from the main thread.
My recommendation would be to handle just stuff related to your request in the dataTask's completion handler then pass control back to the main thread to handle the rest of it. Here's a sketch based on the code that you sent. Note that we just get the response data and parse the JSON in the dataTask's background thread, then pass control to a function on the main thread to do the rest of the work:
#IBAction func LoginButton(_ sender: UIButton) {
LoadSign.isHidden = false
let email = EMail.text
let SQLurl = URL(string:"[myUrl]")
let task = URLSession.shared.dataTask(with: SQLurl!) { (data, responce, error) in
if error != nil
{
print("error")
}
else
{
do {
// parse the JSON
if let myJson = try JSONSerialization.jsonObject(with: content, options: JSONSerialization.ReadingOptions.mutableContainers) as? NSArray {
DispatchQueue.main.async { self.handleLoginData(myJson) }
} catch {
print("JSON Parse error")
}
}
}
task.resume()
}
func handleLoginData(myJson: NSArray) {
do {
var notInDB = false
var count = 0
var myJsonCount = myJson[count] as! NSDictionary
var EmailCount = (myJsonCount["txtEmailAddress"]) as! NSString
var userCount = 0
while email != EmailCount as String && notInDB == false {
count = count+1
myJsonCount = myJson[count] as! NSDictionary
EmailCount = (myJsonCount["txtEmailAddress"]) as! NSString
if count == (myJson.count-1) {
print("Error! E-Mail not found in database")
notInDB = true
count = -1
}
}
userCount = count
if userCount != -1 {
self.performSegue(withIdentifier: "GoToWelcome", sender: nil)
} else {
self.LoadSign.isHidden = true
self.createAlert(title: "Error!", message: "Incorrect E-Mail, Please try again")
}
}
catch
{
}
}
You my have to play with the code a bit to get it to work, but notice how it leaves the data handling for the request with the request and separates out the work for your problem domain to another function. When working with asynchronous blocks try to keep all of the work done in any particular block related to just one task.

SWIFT: Tapping a UIButton really fast results in incorrect vote count

I've created a simple song list app below.
It works as follows:
1. Users can add songs to the list (using the '+' icon)
2. Uses can click to the vote button to increase or decrease their vote by one. It works similar to the 'LIKE' button on Facebook.
As you can see, some of the vote counts are negative numbers. The reason for this is because during QA, I clicked the 'Vote' button extremely fast and it causes the vote count to go haywire. I do not see this as being a scenario that would occur a lot, but does anyone have a creative way to prevent this from happening? Any solutions would be appreciated. Thanks.
I can add my code if it's required, but I am hoping that there is a simple solution to this that I haven't found as yet.
UPDATE: Added code for the Table View Controller and the Table View Cell -
Table View Cell (with UIButton Action for 'vote'):
#IBAction func voteButton(sender: UIButton) {
var query = PFQuery(className:"UserVotes")
//query the database to check if the user actually like the song in the paticular row that's clicked
query.whereKey("username", equalTo: (PFUser.currentUser()?.username)!)
query.whereKey("song", equalTo: songID!)
query.whereKey("vote", equalTo: true)
query.findObjectsInBackgroundWithBlock {
(object: [PFObject]?, error: NSError?) -> Void in
if error == nil {
//this is the case where a TRUE value is found. LIKE is ON
if object!.count > 0 {
if let object = object! as? [PFObject] {
for object in object {
if self.delegate != nil && self.rowValue != nil {
//decrease the count by 1
self.voteCount = self.voteCount! - 1
self.votes.text = "\(self.voteCount!)"
self.voteButtonOn = true
self.delegate!.voteButtonTurnOffFromCellRow(self.rowValue!)
print(self.voteButtonOn)
}
}
}
//this is the case where a TRUE value is NOT found, LIKE is OFF.
} else if object?.count == 0 {
if self.delegate != nil && self.rowValue != nil {
//increase the count by 1
self.voteCount = self.voteCount! + 1
self.votes.text = "\(self.voteCount!)"
self.voteButtonOn = false
self.delegate!.voteButtonTurnOnFromCellRow(self.rowValue!)
print(self.voteButtonOn)
}
}
} else {
// Log details of the failure
print("Error: \(error!) \(error!.userInfo)")
}
}
}
Table View Controller:
import UIKit
import Parse
protocol VoteProtocol {
func voteButtonTurnOnFromCellRow(row: Int)
func voteButtonTurnOffFromCellRow(row: Int)
}
class MusicPlaylistTableViewController: UITableViewController, VoteProtocol {
var usernames = [String]()
var songs = [String]()
var songVotes = [Int]()
//Function to display an alert
func displayAlert(title: String, message: String) {
let alert = UIAlertController(title: title, message: message, preferredStyle: UIAlertControllerStyle.Alert)
alert.addAction(UIAlertAction(title: "OK", style: UIAlertActionStyle.Default, handler: nil))
self.presentViewController(alert, animated: true, completion: nil)
}
override func viewDidLoad() {
super.viewDidLoad()
tableView.separatorColor = UIColor.grayColor()
let query = PFQuery(className:"PlaylistData")
query.findObjectsInBackgroundWithBlock { (objects: [PFObject]?, error: NSError?) -> Void in
if error == nil {
if let objects = objects! as? [PFObject] {
self.usernames.removeAll()
self.songs.removeAll()
for object in objects {
let username = object["username"] as? String
self.usernames.append(username!)
let track = object["song"] as? String
self.songs.append(track!)
let votes = object["numVotes"] as? Int
self.songVotes.append(votes!)
}
self.tableView.reloadData()
}
} else {
print(error)
}
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
// #warning Incomplete implementation, return the number of sections
return 1
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of rows
return usernames.count
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("CellTrack", forIndexPath: indexPath) as! TrackTableViewCell
cell.username.text = usernames[indexPath.row]
cell.songTitle.text = songs[indexPath.row]
var voteCount: Int = songVotes[indexPath.row]
var voteCountString = String(voteCount)
cell.votes.text = voteCountString
cell.delegate = self
cell.rowValue = indexPath.row
cell.songID = songs[indexPath.row]
cell.voteCount = songVotes[indexPath.row]
cell.selectionStyle = UITableViewCellSelectionStyle.None
return cell
}
override func tableView(tableView: UITableView, didDeselectRowAtIndexPath indexPath: NSIndexPath) {
}
//If the LIKE button is OFF perform this. This means that a record in the database has NOT been found with a value of TRUE.
func voteButtonTurnOnFromCellRow(var row: Int) {
// At this point, the value needs to be changed to TRUE, and the vote count needs to be increased by 1.
//note: does not take into account scenario where there are duplicate records (slight case)
let query = PFQuery(className:"UserVotes")
query.whereKey("username", equalTo: (PFUser.currentUser()?.username)!)
query.whereKey("song", equalTo: songs[row])
query.whereKey("vote", equalTo: false)
query.findObjectsInBackgroundWithBlock { (object: [PFObject]?, error: NSError?) -> Void in
if error == nil {
//this is the case where a FALSE value is found. LIKE is OFF
if object!.count > 0 {
if let object = object! as? [PFObject] {
for object in object {
object["vote"] = true
object.saveInBackground()
let query = PFQuery(className:"PlaylistData")
query.whereKey("song", equalTo: self.songs[row])
query.findObjectsInBackgroundWithBlock { (object: [PFObject]?, error: NSError?) -> Void in
if error == nil {
if let object = object! as? [PFObject] {
for object in object {
var voteCount:Int = object["numVotes"] as! Int
voteCount = voteCount + 1
object["numVotes"] = voteCount
object.saveInBackground()
}
}
}
}
}
}
//this is the case where a TRUE value is NOT found, LIKE is OFF (first time user vote)
} else if object?.count == 0 {
//add row to table
let addUserVote = PFObject(className: "UserVotes")
addUserVote["song"] = self.songs[row]
addUserVote["username"] = PFUser.currentUser()?.username
addUserVote["vote"] = true
addUserVote.saveInBackgroundWithBlock {
(success: Bool, error: NSError?) -> Void in
if (success) {
if let error = error {
if let errorString = error.userInfo["error"] as? String {
self.displayAlert("Error", message: errorString)
}
} else {
let query = PFQuery(className:"PlaylistData")
query.whereKey("song", equalTo: self.songs[row])
query.findObjectsInBackgroundWithBlock { (object: [PFObject]?, error: NSError?) -> Void in
if error == nil {
if let object = object! as? [PFObject] {
for object in object {
var voteCount:Int = object["numVotes"] as! Int
voteCount = voteCount + 1
object["numVotes"] = voteCount
object.saveInBackground()
}
}
}
}
}
} else {
// There was a problem, check error.description
}
}
}
} else {
// Log details of the failure
print("Error: \(error!) \(error!.userInfo)")
}
}
}
//If the LIKE button is ON perform this. This means that a record in the database has been found with a value of TRUE.
func voteButtonTurnOffFromCellRow(var row: Int) {
// At this point, the value needs to be changed to FALSE, and the vote count needs to be decreased by 1.
//note: does not take into account scenario where there are duplicate records (slight case)
let query = PFQuery(className:"UserVotes")
query.whereKey("username", equalTo: (PFUser.currentUser()?.username)!)
query.whereKey("song", equalTo: songs[row])
query.whereKey("vote", equalTo: true)
query.findObjectsInBackgroundWithBlock { (object: [PFObject]?, error: NSError?) -> Void in
if error == nil {
if let object = object! as? [PFObject] {
for object in object {
object["vote"] = false
object.saveInBackground()
let query = PFQuery(className:"PlaylistData")
query.whereKey("song", equalTo: self.songs[row])
query.findObjectsInBackgroundWithBlock { (object: [PFObject]?, error: NSError?) -> Void in
if error == nil {
if let object = object! as? [PFObject] {
for object in object {
var voteCount:Int = object["numVotes"] as! Int
voteCount = voteCount - 1
object["numVotes"] = voteCount
object.saveInBackground()
}
}
}
}
}
} else {
print(error)
}
}
}
}
}
In your button action toggle button userInteractionEnabled property to NO at the beginning and to YES at the end of the method. This may help you

SWIFT: Parse data is deleted when UIButton Action is performed

Here are the steps that I'm taking -
Step 1: I enter values in the following fields, and they get saved in the Parse table. Works perfect.
Step 2: I access this screen again, and click 'Submit' again without editing the fields, and the Parse table row is deleted.
I am trying to figure out why this is happening. The desired functionality is that when the user loads this screen for the first time, the fields are blank. Once this screen is loaded thereafter, the existing values are displaying the fields, and if the user edits the fields and clicks 'Submit', the values will chance.
This is my code for this particular view controller -
import UIKit
import Parse
class RSVPViewController: UIViewController {
/*----------- ~ OUTLETS AND ACTIONS ~ -----------*/
//View Controller Title Bar Outlets
#IBOutlet weak var usernameTitle: UIBarButtonItem!
#IBOutlet weak var menuTitle: UIBarButtonItem!
//View Controller Body Outlets
#IBOutlet weak var label1: UILabel!
#IBOutlet weak var label2: UILabel!
#IBOutlet weak var label3: UILabel!
#IBOutlet weak var label4: UILabel!
#IBOutlet weak var label1Field: UITextField!
#IBOutlet weak var label2Field: UITextField!
#IBOutlet weak var label3Field: UITextField!
#IBOutlet weak var label4Field: UITextField!
//View Controller Body Actions
#IBAction func submitButton(sender: AnyObject) {
let query = PFQuery(className:"RSVPData")
query.whereKey("username", equalTo: (PFUser.currentUser()?.username)!)
query.findObjectsInBackgroundWithBlock { (objects: [PFObject]?, error: NSError?) -> Void in
if error == nil {
if let objects = objects! as? [PFObject] {
for object in objects {
object.deleteInBackground()
}
}
} else {
print(error)
}
}
let RSVPData = PFObject(className: "RSVPData")
RSVPData["label1"] = label1Field.text
RSVPData["label2"] = label2Field.text
RSVPData["label3"] = label3Field.text
RSVPData["label4"] = label4Field.text
RSVPData["username"] = PFUser.currentUser()?.username
RSVPData.saveInBackgroundWithBlock {
(success: Bool, error: NSError?) -> Void in
if (success) {
if let error = error {
if let errorString = error.userInfo["error"] as? String {
self.displayAlert("Failed", message: errorString)
}
} else {
self.performSegueWithIdentifier("RSVPToMain", sender: self)
}
} else {
// There was a problem, check error.description
}
}
}
/*----------- ~ MAIN THREAD ~ -----------*/
override func viewDidLoad() {
super.viewDidLoad()
//Run title bar function
TitleBar()
label1.text = "Label 1"
label2.text = "Label 2"
label3.text = "Label 3"
label4.text = "Label 4"
let query = PFQuery(className:"RSVPData")
query.whereKey("username", equalTo: (PFUser.currentUser()?.username)!)
query.findObjectsInBackgroundWithBlock { (objects: [PFObject]?, error: NSError?) -> Void in
if error == nil {
if let objects = objects! as? [PFObject] {
for object in objects {
self.label1Field.text = object["label1"] as? String
self.label2Field.text = object["label2"] as? String
self.label3Field.text = object["label3"] as? String
self.label4Field.text = object["label4"] as? String
}
}
} else {
print(error)
}
}
}
/*----------- ~ FUNCTIONS ~ -----------*/
//Function containing all title bar instructions
func TitleBar() {
/*----------- ~ NAVIGATION BAR (USER INTERFACE) ~ -----------*/
//Set the navigation bar tet color and size
let nav = self.navigationController?.navigationBar
nav?.titleTextAttributes = [NSFontAttributeName: UIFont(name: "HelveticaNeue-Light", size: 20)!, NSForegroundColorAttributeName: UIColor(red: 255/255, green: 255/255, blue: 255/255, alpha: 1.0)]
/*----------- ~ OUTLET - 'usernameTitle' ~ -----------*/
//Set the 'usernameTitle' font and size
usernameTitle.setTitleTextAttributes([NSFontAttributeName: UIFont(name: "Arial", size: 11)!], forState: UIControlState.Normal)
//Set the 'usernameTitle' outlet to the current user
usernameTitle.title = PFUser.currentUser()?.username
}
//Function to display an alert
func displayAlert(title: String, message: String) {
let alert = UIAlertController(title: title, message: message, preferredStyle: UIAlertControllerStyle.Alert)
alert.addAction(UIAlertAction(title: "OK", style: UIAlertActionStyle.Default, handler: nil))
self.presentViewController(alert, animated: true, completion: nil)
}
SOLUTION:
#IBAction func submitButton(sender: AnyObject) {
let query = PFQuery(className:"RSVPData")
query.whereKey("username", equalTo: (PFUser.currentUser()?.username)!)
query.findObjectsInBackgroundWithBlock { (object: [PFObject]?, error: NSError?) -> Void in
if error == nil {
if object!.count > 0 {
if let object = object! as? [PFObject] {
for object in object {
object["label1"] = self.label1Field.text
object["label2"] = self.label2Field.text
object["label3"] = self.label3Field.text
object["label4"] = self.label4Field.text
object.saveInBackground()
}
self.performSegueWithIdentifier("RSVPToMain", sender: self)
}
} else if object?.count == 0 {
let RSVPData = PFObject(className: "RSVPData")
RSVPData["label1"] = self.label1Field.text
RSVPData["label2"] = self.label2Field.text
RSVPData["label3"] = self.label3Field.text
RSVPData["label4"] = self.label4Field.text
RSVPData["username"] = PFUser.currentUser()?.username
RSVPData.saveInBackgroundWithBlock {
(success: Bool, error: NSError?) -> Void in
if (success) {
if let error = error {
if let errorString = error.userInfo["error"] as? String {
self.displayAlert("Failed", message: errorString)
}
} else {
self.performSegueWithIdentifier("RSVPToMain", sender: self)
}
} else {
// There was a problem, check error.description
}
}
}
} else {
print(error)
}
}
}
I do not need to delete the row if it exists. Instead of deleting the row, you can just update with the new values that you get from the user. Here is a sample from parse.com website on how to update an existing object. Let me know if you still have a question and i will try it in Xcode and update it with your code. https://parse.com/docs/ios/guide
var query = PFQuery(className:"GameScore")
query.getObjectInBackgroundWithId("xWMyZEGZ") {
(gameScore: PFObject?, error: NSError?) -> Void in
if error != nil {
print(error)
} else if let gameScore = gameScore {
gameScore["cheatMode"] = true
gameScore["score"] = 1338
gameScore.saveInBackground()
}
}
You can use your code and see comments bellow.
query.whereKey("username", equalTo: (PFUser.currentUser()?.username)!)
query.findObjectsInBackgroundWithBlock { (objects: [PFObject]?, error: NSError?) -> Void in
if error == nil {
if let objects = objects! as? [PFObject] {
//see comments bellow
//for object in objects {
//object.deleteInBackground()
// }
}
} else {
print(error)
}
}
When you get the array of objects, you would check if there is only one object in the array to make sure there is only one row for that user and then do object["sangeet"] = label1Field.text
update all the fields and do object.saveInBackground();
==========
Update
You can create a new row when objects return nil. I am thinking you should be able excuse save operation inside of the findObjectsInBackgroundWithBlock block I am not very familiar with swift so i copy pasted your code and it should work. Let me know if you still having issues.
let query = PFQuery(className:"RSVPData")
query.whereKey("username", equalTo: (PFUser.currentUser()?.username)!)
query.findObjectsInBackgroundWithBlock { (objects: [PFObject]?, error: NSError?) -> Void in
if error == nil {
if let objects = objects! as? [PFObject] {
for object in objects {
object.deleteInBackground()
}
}else{
//assuming that object is nil and there is no record for that user
//you would then add the user and information into the database
let RSVPData = PFObject(className: "RSVPData")
RSVPData["sangeet"] = label1Field.text
RSVPData["tibetan"] = label2Field.text
RSVPData["hindu"] = label3Field.text
RSVPData["reception"] = label4Field.text
RSVPData["username"] = PFUser.currentUser()?.username
RSVPData.saveInBackgroundWithBlock {
(success: Bool, error: NSError?) -> Void in
if (success) {
if let error = error {
if let errorString = error.userInfo["error"] as? String {
self.displayAlert("Failed", message: errorString)
}
} else {
self.performSegueWithIdentifier("RSVPToMain", sender: self)
}
} else {
// There was a problem, check error.description
}
}
}
} else {
print(error)
}
}
Seems to me that you do delete the row if the username is the same value as your PFUser's username. I have not used Parse before but object.deleteInBackground() looks like a delete to me.
let query = PFQuery(className:"RSVPData")
query.whereKey("username", equalTo: (PFUser.currentUser()?.username)!)
query.findObjectsInBackgroundWithBlock { (objects: [PFObject]?, error: NSError?) -> Void in
if error == nil {
if let objects = objects! as? [PFObject] {
for object in objects {
object.deleteInBackground()
}
}
}
}
So I guess you only delete the entry if you use the same user twice.

Operation is invalid after previous operation

I am making some good progress on my social networking application. I have run into an issue. I have a ViewController that provides a TableView that is populated with content from my parse database. In the cells I have a RSVP button that is supposed to send a RSVP based on the indexPath to the database when clicked and remove when it is clicked again. I believe my logic is correct, however, I keep receiving the error:
Operation is invalid after previous operation.
I placed a breakpoint to locate the cause of the crash and I have identified that it is happening at the addUniqueObject line within the rsvpButtonClicked IBOutlet function.
Can someone help me? My code is below.
import UIKit
import CoreLocation
class MainFeedViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
#IBOutlet var eventsTable: UITableView!
var user = PFUser.currentUser()
var refresher: UIRefreshControl!
var eventId = [String]()
var eventNames = [String]()
var eventCity = [String]()
var imageFiles = [PFFile]()
var getEventsQuery = 0
var userRsvps = [NSArray]()
var rsvp = 0
override func viewDidLoad() {
super.viewDidLoad()
getuserlocation()
getTodayDate()
}
override func viewWillAppear(animated: Bool) {
refreshControl()
//getMyRsvps()
}
override func viewDidAppear(animated: Bool) {
let timer = NSTimer.scheduledTimerWithTimeInterval(2.0, target: self, selector: "eventQuery", userInfo: nil, repeats: false)
}
func getMyRsvps() {
var getRsvps = PFUser.query()
getRsvps.whereKey("objectId", equalTo: user.objectId)
getRsvps.whereKeyExists("myrsvp")
getRsvps.findObjectsInBackgroundWithBlock {
(objects: [AnyObject]!, error: NSError!) -> Void in
if error == nil {
self.userRsvps.append(objects)
}
}
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = self.eventsTable.dequeueReusableCellWithIdentifier("mainFeedContent") as MainFeedContentTableViewCell
cell.eventName.text = eventNames[indexPath.row]
imageFiles[indexPath.row].getDataInBackgroundWithBlock{
(imageData: NSData!, error: NSError!) -> Void in
if error == nil {
let image = UIImage(data: imageData)
cell.eventImage.image = image
}
}
cell.rsvpButton.tag = indexPath.row
cell.rsvpButton.addTarget(self, action: "rsvpButtonClick:", forControlEvents: UIControlEvents.TouchUpInside)
return cell
}
#IBAction func rsvpButtonClick(sender: UIButton) {
var senderButton = sender
println("Current row \(senderButton.tag)")
var tempObject = eventId[senderButton.tag]
println("\(tempObject)")
PFUser.currentUser().addUniqueObject(tempObject, forKey: "myrsvp")
PFUser.currentUser().saveInBackground()
}
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return eventId.count
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
let mainViewIdentifier = "showDetail"
if segue.identifier == mainViewIdentifier {
if let destination = segue.destinationViewController as? DetailViewController {
if let feedIndex = eventsTable.indexPathForSelectedRow()?.row {
destination.eventNames = eventNames[feedIndex]
destination.eventId = eventId[feedIndex]
destination.eventImagesFile = imageFiles[feedIndex]
}
}
}
}
func eventQuery() {
let getEventsQuery = PFQuery(className: "Events")
getEventsQuery.whereKey("eventLocation", nearGeoPoint: user["location"] as PFGeoPoint, withinMiles: user["preferredDistanceEvents"] as Double)
getEventsQuery.limit = 16
getEventsQuery.findObjectsInBackgroundWithBlock {(objects: [AnyObject]!, error: NSError!) -> Void in
if error == nil {
println("Successfully retrieved \(objects.count) events")
for object in objects {
self.eventId.append(object.objectId)
self.eventNames.append(object["eventName"] as String)
self.eventCity.append(object["eventCity"] as String)
self.imageFiles.append(object["eventPicture"] as PFFile)
self.eventsTable.reloadData()
}
} else {
println(error)
}
}
}
func refreshControl() {
refresher = UIRefreshControl()
refresher.attributedTitle = NSAttributedString(string: "Pull to refresh")
refresher.addTarget(self, action: "refresh", forControlEvents: UIControlEvents.ValueChanged)
self.eventsTable.addSubview(refresher)
}
/*
func scrollViewDidEndDragging(scrollView: UIScrollView, willDecelerate decelerate: Bool) {
var height: CGFloat = scrollView.frame.size.height
var contentYoffset: CGFloat = scrollView.contentOffset.y
var distanceFromBottom: CGFloat = scrollView.contentSize.height - contentYoffset
if distanceFromBottom <= height {
println("End of Table")
refresh()
}
}
*/
func refresh() {
self.getEventsQuery++
if self.getEventsQuery < self.eventId.count {
let refreshEventsQuery = PFQuery(className: "Events")
refreshEventsQuery.whereKey("location", nearGeoPoint: user["location"] as PFGeoPoint, withinMiles: user["preferredDistanceEvents"] as Double)
refreshEventsQuery.whereKey("objectId", notContainedIn: eventId)
refreshEventsQuery.limit = 4
refreshEventsQuery.findObjectsInBackgroundWithBlock {(objects: [AnyObject]!, error: NSError!) -> Void in
if error == nil {
println("Successfully retrieved \(objects.count) events")
for object in objects {
self.eventNames.append(object["eventName"] as String)
self.eventCity.append(object["City"] as String)
self.imageFiles.append(object["eventPicture"] as PFFile)
self.eventId.append(object.objectId)
self.eventsTable.reloadData()
}
} else {
println(error)
}
}
} else {
println("No More Events. Sorry")
}
self.refresher.endRefreshing()
}
func getuserlocation() {
PFGeoPoint.geoPointForCurrentLocationInBackground { (geopoint: PFGeoPoint!, error: NSError!) -> Void in
if error == nil {
self.user["location"] = geopoint
self.user.saveInBackground()
}
}
}
func getTodayDate(){
var today = NSDate()
var calendar = NSCalendar.currentCalendar()
var flags = NSCalendarUnit.HourCalendarUnit | NSCalendarUnit.MinuteCalendarUnit
var components = calendar.components(flags, fromDate: today)
var hour = components.hour
var minutes = components.minute
println("Today is \(today). The time is \(hour):\(minutes)")
}
}
I think this is a bug in parse (in code shared with JS, because I've experienced the problem there). Specifically, this failure appears when intermixing the add and remove PFObject methods to manipulate array columns.
I've used both of the following workarounds with some success:
Delimit adds and removes with intervening saves. In other words, build your own addToColAndSave and removeFromColAndSave convenience methods. If you've called one of them, don't call the other until the save completes.
Probably easier: restructure the code to avoid the add/remove methods on arrays. Instead, use the getter (get in JS, objectForKey: in iOS) to get the array in memory, then manipulate it natively. Use the sdk setter to update the object.

Resources