Parse array seems to store locally to my app session - ios

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)
}

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.

Segue is not working, and i can't figure out why

I have an if...then statement in the ViewDidLoad method for the view that acts as my storyboard entry point.
Basically, I am doing a check to see if there is any data in core data, to indicate that they've completed a small "setup form".
If it is found that the core data is empty or that the app has not been properly set up, I want it to automatically kick them over to the settings view with a segue.
My ViewDidLoad method looks like this:
override func viewDidLoad() {
super.viewDidLoad()
//Get any entries from the App Settings Entity
getAppSettings()
//If any entries are found, check to see if the setup has been completed
if (appSettings.count > 0) {
print("We found entries in the database for App Settings")
if (appSettings[0].setupComplete == false) {
print("Setup has not been completed")
self.performSegue(withIdentifier: "toAppSettings", sender: self)
} else {
print("Setup is completed")
//Load the settings into global variables
preferredRegion = appSettings[0].region!
usersName = appSettings[0].usersName!
}
} else {
print("We found no entries in the database for App Settings")
self.performSegue(withIdentifier: "toAppSettings", sender: self)
}
}
I made sure that the segue does exist, and that the identifier for the segue is exactly as I have it in the quotes (I even copied & pasted all instances of it to make sure that they are all consistent).
I also went the extra mile and put a checker in the "prepare for segue" method, to print whether it was getting called, and who the sender was:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
print("We're getting ready to segue!")
print(segue.identifier)
}
Both of those items get printed to the log, which tells me that the segue is being recognized and that the app is attempting to fire it. But - for some reason that I can't figure out - it simply isn't firing.
Any ideas what I am missing here?
I have an if...then statement in the ViewDidLoad
But that's the problem. viewDidLoad is way too early for this. Remember, all viewDidLoad means is that your view controller has a view. That's all it means. That view is not yet in the interface, and the view controller itself may not even be in the view hierarchy! It's just sitting out there in object space. You cannot segue from here; there is nothing to segue from.
Try waiting until viewDidAppear. If that makes it work, you might try moving it back to viewWillAppear, but I don't guarantee anything. Keep in mind that these methods can be called multiple times, so you might also need a flag to make sure that this segue fires just the once.

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

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.

How to make an view controller, which displays only when app opens first time?

I use Xcode 6.4
I want to make an viewController which displays a settings and which opens only when the app launch at first time, or when a setting contains nil. What can i do, to do such thing?
Just set a flag to NSUserDefaults...
if (!NSUserDefaults.standardUserDefaults().boolForKey("firstTimeKey")) {
NSUserDefaults.standardUserDefaults().setBool(true, forKey: "firstTimeKey")
NSUserDefaults.standardUserDefaults().synchronize()
showViewController()
}
showViewController() is where you call your ViewController that should be showed once...
I typically solve this problem by creating a CoreData Entity called AppConfig and I give it any important properties to do with Application state, time open, user settings ect...
Upon App Launch if the CoreData entity does not exist, than I create it, and know that its the first launch and that I should display the "FirstLaunch" or "Help" ViewController.
Here is an example of what I'd do:
override func viewDidLoad() {
super.viewDidLoad()
var fetch = NSFetchRequest(entityName: "AppConfig")
var appConfigs = managedObjectContext.executeFetchRequest(fetch, error: error) as! [AppConfig]
if let appConfig = appConfigs.first{
//App has already launched before
}else{
//Create a new AppConfig Object and save it in CoreData
//Present your FirstLaunch Controller
}
return nil
}
In the above code I'm assuming you know the basic concepts of CoreData, and presenting ViewControllers. Both are relatively basic concepts so I'll omit them for now, but if someone does need help with them I can provide more info.

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