Store dictionary in UserDefaults using #Published - swift

I have this:
#Published var mealsAndTime: [String: Date] = UserDefaults.standard.dictionary(forKey: "mealsAndTime") {
didSet {
UserDefaults.standard.set(self.mealsAndTime, forKey: "mealsAndTime")
}
}
and it's telling me:
Cannot convert value of type '[String : Any]' to expected argument type '[String : Date]'
What's wrong here?
Thanks

Casting... UserDefaults does not know about internal values, so type is Any. You have to cast explicitly, as you know your types, but safely, just in case.
So solution is
#Published var mealsAndTime: [String: Date] =
UserDefaults.standard.dictionary(forKey: "mealsAndTime") as? [String: Date] ?? [:] {
didSet {
UserDefaults.standard.set(self.mealsAndTime, forKey: "mealsAndTime")
}
}

Related

Cannot downcast object of type Any to Int when accessing from dictionary

I have a Gfycat struct that represents the data I want to store after making a network call to the Gfycat API.
typealias JSONDictionary = [String: Any]
struct Gfycat {
let id: String
let number: Int
}
In an extension to the Gfycat struct, I wrote a failable initializer that takes a dictionary of type [String: Any] as its argument. This dictionary is then used to assign values to the struct's properties. This is the original init method I wrote:
extension Gfycat {
init?(dictionary: JSONDictionary) {
guard let id = dictionary["gfyId"] as? String,
let number = dictionary["gfyNumber"] as? Int { return nil }
self.id = id
self.number = number
}
}
The problem is that when accessing a value from the dictionary, I cannot downcast the value from Any to Int. I must first downcast Any to String, then convert that string to Int. Is this a bug or rather a feature of Swift that I don't understand?
This was my solution:
extension Gfycat {
init?(dictionary: JSONDictionary) {
guard let id = dictionary["gfyId"] as? String,
let uncastedNumber = dictionary["gfyNumber"] as? String,
let number = Int(uncastedNumber) else { return nil }
self.id = id
self.number = number
}
}
I must first downcast Any to String, then convert that string to Int. Is this a bug or rather a feature of Swift that I don't understand?
It's neither a bug nor a feature of Swift. It's a fact about the dictionary you're working with. This thing is a String, not an Int. So you cannot cast it to an Int.

Swift: Cannot convert value of type '[String : DayData]' to expected argument type 'AnyObject?'

I'm having trouble saving to CoreData when trying to save a new struct I created.
var allInformationByDate = [
"2016-08-13": DayData(sales: 0, doorsKnocked: 0, milesWalked: 0.00, hoursWorked: 0.00)
]
struct DayData {
let sales: Int
let doorsKnocked: Int
let milesWalked: Double
let hoursWorked: Double
}
I'm getting the error:
Cannot convert value of type '[String : DayData]' to expected argument type 'AnyObject?'
on this chunk of code, specifically on "allInformationByDate"...
var allInfoByDateDefault = NSUserDefaults.standardUserDefaults()
allInfoByDateDefault.setValue(allInformationByDate, forKey:"allInfoByDateRecord")
allInfoByDateDefault.synchronize()
Does anybody know how to fix this syntax? I've tried changing to
allInformationByDate as! AnyObject
but that just makes the app crash.
The issue is that you cannot save custom objects in NSUserDefaults
But since all properties of DayData are property list compliant you could write an extension of NSUserDefaults to convert DayData to a dictionary and vice versa.
This extension can write a single DayData object as well as a dictionary [String:DayData].
extension NSUserDefaults {
// read a write a single 'DayData' object
func dayDataForKey(key: String) -> DayData? {
guard let data = self.objectForKey(key) as? [String:AnyObject] else { return nil }
return DayData(sales: data["sales"] as! Int, doorsKnocked: data["doorsKnocked"] as! Int, milesWalked: data["milesWalked"] as! Double, hoursWorked: data["hoursWorked"] as! Double)
}
func setDayData(dayData : DayData, forKey key: String) {
let propertyListRepresentation = ["sales": dayData.sales, "doorsKnocked" : dayData.doorsKnocked, "milesWalked": dayData.milesWalked, "hoursWorked": dayData.hoursWorked]
self.setObject(propertyListRepresentation, forKey: key)
}
// read a write a dictionary ('[String:DayData]') object
func dayDataDictionaryForKey(key: String) -> [String: DayData]? {
guard let dayData = self.objectForKey(key) as? [String : [String: AnyObject]] else { return nil }
var result = [String: DayData]()
for (key, value) in dayData {
result[key] = DayData(sales: value["sales"] as! Int, doorsKnocked: value["doorsKnocked"] as! Int, milesWalked: value["milesWalked"] as! Double, hoursWorked: value["hoursWorked"] as! Double)
}
return result
}
func setDayDataDictionary(dayData : [String: DayData], forKey key: String) {
var result = [String : [String: AnyObject]]()
for (key, value) in dayData {
result[key] = ["sales": value.sales, "doorsKnocked" : value.doorsKnocked, "milesWalked": value.milesWalked, "hoursWorked": value.hoursWorked]
}
self.setObject(result, forKey: key)
}
}
Now you can easily write a dictionary to user defaults:
let defaults = NSUserDefaults.standardUserDefaults()
defaults.setDayDataDictionary(allInformationByDate, forKey: "allInfoByDateRecord")
or read it
let defaults = NSUserDefaults.standardUserDefaults()
if let infoByDateDefault = defaults.dayDataDictionaryForKey("allInfoByDateRecord") {
allInformationByDate = infoByDateDefault
}
You shouldn't use the setValue method. To set a dictionary, call the setObject method.
Documentation:
Sets the value of the specified default key in the standard application domain.
The value parameter can be only property list objects: NSData, NSString, NSNumber, NSDate, NSArray, or NSDictionary. For NSArray and NSDictionary objects...
Also, it's very inconvenient to use Swift structs with Objective-C APIs. You need to make your struct conform to NSCoding and convert it to NSData before storing it to NSUserDefaults. It's basically a whole lot of mess.
You can search for tutorials that show you how to conform to NSCoding, but I don't really recommend using it.
NSUserDefaults is usually used to save simple data structures, like integers, floats, bools. This is because, after all, it is designed for you to save user preferences and related stuff.
Seeing you have a struct like this, I recommend you to use Core Data instead of NSUserDefaults. You can create a data model and generate an NSManagedObject subclass based on the struct:
class DayData: NSManagedObject {
#NSManaged var sales: NSNumber?
#NSManaged var doorsKnocked: NSNumber?
#NSManaged var milesWalked: NSNumber?
#NSManaged var hoursWorked: NSNumber?
}
Trust me, Core Data is much more convenient in these kinds of circumstances.
For details, take a look at this tutorial.

Store dictionary with optional values [String: AnyObject?] in NSUserDefaults

I need to store a dictionary that which can contain a nil as a value
Example
var someOptionalVar: String? = nil
var dict: [String: AnyObject?] = [
"someOptionalVar": self.someOptionalVar
]
defaults.setObject(dict, forKey: self.nsUserDefaultsKey)
But it gives me this error
Cannot convert value of type '[String: AnyObject?]' to expected
argument type 'AnyObject?'
I know I could leave the nil variables and then when I'm parsing the dictionary from NSUserDefaults I would set variables (corresponding to the missing properties) to nil, but this is not what I would like to do.
So how can I store nil values in NSUserDefaults ?
Use NSNull() instead of nil, and declare the dictionary to contain only non-optionals:
var someOptionalVar: String? = nil
var dict: [String: AnyObject] = [
"someOptionalVar": self.someOptionalVar ?? NSNull()
]
defaults.setObject(dict, forKey: self.nsUserDefaultsKey)

Cannot convert value of type '[NSObject : AnyObject]' to expected argument type '[String : AnyObject]'

Xcode7 and swift, My code:
func loadDefaults() {
let settingBundle = NSBundle.mainBundle().pathForResource("Settings", ofType: "bundle")
if settingBundle == nil {
return
}
let root = NSDictionary(contentsOfFile: settingBundle!.stringByAppendingString("Root.plist"))
let prefrences = root?.objectForKey("PreferenceSpecifiers") as! Array<NSDictionary>
let defautlsToRegister = NSMutableDictionary(capacity: root!.count)
for prefrence in prefrences {
let key = prefrence.objectForKey("Key") as! String!
if key != nil {
defautlsToRegister.setValue(prefrence.objectForKey("DefaultVale"), forKey: key!)
}
}
NSUserDefaults.standardUserDefaults().registerDefaults(defautlsToRegister as [NSObject: AnyObject])
}
Problem code:
NSUserDefaults.standardUserDefaults().registerDefaults(defautlsToRegister as [NSObject: AnyObject])
building warnings
Cannot convert value of type '[NSObject : AnyObject]' to expected argument type '[String : AnyObject]'
change code:
NSUserDefaults.standardUserDefaults().registerDefaults(defautlsToRegister as [String: AnyObject])
building warnings
'NSMutableDictionary' is not convertible to '[String : AnyObject]'
Please teach me how to do? thanks.
Your defautlsToRegister should be in the following format [String: AnyObject]
Example: The following should work without warning
let defautlsToRegister = ["Test":10]
NSUserDefaults.standardUserDefaults().registerDefaults(defautlsToRegister as [String: AnyObject])
I've noticed a simple thing about this error. I'm not sure if this the case but casting String to NSString seems to solve the problem for me. I found an explanation that AnyObject is a type alias to represent instances of any reference type, which is for example: NSString. But String is a struct so it can't be the reference type for AnyObject.
I see two ways for this:
First:
let keyForMyKey: NSString = NSString(string: "mykey")
let result = dict.objectForKey(keyForMyKey) as? NSMutableArray
Second:
let result = dict.objectForKey(NSString(string: "myKey")) as? NSMUtableArray
More on the problem here: http://drewag.me/posts/swift-s-weird-handling-of-basic-value-types-and-anyobject
For me this is worked.
let token = fields["X-AUTH-TOKEN"]! as? [[String : AnyObject]] //{
// safe to use employees
self.bindings.setObject(NSString(format: "%#", token!) as String, forKey: "X-AUTH-TOKEN")
Format(NSString method) will work in this scenario.

Assign Value of NSNumber to AnyObject

I have a segment of code that gets info from an API, and I need to add it to a Dictionary. The code is below:
typealias JSONdic = [String: AnyObject]
var weatherData: AnyObject = StorageManager.getValue(StorageManager.StorageKeys.WeatherData)!
let json: AnyObject = ["Any": "Object"]
if let json = json as? JSONdic, history = json["history"] as? JSONdic, tempi = history["tempi"] as? Int, hum = history["hum"] as? String, precip = history["precipi"] as? String{
println("Temperature:\(tempi) Humidity:\(hum) Precipitation:\(precip)")
weatherData = [NSDate: AnyObject]()
let temp = tempi as NSNumber
weatherData[(The Current Date)] = temp
}
I want to first add "temp" to the weatherData Dictionary, but even after casting it to NSNumber, I am told that an NSNumber value cannot be assigned to the AnyObject?! type. Can anyone help me fix this?
Your weatherData variable is of type AnyObject. Despite the fact that you later assign it a value of type [NSDate: AnyObject], the variable itself is still considered by the compiler to be AnyObject. You then hit problems because you try to subscript it, assigning an NSNumber, which is obviously not possible on AnyObject.
Your declaration of weatherData should ensure it is the type you intend. If you are sure that your StorageManager will return you the appropriate dictionary type for the weather data key, you can force downcast it to the correct type:
var weatherData = StorageManager.getValue(StorageManager.StorageKeys.WeatherData) as! [NSDate: NSObject]