iOS: how to convert NSUserDefaults stored AnyObject type to array - ios

I have put an array into NSUserDefaults() like so:
NSUserDefaults.standardUserDefaults().setObject(users, forKey: "usersArray")
NSUserDefaults.standardUserDefaults().synchronize()
Then I pull it out like so:
fetchedUserArray = NSUserDefaults.standardUserDefaults().objectForKey("usersArray")
The problem I am facing is that once it is removed from NSUserDefaults it is of type NSArray, preventing me from manipulating it like a Swift array. I have tried this to convert the type, however the compiler does not recognize the variable "castedUsersArray" when it is used later in the code despite not raising any errors upon type casting:
var fetchedArray = NSUserDefaults.standardUserDefaults().objectForKey("usersArray") as? NSArray
var castedUsersArray = fetchedArray as AnyObject as [String]
I have spent a very long time on this with no success. The type constraints of Swift are driving me nuts.
Thank you,
Nick

You almost had it. Don't cast the objectForKey to an Array but rather an Array containing a certain type like you did with castedUsersArray. Don't throw away type information like you did with fetchedArray.
let users = ["Amy", "Bill", "Cindy"]
NSUserDefaults.standardUserDefaults().setObject(users, forKey: "usersArray")
NSUserDefaults.standardUserDefaults().synchronize()
let fetched = NSUserDefaults.standardUserDefaults().objectForKey("usersArray") as? [String] ?? []
The nil coalescing at the end of the line handles the empty NSUserDefaults case.

NSUserDefaults has a specific method to get your stored string arrays called stringArrayForKey:
let stringArray = ["Hello","playground"]
store your string array
NSUserDefaults.standardUserDefaults().setObject(stringArray, forKey: "stringArray")
load it when needed
if let loadedStringArray = NSUserDefaults.standardUserDefaults().stringArrayForKey("stringArray") {
print(loadedStringArray) // ["Hello", "playground"]
}

Related

iOS Swift 3 - Argument labels '(of:)' do not match any available overloads Error

I'm getting the error message Argument labels '(of:)' do not match any available overloads. Below is the code I'm using.
let prefs = UserDefaults.standard
var id: String!
if var array = prefs.string(forKey: "myArray"){
if let index = array.index(of: id) {
array.remove(at: index)
prefs.setValue(array, forKey: "myArray")
}
}
I've seen a lot of answers on Stack Overflow with very similar code to that. So I'm not quite sure why this wouldn't be working.
Basically I'm just trying to remove the element in the array that = id then set that new array to the user defaults.
Update
Just updated the code above to show how array is getting defined. id is a string that is defined in a separate section.
By accessing prefs.string(forKey: "myArray"), you are getting a String, not an array of strings. You should use this:
if var prefs.array(forKey: "myArray") as? [String] { }
or
if var prefs.value(forKey: "myArray") as? [String] { }
Make sure to not forget putting as! [String], because the first method returns [Any], an which can contain objects of any type, not specifically String. Then your error should be solved, because index(of: ) can only be used on Arrays of specified types.
Hope it helps!
Just make an alt + Click on an "array" variable to make sure it is of type Array ([String]), not a String. To apply .index(of:) method it must be an array.
Like this:
String does not have a method .index(of:). That's what the error is pointing at. And sure make a cast to [String]? if it fits.

iOS Swift Updating Dictionary In An Array

I have an array of dictionaries inside a dictionary. I initialize it like this:
var fillups:[NSMutableDictionary] = []
Then I load it like this:
fillups = userDefaults.object(forKey: car) as! NSArray as! [NSMutableDictionary]
Then when I try to update a dictionary element in the array I get the "mutating method sent to immutable object" error. Here's my code to update the record:
let dict=fillups[row]
dict.setValue(odometerField.text, forKey: "odometer")
dict.setValue(gallonsField.text, forKey: "gallons")
fillups[row]=dict
The error occurs in my first setValue line.
Objects that you retrieve from NSUserDefaults are immutable even if they were mutable when they were inserted. You need to take the immutable objects you get from defaults and create mutable versions of them. You also shouldn't force unwrap everywhere if you don't want your app to crash.
if let array = userDefaults.object(forKey: car) as? [NSDictionary] {
fillups = array.map { ($0.mutableCopy() as! NSMutableDictionary) }
}
You also don't need the fillips[row] = dict line since NSMutableDictionary is a reference type and editing the reference you pull out of the array is already editing the one inside the array.
If you want to mutate your dict, you need to declare it with 'var' not with 'let'; 'let' is for constants. Also fix the unwrapping problems pointed out by the comment
let dict=fillups[row]
should be
var dict=fillups[row]

how to don't get a nil value from NSUserDefaults in ViewDidLoad

I have a case where when my viewControler starts in viewDidLoad I have to load some data using NSUserDefaults.standardUserDefaults() which doesn't exist in this monent. This data are saved when I tap send Button in the same viewController and I need this data when I open this viewController again. Now it looks like that:
var orderHistory = [String:String]()
vievDidLoad(){
let userDefault = NSUserDefaults.standardUserDefaults()
let orderHistory = userDefault.objectForKey("orderHistory")
if orderHistory == nil {
self.orderHistory = orderHistory["name":"", "surname":""] as! [String:String]
} else {
self.orderHistory = orderHistory as! [String:String]
{
}// end viewDidLoad
In this moment I recieve an imformation, I have a problem with memory. How should I avoid this situation?
As Leo Dabus said you should try using the ?? nil coalescing operator.
ObjectForKey does not provide a default value because it doesnt know what kind of object it is until you set it the first time. This results in a nil crash if you try to access it value without having it set once.
Compare this to say "boolForKey" where you dont have to do this, because it knows you are dealing with boolean values and therefore defaults to false automatically.
You also dont have to create 2 orderHistory dictionaries, it just makes your code more confusing.
Try this instead
var orderHistory = [String:String]()
vievDidLoad(){
let userDefault = NSUserDefaults.standardUserDefaults()
orderHistory = userDefault.objectForKey("orderHistory") as? [String: String] ?? orderHistory
//than just use the 1 dictionary without the if statements or creating another one.
}// end viewDidLoad
You check if saved data exists (as? [String: String]) and update the dictionary accordingly. If no saved data exists it will use the default values in orderHistory (?? orderHistory), which in your case is an empty dictionary.
This way you dont have to do a nil check, its all done in that one line.
Also try putting your keys into structs or global files so that you avoid typos. I see people not doing this all the time and its really bad practice.
So for example, above your class create a struct
struct Key {
static let orderHistory = "OrderHistory"
}
and use it like so
...objectForKey(Key.orderHistory)
This code makes no sense:
if orderHistory == nil
{
self.orderHistory = orderHistory["name":"", "surname":""] as! [String:String]
}
The if statement guarantees that orderHistory is nil, thereby guaranteeing that the attempt to fetch keys from orderHistory will crash. Actually, that doesn't look like valid Swift. I would expect that line to throw a compiler error.
Are you trying to create a new dictionary?
If so, your code should read like this:
if orderHistory == nil
{
self.orderHistory = ["name":"", "surname":""]
}

Parse: Updating array

I'm trying to upload an array with the following code, but I am getting an error on the second to last line which says Cannot assign to value: function call returns immutable array
for object in objects! {
let newstring = NSString(format: ".0f", self.slider.value)
var newarray = [object.objectForKey("times")]
newarray.append(newstring)
object.objectForKey("times") = newarray
object.saveInBackground()
}
"times" is of type array in parse by the way.
Rather than requesting the array and trying to set it you should be using setObject:forKey: to replace the existing value.
object.setObject(newarray, forKey: "times")
May I suggest using the addObject:forKey: method instead?
for object in objects! {
let newstring = NSString(format: ".0f", self.slider.value)
object.addObject(newstring, forKey: "times")
object.saveInBackground()
}
It is much cleaner and doesn't require retrieval :)

how to save and read dictionary of touples to NSUserDefaults?

I have an dictionary = String: ([(String)], [(Int)], NSDate, Bool, [(String)]) and I attempted to deconstruct it into seperate arrays when then app calls applicationWillTerminate
var codes = [(String)]()
var messages = [[String]]()
var senders = [[Int]]()
var dates = [(NSDate)]()
var bools = [(Bool)]()
var pairs = [[String]]()
for code in self.dictionary.keys {
codes.append(code)
messages.append(self.dictionary[code]!.0)
senders.append(self.dictionary[code]!.1)
dates.append(self.dictionary[code]!.2)
bools.append(self.dictionary[code]!.3)
pairs.append(self.dictionary[code]!.4)
}
self.userDefaultsMessages.setObject(codes, forKey: "userMessagesArrays")
self.userDefaultsSenders.setObject(messages, forKey: "userSentArrays")
self.userDefaultsDates.setObject(senders, forKey: "userDatesArray")
self.userDefaultsDeletedBool.setObject(dates, forKey: "userDeletedArrays")
self.userDefaultsPairs.setObject(bools, forKey: "userPairsArrays")
self.userDefaultsCodeKeys.setObject(pairs, forKey: "userCodesArrays")
self.userDefaultsCodeKeys.synchronize()
self.userDefaultsMessages.synchronize()
self.userDefaultsSenders.synchronize()
self.userDefaultsDates.synchronize()
self.userDefaultsDeletedBool.synchronize()
self.userDefaultsPairs.synchronize()
and then I attempt to pull it all back together when the app calls applicationDidBecomeActive
//read
if let savedCodesArray : AnyObject? = self.userDefaultsCodeKeys.objectForKey("userCodesArrays") {
self.userCodes = savedCodesArray! as! [String]
if let savedMessagesArray : AnyObject? = self.userDefaultsCodeKeys.objectForKey("userMessagesArrays") {
self.Usermessages = savedMessagesArray! as! [[String]]
if let savedSendersArray : AnyObject? = self.userDefaultsCodeKeys.objectForKey("userSentArrays") {
self.Usersenders = savedSendersArray! as! [[Int]]
if let savedDatesArray : AnyObject? = self.userDefaultsCodeKeys.objectForKey("userDatesArray") {
self.Userdates = savedDatesArray! as! [NSDate]
if let savedBools : AnyObject? = self.userDefaultsCodeKeys.objectForKey("userDeletedArrays") {
self.Userbools = savedBools! as! [Bool]
if let savedPairs : AnyObject? = self.userDefaultsCodeKeys.objectForKey("userPairsArrays") {
self.Userpairs = savedPairs! as! [[String]]
var indexPath: Int = 0
for code in self.userCodes {
self.dictionary[code]! = [self.Usermessages[indexPath], self.Usermessages[indexPath], self.Userdates[indexPath], self.Userbools[indexPath], self.Userpairs[indexPath]]
}
}
}
}
}
}
}
I am fairly new to iOS development and could use help, how would i save the single dictionary = String: ([(String)], [(Int)], NSDate, Bool, [(String)]) to NSUserDefaults and then later read it.. the documentation was not very helpful since it only worked with simple dictionaries
the code looks incredibly cumbersome so I know I can't be doing it right. It should be a simple solution since I only have one variable to save to NSUserDefaults.
Short answer: You can't.
NSUserDefaults will only record "property list objects" (dictionaries, arrays, strings, numbers (integer and float), dates, binary data, and Boolean values).
You can't save any other types of data into NSUserDefaults, or into a property list. The only solution is to convert other data types into those types.
Tuples are not one of those types, so they can't be saved into user defaults.
First, your interaction with NSUserDefaults is completely wrong. You only need to interact with the shared NSUserDefaults.sharedUserDefaults() instance, not create separate instances, which seems to be what you're doing (i.e. userDefaultsMessages, userDefaultsSenders, etc.).
Second, you don't need to call synchronize() at all. There are very few conditions under which you need to call it manually, and this isn't one of them.
Third, the easiest way to store a particular tuple in NSUserDefaults is to convert it into an Array (or NSArray) and store the result. Of course, this assumes that the tuple contains only types that can be serialized, which your example seems to contain. Unfortunately there's no general solution to this, but creating an array from a tuple is straight forward, as you can just map the tuple indices to array indices.
Finally, such large tuples are usually the result of poor design somewhere along the line. Perhaps refactoring would help resolve your storage issue?

Resources