Parse + iOS: 101 error when saving after a delete operation - ios

In my iOS app, I have a PFQueryCollectionViewController that displays UserPhoto objects. Tapping on a collection view cell opens a photo detail page where the user can add comments to the photo. This workflow works perfectly with no errors until I delete a UserPhoto either from the collection view controller (using removeObjectsAtIndexPaths) or the detail view controller (using deleteInBackgroundWithBlock).
The scope of the issue is broad. When I attempt to perform any kind of save operation on any existing object (not just UserPhoto) after performing a delete operation, I get:
"[Error]: object not found for update (Code: 101, Version: 1.11.0)"
I've triple checked that I'm attempting to save the correct object, and not the one that was just deleted
I can log out the objects I'm trying to save and they are well-formed.
The object I'm attempting to save exists on the Parse dashboard
The ACL for all objects is Public Read + Write
The save operations that fail after delete work fine otherwise, so there is nothing wrong with the code that does the saving
The only way to restore save functionality after a delete operation is completely quit the app.
I'm dying here. Thanks.
Source Code
First, a simple function for looping through an array of selected photos from the collectionView and calling removeObjectsAtIndexPaths. Although removeObjectsAtIndexPaths is somewhat of a mystery (limited documentation), I have confirmed that it immediately deletes the objects from Parse.
func deleteSelectedPhotos() {
var indexPaths = [NSIndexPath]()
for image in self.selectedImages {
let index = self.objects.indexOf({ (photo: AnyObject) -> Bool in
return image == photo as! PFObject
})
let indexPath = NSIndexPath(forItem: index!, inSection: 0)
indexPaths.append(indexPath)
}
self.removeObjectsAtIndexPaths(indexPaths)
}
And this is the delete code for the photo detail screen:
func deleteUserPhoto() {
currentPhoto.deleteInBackgroundWithBlock { (deleted: Bool, error: NSError?) -> Void in
if let error = error {
print("error deleting photo")
print(error.localizedDescription)
} else {
if deleted == true {
self.delegate?.photoDetailVcDidDeletePhoto(self.currentPhoto)
self.navigationController?.popViewControllerAnimated(true)
}
}
}
}
The delegate method near the bottom is called by the PFQueryCollectionViewController so it can loadObjects and update the UI to reflect the deletion.

I think I resolved this. When I was creating new UserPhoto objects, I was adding them to an array on the parent object—an Album—and saving the Album without a completion block. I wasn't actually using the array (instead just querying UserPhoto objects with a pointer to the Album).
When I removed the code to add the photo to the unused array, I was able to update and create UserPhoto objects again. I suspect that the error was referring to the Album object which was not finished saving(?).
Leaving the Album completely out of the process of updating UserPhotos seems to have done it.

Related

Retreiving data with Core Data

I'm working on a project where I should save data locally with Core Data.
Here is my workflow :
To start, I ask the user to fill a form, let's say his firstname and lastname.
He clicks on the submit button, then data is saved on the device using Core Data
User is redirected to the "last filled form" view controller.
I have a bar button item that when clicked can show the latest filled form.
I should test if the array of filled forms is empty, then the button should be disabled.
Otherwise, the button should be enabled ...
I tried this piece of code, where I fetch data from the database and affected to an array but the button seams not working at all and it never gets disabled ...
class ViewController: UIViewController {
var userIdentity: UserIDentity = UserIDentity(context: PersistanceService.context)
var identityArray = [UserIDentity]()
override func viewDidLoad() {
super.viewDidLoad()
self.fetchIdentityHistoryArray()
}
func fetchIdentityHistoryArray(){
let fetchRequest: NSFetchRequest<UserIDentity> = UserIDentity.fetchRequest()
do {
let identityArray = try PersistanceService.context.fetch(fetchRequest)
if identityArray.isEmpty {
self.identityHistoryButton.isEnabled = false
}
else {
self.identityHistoryButton.isEnabled = true
}
}
catch {
print("Error fetching sworn statement history !")
}
}
}
So I have 2 questions :
What's wrong with my code ?
How can I manage that when the user clicks on the "back button" for the first form filled ever, the "history button" can refresh itself and turn from disabled to enabled button ?
Any help is much appreciated. Thank you
You are initialising a core data object directly in your code
var userIdentity: UserIDentity = UserIDentity(context: PersistanceService.context)
This new object will exist in Core Data and will be included everytime you execute the fetch request. You must understand that Core Data is not a database layer it is an object mapping layer so even if you haven't called save() yet the object exists in the Core Data context.
Either change the declaration to
var userIdentity: UserIDentity?
or remove it completely if it isn't used.

UISearchBar: Predictive Search ReloadData crash

In my app I've a searchbar to search 3 different type of items. These 3 items have different data sources so on each text change I've to send 3 diffrent network requests. I show these 3 type of items in 3 different sections in UITableView. When response of any request is received I update the data source and call tableView.reloadData. An example function to get one item (User) can be seen below:-
func searchUsers(withName name: String) {
showNetworkActivity()
UserService.search(query: name) { (data, error) in
Queue.Main.execute({
self.hideNetworkActivity()
if error == nil {
self.searchedUsers = data as! [User]
} else {
self.searchedUsers = []
}
self.searchTableView.reloadData()
})
}
}
But randomly my App is crashed due to index out of bound exception. The crash happens when new data is arrived but tableview is busy in reloading according to previously received data. I tried reloading only related section instead of reloading whole table but it causes more crashes. How can I avoid such crashes?
PS: Queue.Main ensures that all the things are being done on Main thread.

Parse array seems to store locally to my app session

I have a weird bug in my app that I haven't seen before. Basically I have two views - User Profile and Edit Profile. In the User Profile view, I retrieve two things from Parse:
An array of strings telling the order of the user's profile photos (e.g. ["pic1", "pic3", "pic2"])
The photo files themselves (up to 6)
In the Edit Profile View, the user is able to upload new photos as well as rearrange the order array. The view also has a button to close the view and discard any changes as seen here:
#IBAction func cancelTapped(sender: AnyObject) {
self.dismissViewControllerAnimated(true, completion: nil)
}
The problem is that if I make any changes to photos order array such as rearranging it or appending to it, then hit the "Cancel" button, the view dismisses as it should. However, when I go back into the Edit Profile view or even if I close the User Profile then reopen that, both views show the new order array that wasn't supposed to be saved because I hit the "Cancel" button. The only way I can fix is by closing the app and reopening.
Is the array being saved locally to the iPhone? Please let me know if you need any more info or to see more code!
Edit: Forgot to mention, when I look in Parse the values aren't stored on there, but the Xcode logs say the array is changed when reloading the view.
Edit 2: Here is the instantiation of self.photoOrder:
var photoOrder:NSMutableArray = []
currentuser instantiation from Constants.swift
var currentuser = PFUser.currentUser()
I don't know how your reorder method looks like and if your calling the save method on the PFUser object when reording is done. It seems like you're overwritting the photoOrder attribute in your edit profile view somehow.
Solution 1
A simple solution would be to backup the current value when you load the view:
var photoOrderBackup:NSMutableArray = []
// viewDidLoad
self.photoOrder = currentuser.objectForKey("photoOrder") as! NSMutableArray
self.photoOrderBackup = currentuser.objectForKey("photoOrder") as! NSMutableArray
Then you got the chance to apply the backup values when the user cancels the operations.
#IBAction func cancelTapped(sender: AnyObject) {
currentUser.photoOrder = self.photoOrderBackup; // Apply backup value
// Perhaps add the save method here after you've overwritten the photoOrder value
self.dismissViewControllerAnimated(true, completion: nil)
}
Solution 2
If the changes aren't saved to the currentuser object yet, refreshing the object could help to dismiss pending changes.
#IBAction func cancelTapped(sender: AnyObject) {
currentUser.refresh() // Refresh object with local data
self.dismissViewControllerAnimated(true, completion: nil)
}

Dynamically Show UIButton in UITableViewCell

Solved:
Phillip Mills' solution has solved the issue presented in this post, as outlined below.
Currently, when a user searches for a beer in the application, the beer shows up in a UITableViewCell subclass, which displays the name of the beer and the brewery name in UILabel instances. The cell also contains four buttons below the labels: likeButton, tryButton, dislikeButton, and deleteButton.
When a user searches the API database for a beer, the user can then save a beer to a specific category in Core Data by using one of the buttons in the cell. These saved beers will then show up in a UITableView elsewhere in the app. I am able to successfully save and delete beers from the cells, and they do show up in the correct category's UITableView instance. However, if a returned beer result is not saved in Core Data, I want the deleteButton to not be shown in the cell that is populated from the JSON of the API because the app is set up for the user to save a beer, change a beer's category, or delete a beer from the search results UITableView instance.
I have the saving, changing of a beer's category, and deleting of a beer working correctly in both the category UITableView instances and in the search results UITableView. My issue arises when displaying the buttons in the results UITableView.
When results are returned from he API, I only want the deleteButton to be displayed if there is a beer saved in Core Data that matches the returned result. I'm guessing that I need to perform this comparison in cellForRowAtIndexPath:, but I feel like I am going about it incorrectly because I can get the deleteButton to either be visible or be hidden in all cells, regardless of whether the beer is saved in Core Data or not.
Here is my current implementation:
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
if scopeButtonIndex == 0 {
let beerCell = tableView.dequeueReusableCellWithIdentifier("foundBeerResultCell", forIndexPath: indexPath) as! BeerTableViewCell
let beer = foundBeerResults[indexPath.row]
beerCell.beerNameLabel.text = beer.name
beerCell.breweryNameLabel.text = beer.brewery
beerCell.delegate = self
for savedBeer in savedBeers {
if beer.name == savedBeer.beerName && beer.brewery == savedBeer.beerBrewery {
beerCell.deleteButton.hidden = false
} else if beer.name != savedBeer.beerName && beer.brewery != savedBeer.beerBrewery {
beerCell.deleteButton.hidden = true
}
}
return beerCell
}
}
Currently savedBeers in an array of the saved beers in Core Data. As you can see, I am taking each beer that is returned from the API and saved in foundBeerResults, which populates the results UITableView instance. I am then looping through savedBeers to see if each returned beer in foundBeerResults matches the saved beer. If the information matches, I want the deleteButton to be visible so that the user can delete the saved beer directly from the search results. If a returned beer result does not match a currently saved beer, I want the deleteButton to be invisible.
Am I incorrect by assuming that I should not be iterating through arrays in cellForRowAtIndexPath:? It seems inefficient. However, I am not sure of how to solve this problem.
Looping might or might not be a performance problem. You can measure for that but let's assume "not" for the moment since it seems like that would be a fairly small array you're using.
I think your problem is that you're not stopping the loop once you get a right answer.
How about:
beerCell.deleteButton.hidden = true
for savedBeer in savedBeers {
if beer.name == savedBeer.beerName && beer.brewery == savedBeer.beerBrewery {
beerCell.deleteButton.hidden = false
break
}
}

Won't update UILabel from Parse data in swift

I am trying to update my UILabel to a String from my Parse database.
My problem is the label will not update my firstnameLabel when I first sign in. But it WILL update, when i sign in (nothing happens), push the stop button in Xcode and then launch it again (still logged in) and then it updates the label.
How can I do this faster??
Here is my code:
var currentUser = PFUser.currentUser()
if currentUser != nil {
var query = PFQuery(className:"_User")
query.getObjectInBackgroundWithId(currentUser.objectId) {
(bruger: PFObject!, error: NSError!) -> Void in
if error == nil && bruger != nil {
var firstName: String = bruger["firstname"] as String
self.usernameLabel.text = firstName
} else {
println("Error")
}
}
} else {
self.performSegueWithIdentifier("goto_login", sender: self)
}
Hope you can help me!
Rather than trying to load the user object again by the id, try just doing a fetch instead.
[currentUser fetchIfNeededInBackgroundWithBlock: ... ];
By trying to load the object, you might be getting a cached version.
I read somewhere that it could be because of the fact that the could was put inside the ViewDidLoad. I tried to put it outside of that and it worked!
It sounds like you put it in the ViewDidLoad method. You should put it in the ViewWillAppear method instead. Here's an example.
1) ViewDidLoad - Whenever I'm adding controls to a view that should appear together with the view, right away, I put it in the ViewDidLoad method. Basically this method is called whenever the view was loaded into memory. So for example, if my view is a form with 3 labels, I would add the labels here; the view will never exist without those forms.
2) ViewWillAppear: I use ViewWillAppear usually just to update the data on the form. So, for the example above, I would use this to actually load the data from my domain into the form. Creation of UIViews is fairly expensive, and you should avoid as much as possible doing that on the ViewWillAppear method, becuase when this gets called, it means that the iPhone is already ready to show the UIView to the user, and anything heavy you do here will impact performance in a very visible manner (like animations being delayed, etc).
3) ViewDidAppear: Finally, I use the ViewDidAppear to start off new threads to things that would take a long time to execute, like for example doing a webservice call to get extra data for the form above.The good thing is that because the view already exists and is being displayed to the user, you can show a nice "Waiting" message to the user while you get the data.

Resources