What's wrong with my UserDefault code for global Array? - swift

Trying to store an array through UserDefault, but Xcode gives me an error. The error message is Thread 1: Signal SIGABRT, and the console says "NSInvalidArgumentException, reason: Attempt to insert non-property list object". I have previously stored data in the array using this code:
let tempRecipe = GlobalFavorites(recipeImageObject: "", recipeTextObject: "", recipeHeaderObject: "", favoriteRecipeArray: [globalFavoriteRecipes])
tempRecipe.recipeHeaderObject = self.recipeClassArray[self.currentView].recipeHeaderObject
tempRecipe.recipeTextObject = self.recipeClassArray[self.currentView].recipeTextObject
tempRecipe.recipeImageObject = self.recipeClassArray[self.currentView].recipeImageObject
globalFavoriteRecipes.favoriteRecipeArray.append(tempRecipe)
And that works fine. Here's the code for storing with UserDefault that gives me the error:
UserDefaults.standard.setValue(globalFavoriteRecipes.favoriteRecipeArray, forKey: "savedFavoriteArray")
It's a global array and I want to store the whole array. I guess it has to do with how I write the array in UserDefault, because to me it seems that I'm trying to store something that's not there. Or what am I missing?

If globalFavoriteRecipes.favoriteRecipeArray is an array of custom objects, you need to make sure you are archiving and unarchiving them properly. Refer to this StackOverflow post.
That post also touches on some other options, as NSUserDefaults isn't the best place to store an array that can potentially grow pretty large, which it sounds like it could in this case based on the variable name.

Use set not setValue:
UserDefaults.standard.set(globalFavoriteRecipes.favoriteRecipeArray, forKey: "savedFavoriteArray")
But, I agree with #Jake, this should only be used to store small amounts of data, not a large array. Happy coding!

Related

CKAsset in server record contains no fileURL, cannot even check for nil

I am testing a sync conflict when I save a record that contains a CKAsset (simply a JPG image) using CKModifyRecordsOperation with a save policy of .IfServerRecordUnchanged. I am getting the error CKErrorCode.ServerRecordChanged. This CKError returns me useful information for conflict resolution, including the CKRecord I tried to save, and the current server version of the record. The first is in error.userInfo[CKRecordChangedErrorClientRecordKey] the second is in error.userInfo[CKRecordChangedErrorServerRecordKey].
My problem is I am trying to access the server record's CKAsset with this code:
if let photoAsset = rec["myPhoto"] as? CKAsset {
print("PhotoAsset.fileURL: \(photoAsset.fileURL)") // BAD_ACCESS ERROR HERE
self.myPartner.photo = NSData(contentsOfURL: photoAsset.fileURL)
}
I don't get how this is possible. But after further investigating, I print out the client and server CKRecords and the server one is missing the 'path' property.
client CKAsset...myPhoto (modified) -> CKAsset: 0x7b960d90; path=~/tmp/BF185B2C-7A39-4730-9530-9797E843243Aphoto, size=373959, uploadRank=0, uploadReceipt=A92Eg1qoyPG7yrg3, UUID=3C2D5DC8-4FF5-4A81-853B-395FC1C59862, referenceSignature=<012fd149 200fc600 617e3907 88763e3e 5002abbf 5b>, flags=uploaded, wrappedEncryptionKey=, signature=<0134a297 38d52f5f 9275bfba fce5b1a8 3d6b9692 d3>
server CKAsset...myPhoto = CKAsset: 0x7be700d0; referenceSignature=<015337bd 84409893 7c014f46 36248d27 ce911dc3 7a>, size=373959, uploadRank=0, UUID=DF5D2EB4-033C-49A2-AF52-6055B5A44106, wrappedEncryptionKey=<767e7cfd d1e62110 32119ee9 f6f026b3 5bcf0cc3 8053a4de>, signature=<0134a297 38d52f5f 9275bfba fce5b1a8 3d6b9692 d3>
Notice how path=~/tmp/C706423B-A3E8-4051-A9B3-483C718BFBF5photo is missing from the server one? Can anyone explain this? To fix it I try to avoid touching the CKAsset from the server record. I would like to at least be able to check for nil. I wanted to put this out there in case it helps anyone else.
Due to the crash on accessing fileURL, this is most likely a framework bug. Probably an oversight on account of the CKRecord being buried in a dictionary. I just follow it up with a regular fetch(withRecordID:).
I'm experiencing this issue as well on iOS 11.2.1 when accessing CKAsset from serverRecord property in CKRecord. It's a little bit frustrating. A workaround is fetching the object once again via func fetch(withRecordID... and then access fileURL.
This seems like the correct behavior, not a bug.
CloudKit informed you that your write operation failed because you weren't working from the latest CKRecord and gave you a non-hydrated version of the server's current CKRecord so you could determine which fields were different from your starting point. The rest is up to you.
If CloudKit returned the fully hydrated server record in the error response for a write operation, it would potentially waste enormous amounts of bandwidth/resources.
That is why CKAssets exist: to separate the size-constrained key-value fields associated with a CKRecord from the unlimited-size binary assets that can be attached to them.

How do you save multiple fields to one row or objectId in Parse using Swift?

I'm getting the above layout from Parse. What I want is vid 1, 2, and 3 to be in the same row; associated with same object ID. How can I do this? My ultimate goal is to easily retrieve 10 video dictionary's per user on a table view. Will any of this make a difference? I'm saving like this.....
videoDict = ["id":videoId, "title":vidTitleText, "description":vidDescription, "image":vidIMG]
let videoSave = PFObject(className:"UserVideos")
videoSave["user"] = PFUser.currentUser()!.username
videoSave["userObjectId"] = PFUser.currentUser()!.objectId
videoSave["vid\(saveValueLBL.text!)"] = videoDict
videoSave.saveInBackgroundWithBlock { (success, error ) -> Void in
if success == true
{
print("Succesfull")
}
}
Where you have let videoSave = PFObject(className:"UserVideos") you are creating a new videoSave object each time. you need to move that outside of your loop so that you're accessing the same object each time instead of making a new one. However, the way you currently have your code set up you'll run into problems, because each object can only have one synchronous action called on it (in this case, your save), so the second, third, maybe even all the way to the 10th save may not occur because it needs the first one to finish before the next one can be called. You need to create your object outside your loop, run the loop, then call the save at the end to make sure it isn't saving until all of the data is updated.
If this isn't all inside of a loop, you need to get the videoSave object back each time, perhaps by storing it onto your user, and then fetching it from the user object.
Put everything outside the loop and keep just the code below inside the loop:
videoDict = ["id":videoId, "title":vidTitleText, "description":vidDescription, "image":vidIMG]
videoSave["vid\(saveValueLBL.text!)"] = videoDict
From what I understand although I saved information in Parse as a Dictionary this is in fact an invalid data type. That's why I'm having trouble retrieving because Parse doesn't recognize the info.

How to get object for key from NSDictionary?

In dictionary named dict the key value pair is:
"alba\U2019s window" = "A model in westindies.";
And to send objectForKey: I am getting the string like "alba's window". When I send like following:
[dict objectForKey:alba's window];
I am not getting anything, it is showing null.
For starters, you might want to make that an actual string, like so:
[dict objectForKey:#"alba's window"];
Note also that \U2019 is not '; but ’, which is a different character entirely.
And more generally, sticking to [a-zA-Z0-9]+ for dictionary keys is probably a good idea, unless you are inserting and retrieving programmatically using the exact same string as a key.
Since iOS6 onwards, a convenient method for setting and accessing the object for a key from an NSDictionary is:
//Setting the object in NSMutableDictionary
dictionary[#"someKey"] = #"someString";
//Accessing the object
NSString *str = dictionary[#"someKey"];
Make sure your dict isn't null; sending a message to a null object will silently fail and return null.
Another way to check your key/values is to simply NSLog(#"%#",dict); and this will show you the contents of the dictionary. Note that this output only shows quotes around values when the value contains a space.
Also, make sure you're using the same pairs of strings as the key - it looks like you're using "alba\U2019s window" in addition to "alba's window".

Removing a NSData stored in CoreData

I saved a NSData (of UIImage) in an attribute of CoreData.
There are still some other attributes under the same entity.
How can I remove just the NSData stored, but not removing the whole NSManagedObject?
I tried overwriting it to nil, but the size of DB did not change at all, which means the NSData is not removed?
It means the amount of space used to store some value for that attribute is still there, but it does not mean that you did not successfully obliterate the value. Did you use [managedObjectContext save:&error] after writing to the value? If so and it reported no error then the value is gone.
(Does the size of the store really increase when you set an attribute? I expect it would when you add an entity but not just for setting an attribute.)
if you are using tableview to display your CoreData and edit current indexPath in an new VC, try YOURCOREDATA.setNilValueForKey("YOUR IMAGE KEY").
i'm just a newbee of iOS dev but it works fine to me at least.

Saving Array to PList from downloaded content in table view on iPhone

I copied these posts from a forum where I posted recently, but got no reply.
I have the base for my code all setup, it is a drill down table with navigation controller which loads data into a nsmutabledictionary from a plist with some empty values, on purpose. On command the app then downloads information which is then set, setValue, in NSMutableArray. The issue is that this NSMutableArray is 3 levels down from the root of the plist. I am looking for a way to replace the array in the plist which is loaded and saved in the appdelegate. How do I this? Help is much appreciated.
So that probably didn't make much sense. In the AppDelegate it loads a plist file into an NSMutableDictionary. The plist is like this:
Root
>Title(String)
>Rows(Array)
>>Item 1(Dictionary)
>>>Title(String)
>>>ID(String)
>>>ItemList(Array)
>>>>Item 1(Dictionary)
>>>>>Title(String)
>>>>>ID(String)
>>>>>Value(String)-----------Empty Value which is Downloaded
>>>>Item 2(Dictionary)
>>>>Item 3(Dictionary)
>>>>Item 4(Dictionary)
>>Item 2(Array)
>>Item 3(Array)
>>Item 4(Array)
The Rows array is sent to the RootViewController. When one of these items is selected from the TableView and new instance is created with the table data source set to the ItemList of that selected item. When the user presses a button it downloads the values and using [setValue: forKey:#"Value"] the values are set in the array. Here is where it gets lost. I need to now set this ItemList array back into the main Root dictionary and then that Root dictionary sent back to the AppDelegate, so that on applicationWillTerminate [self.data writeToFile:] can be called and the downloaded information does not need to be downloaded the next time.
I'm not totally sure I understanad what your problem is. Once you've got the new value populated into the array, you just need to use setObject:forKey: on the dictionary to replace the old array.
If you're having trouble getting a reference to the app delegate, you can use [[UIApplication sharedApplication] delegate]
if the problem is that your dictionaries and arrays are nested such that you need to change more than one object to set the value, consider using mutableCopy to produce mutable arrays and dictionaries from the plist on load. Alternatively, maybe think about using a mire-sophisticated data representation, rather than reading and writing plist format.
I believe I might have to use forKeyPath