I've got a Dictionary that I've created where the key is a String and the value is a custom object called SectorCoordinate.
I just want to store the whole darn thing in NSUserDefaults, but when I do, Xcode says:
The type [String, SectorCoordinate] does not conform to protocol AnyObject
I'm really confused. I thought AnyObject was Swift's version of Objective-C's id and should be able to hold any object.
I've seen and attempted to implement a bunch of solutions that were targeted towards [String: String] dictionaries (e.g. JSON manipulations), but those didn't work, with similar errors. I even tried to break up the key-value-pairs, and I get the same error when trying to store even a single SectorCoordinate (which itself is just a struct with a bunch of Strings, Ints and dates in it) as an AnyObject.
Does anyone have any suggestions on how to store a semi-complex object and/or a dictionary thereof as an AnyObject? It seems like this should be a lot simpler.
The Apple documentation states about NSUserdefaults setObject:forKey: method:
The value parameter can be only property list objects: NSData,
NSString, NSNumber, NSDate, NSArray, or NSDictionary. For NSArray and
NSDictionary objects, their contents must be property list objects.
See “What is a Property List?” in Property List Programming Guide.
Thus, for example, you can cast a Swift Dictionary [String : NSNumber] to a NSDictionary and save/retrieve it with NSUserDefaults just like this:
let dictionary = ["myKey" : NSNumber(int: 12)] as NSDictionary
NSUserDefaults.standardUserDefaults().setObject(dictionary, forKey: "myDict") //[myKey : 12]
NSUserDefaults.standardUserDefaults().dictionaryForKey("myDict") //{[myKey : 12]}
But this is not possible for a Swift Dictionary of type [String : SectorCoordinate] where SectorCoordinate is a Swift Struct.
You can store any object in NSUserDefs, see NSUserDefaults Apple docs:
The NSUserDefaults class provides convenience methods for accessing
common types such as floats, doubles, integers, Booleans, and URLs. A
default object must be a property list, that is, an instance of (or
for collections a combination of instances of): NSData, NSString,
NSNumber, NSDate, NSArray, or NSDictionary. If you want to store any
other type of object, you should typically archive it to create an
instance of NSData. For more details, see Preferences and Settings
Programming Guide.
To have an object that can seamlessly convert to NSData, make sure your class conforms to NSCoding protocol (i.e. so it can be archived and unarchived). Swift also sometimes will want you class to conform to NSSecureCoding as well.
This saves a dictionary containing a home made structure to NSUserDefaults. however if you want to save more than just a little data you should use the file example I have also posted here.
import Cocoa
//The key to the dictionary is in the struct here as permanentTimeDateCode
struct CheckBookEntry{
var permanentTimeDateCode = String() //value here is also the key for the dictorary it must be a string
var amountOfTransaction = Double()
var category = String()
var payee = String()
var memo = String()
var checkNumber = String()
}
var checkBookEntryOne = CheckBookEntry(permanentTimeDateCode: "2015-02--06", amountOfTransaction: 20.00, category: "Properietor", payee: "Balance Forward", memo: "No memo", checkNumber: "00000")
var checkBookEntryTwo = CheckBookEntry(permanentTimeDateCode: "2015-02--05", amountOfTransaction: -15.00, category: "Reference", payee: "Bookstore", memo: "No memo", checkNumber: "00001")
//A dictionary with the date as the key and a CheckBookEntry struct as the value.
var myCheckBookEntry:Dictionary = [String :CheckBookEntry ]()
myCheckBookEntry["2015-02--06"] = checkBookEntryOne
myCheckBookEntry["2015-02--07"] = checkBookEntryTwo
print(myCheckBookEntry)
//To save these set up an array of dictionaries
var checkEntryArrayOfDictionaries:[[String:AnyObject]] = []
//your struct is no an object that can be saved so it needs to be converted.
//use the variable names from our struct CheckBookEntry as the keys
checkEntryArrayOfDictionaries.append( ["permanentTimeDateCode" : checkBookEntryOne.permanentTimeDateCode, "amountOfTransaction" : checkBookEntryOne.amountOfTransaction, "catergory" : checkBookEntryOne.category, "payee" : checkBookEntryOne.payee, "memo" : checkBookEntryOne.memo, "checkNumber": checkBookEntryOne.checkNumber])
checkEntryArrayOfDictionaries.append( ["permanentTimeDateCode" : checkBookEntryTwo.permanentTimeDateCode, "amountOfTransaction" : checkBookEntryTwo.amountOfTransaction, "catergory" : checkBookEntryTwo.category, "payee" : checkBookEntryTwo.payee, "memo" : checkBookEntryTwo.memo, "checkNumber": checkBookEntryTwo.checkNumber])
print("//______________printing checkEntryArrayOfDictionaries----//")
print(checkEntryArrayOfDictionaries)
//Save The values
NSUserDefaults().setObject(checkEntryArrayOfDictionaries, forKey: "aCheckbook")
//recovering the struct
//The dictionary to recover to PLAYGROUND
var myCheckBookEntry2:Dictionary = [String :CheckBookEntry ]()
//A SINGLE INSTANCE OF THE STRUCT TO SAVE EACH TO.
var anIndividualCheckBookEntry = CheckBookEntry()
//RECOVER THE SAVED ENTRY
if let checkEntry2 = NSUserDefaults().arrayForKey("aCheckbook") as? [[String:AnyObject]] {
for key in checkEntry2{
anIndividualCheckBookEntry.permanentTimeDateCode = key["permanentTimeDateCode"]! as! String
anIndividualCheckBookEntry.amountOfTransaction = key["amountOfTransaction"]! as! Double
anIndividualCheckBookEntry.category = key["catergory"]! as! String
anIndividualCheckBookEntry.payee = key["payee"]! as! String
anIndividualCheckBookEntry.memo = key["memo"]! as! String
anIndividualCheckBookEntry.checkNumber = key["checkNumber"]! as! String
//LOAD THIS SINGLE ENTRY INTO OUR NEW DICTIONARY
myCheckBookEntry2[ anIndividualCheckBookEntry.permanentTimeDateCode] = anIndividualCheckBookEntry
}
print("//---------------------------------//")
print(myCheckBookEntry2)
}
I have answered how to use NSUserDefaults elsewhere on this page however if you have any data at all don't use NSUserDefaults. This version saves a array of dictionaries including your own homemade struct to FILE and recovers them. This can be pasted into a playground for testing. In fact, in the NSUserDefaults version a two more check book entries would crash the save in user defaults.
import Cocoa
//The key to the dictionary is in the struct here as permanentTimeDateCode
struct CheckBookEntry{
var permanentTimeDateCode = String() //value here is also the key for the dictorary it must be a string
var amountOfTransaction = Double()
var category = String()
var payee = String()
var memo = String()
var checkNumber = String()
}
var checkBookEntryOne = CheckBookEntry(permanentTimeDateCode: "2015-02--06", amountOfTransaction: 20.00, category: "Properietor", payee: "Balance Forward", memo: "No memo", checkNumber: "00000")
var checkBookEntryTwo = CheckBookEntry(permanentTimeDateCode: "2015-02--05", amountOfTransaction: -15.00, category: "Reference", payee: "Bookstore", memo: "No memo", checkNumber: "00001")
var checkBookEntryThree = CheckBookEntry(permanentTimeDateCode: "2015-02--08", amountOfTransaction: -5.00, category: "Dinning", payee: "Moe's", memo: "Good Eats", checkNumber: "00003")
//A dictionary with the date as the key and a CheckBookEntry struct as the value.
var myCheckBookEntry:Dictionary = [String :CheckBookEntry ]()
myCheckBookEntry["2015-02--06"] = checkBookEntryOne
myCheckBookEntry["2015-02--07"] = checkBookEntryTwo
myCheckBookEntry["2015-02--08"] = checkBookEntryThree
print(myCheckBookEntry)
//To save these set up an array of dictionaries
var checkEntryArrayOfDictionaries:[[String:AnyObject]] = []
//your struct is no an object that can be saved so it needs to be converted.
//use the variable names from our struct CheckBookEntry as the keys
checkEntryArrayOfDictionaries.append( ["permanentTimeDateCode" : checkBookEntryOne.permanentTimeDateCode, "amountOfTransaction" : checkBookEntryOne.amountOfTransaction, "catergory" : checkBookEntryOne.category, "payee" : checkBookEntryOne.payee, "memo" : checkBookEntryOne.memo, "checkNumber": checkBookEntryOne.checkNumber])
checkEntryArrayOfDictionaries.append( ["permanentTimeDateCode" : checkBookEntryTwo.permanentTimeDateCode, "amountOfTransaction" : checkBookEntryTwo.amountOfTransaction, "catergory" : checkBookEntryTwo.category, "payee" : checkBookEntryTwo.payee, "memo" : checkBookEntryTwo.memo, "checkNumber": checkBookEntryTwo.checkNumber])
checkEntryArrayOfDictionaries.append( ["permanentTimeDateCode" : checkBookEntryThree.permanentTimeDateCode, "amountOfTransaction" : checkBookEntryThree.amountOfTransaction, "catergory" : checkBookEntryThree.category, "payee" : checkBookEntryThree.payee, "memo" : checkBookEntryThree.memo, "checkNumber": checkBookEntryThree.checkNumber])
print("//______________printing checkEntryArrayOfDictionaries----//")
print(checkEntryArrayOfDictionaries)
//Save The values
NSUserDefaults().setObject(checkEntryArrayOfDictionaries, forKey: "aCheckbook")
let paths = NSSearchPathForDirectoriesInDomains(NSSearchPathDirectory.DocumentDirectory, NSSearchPathDomainMask.UserDomainMask, true)
let pathLocationString:String = paths[0] as String
let checkbookFile:String = pathLocationString.stringByAppendingString("/aCheckbook")
print(checkbookFile)
if !NSFileManager.defaultManager().fileExistsAtPath(checkbookFile) {
print("files exists or will exist")
NSFileManager.defaultManager().createFileAtPath(checkbookFile, contents: nil, attributes: nil)
}
NSKeyedArchiver.archiveRootObject(checkEntryArrayOfDictionaries,
toFile: checkbookFile)
//The dictionary to recover to PLAYGROUND
var myCheckBookEntry2:Dictionary = [String :CheckBookEntry ]()
//A SINGLE INSTANCE OF THE STRUCT TO SAVE EACH TO.
var anIndividualCheckBookEntry = CheckBookEntry()
let path2 = NSSearchPathForDirectoriesInDomains(NSSearchPathDirectory.DocumentDirectory, NSSearchPathDomainMask.UserDomainMask, true)
let myStringDictionaryArray:String = path2[0] as String
let arrayDictionaryFilePath:String = myStringDictionaryArray.stringByAppendingString("/aCheckbook")
print(arrayDictionaryFilePath)
if NSFileManager.defaultManager().fileExistsAtPath(arrayDictionaryFilePath) {
let dictionaryFileArray =
NSKeyedUnarchiver.unarchiveObjectWithFile(arrayDictionaryFilePath)
as! [Dictionary <String,AnyObject> ]
var x = dictionaryFileArray[0]
var y = dictionaryFileArray[1]
var z = dictionaryFileArray[2]
print("\(x) \(y) \(z)")
var myDictionaryX = x as! [String : AnyObject]
var myDictionaryY = y as! [String : AnyObject]
var myDictionaryZ = z as! [String : AnyObject]
}
print("//---------------------------------//")
Related
Player Object Model
In the Player Model, I want to save the JSON response so that I will get any new computed properties in the future without changing the schema.
But here, I'm getting the error to save the json of type [String: Any].
Any alternative or recommendations...?
Any is not a supported value type of Map. Looking a the documentation for Map, which shows the definition
public final class Map<Key, Value>
value is a RealmCollectionValue can be one of the following types
This can be either an Object subclass or one of the following types:
Bool, Int, Int8, Int16, Int32, Int64, Float, Double, String, Data,
Date, Decimal128, and ObjectId (and their optional versions)
One option is to to use AnyRealmValue so it would look like this
class Player: Object {
let json = Map<String, AnyRealmValue>()
}
here's how to populate the json with a string and an int
let s: AnyRealmValue = .string("Hello")
let i: AnyRealmValue = .int(100)
let p = Player()
p.json["key 0"] = s
p.json["key 1"] = i
then to get back the values stored in the map:
for key in p.json {
let v = key.value
if let s = v.stringValue {
print("it's a string: \(s)")
} else if let i = v.intValue {
print("it's an int: \(i)")
}
}
and the output
it's a string: Hello
it's an int: 100
What's needed to get this code running?
dict.setObject(gReplacements, forKey: "gReplacements")
I created my own plist within a dictionary. I could load and save with different types of data. Only when I try to save my own struct (s. below). I get an error message:
"Cannot invoke "setObject" with an argument of type ('[FromTo], forKey: String)'
Definition:
struct FromTo {
var From = String()
var To = String()
}
var gReplacements : [FromTo] = [ FromTo(From: "X", To: "Y") ]
Any idea?
this code work in playground
var detaildata:Dictionary=[:]
detaildata = ["apple":"hello"]
detaildata["orange"]="byebye"
this code don't work in project
class ViewController: UIViewController{
var detaildata:Dictionary=[:]
override func viewDidLoad() {
super.viewDidLoad()
detaildata = ["apple":"hello"]
detaildata["orange"]="byebye" // Error -> 'Dictionary' is not identical to 'Dictionary<key,Value>'
}
}
do you know why?
I think same code.
That code doesn't work in my playground, and shouldn't work in yours. When you declare a Dictionary, you need to give it both key and value types either explicitly or through type inference. All of these will work:
var dict1 = ["apple": "hello"] // inferred
var dict2: [String: String] = [:] // explicit
var dict3: Dictionary<String, String> = ["apple": "hello"] // longest version
Neither of these will work:
var dict3: Dictionary = [:] // type inference impossible
var dict4 = [:] // same
Is it possible to store closures in dictionaries (how we could store ObjC blocks in dictionaries)? Example:
data = [String:AnyObject]()
data!["so:c0.onSelection"] = {() in
Debug.log(.Debug, message: "Hello, World!")
}
You can, but with some restrictions. First of all, function types don't inherit from AnyObject and don't share a common base class. You can have a dictionary [String: () -> Void] and [String: (String) -> Int], but they can't be stored in the same dictionary.
I also had to use a typealias to define the dictionary so that swift would parse correctly. Here's an example based off of your snippet.
typealias myClosure = () -> Void
var data: [String: myClosure]? = [String: myClosure]()
data!["so:c0.onSelection"] = {() -> Void in
Debug.log(.Debug, message: "Hello, World!")
}
I have a different approach
I create a "holder" class to hold your closures something like this:
typealias SocialDownloadImageClosure = (image : UIImage?, error: NSError?) -> ()
typealias SocialDownloadInformationClosure = (userInfo : NSDictionary?, error: NSError?) -> ()
private class ClosureHolder
{
let imageClosure:SocialDownloadImageClosure?
let infoClosure:SocialDownloadInformationClosure?
init(infoClosure:SocialDownloadInformationClosure)
{
self.infoClosure = infoClosure
}
init(imageClosure:SocialDownloadImageClosure)
{
self.imageClosure = imageClosure
}
}
then i make the dictionary like this:
var requests = Dictionary<String,ClosureHolder>()
Now to add a closure to the dictionary just do this:
self.requests["so:c0.onSelection"] = ClosureHolder(completionHandler)
Connor is correct, I did try many ways to store variables and closures in the same dictionary, but I failed and couldn't parse it out, the swift decompiler will throw the error:
"Command failed due to signal: Segmentation fault: 11" (the hell is it?!)
For example:
//This won't work
var params:[String: Any] = ["x":100, "onFoundX": {println("I found X!")}]
if var onFoundX: (()->Void) = params["onFoundX"] as? (()->Void) {
onFoundX()
}
//This should work by separate into 2 dictionaries and declare the "typealias" obviously
var params:[String: Any] = ["x":"100"}]
var events:[String: (()->Void)] = ["onFoundX": {println("I found X!")]
if var onFoundX: (()->Void) = events["onFoundX"] as? (()->Void) {
onFoundX() // "I found X!"
}
if var x = events["x"] as? String {
println(x) // 100
}
I hope Swift will allow this to happen in the future..
Cheers!
This simple example helped me understand a bit more:
//Init dictionary with types (i.e. String type for key, Closure type for value):
var myDictionary: [String: ()->(String)] = [:]
//Make a closure that matches the closure signature above and assign to variable (i.e. no parameter and returns a String):
let sayHello: () -> (String) = {
return "Hello!"
}
//Add closure to dictionary with key:
myDictionary["myFunc"] = sayHello
//Access closure by known key and call it:
myDictionary["myFunc"]!() //"Hello!"
I have code that sends a notification (where serialNumber is a String):
var dataDict = Dictionary<String, String>()
dataDict["Identity"] = serialNumber
dataDict["Direction"] = "Add"
NSNotificationCenter.defaultCenter().postNotificationName("deviceActivity", object:self, userInfo:dataDict)
And code that receives this notification:
func deviceActivity(notification: NSNotification) {
// This method is invoked when the notification is sent
// The problem is in how to access the Dictionary and pull out the entries
}
I've tried a variety of code to accomplish this, with no success:
let dict = notification.userInfo
let dict: Dictionary<String, String> = notification.userInfo
let dict: Dictionary = notification.userInfo as Dictionary
And while some of my attempts satisfy the compiler, none have yielded actual Strings when trying to access what has been extracted as a Dictionary:
let sn : String = dict["Identity"]!
let sn : String = dict.valueForKey("Identity") as String
let sn : String = dict.valueForKey("Identity")
So the question is this: How do I write Swift code to extract an object, in this case a Dictionary, that was passed via a notification, and access the component parts of that object (in this case the keys and values)?
As notification.userInfo type is AnyObject ayou must downcast it to appropriate dictionary type.
After exact type of dictionary is known you don't need to downcast values you get from it. But you may want to check if values are actually present in dictionary before using them:
// First try to cast user info to expected type
if let info = notification.userInfo as? Dictionary<String,String> {
// Check if value present before using it
if let s = info["Direction"] {
print(s)
}
else {
print("no value for key\n")
}
}
else {
print("wrong userInfo type")
}
You should use structure like [NSObject : AnyObject] and retrieve value as from NSDictionary yourLet[key]
func keyboardWillShown(notification : NSNotification){
let tmp : [NSObject : AnyObject] = notification.userInfo!
let duration : NSNumber = tmp[UIKeyboardAnimationDurationUserInfoKey] as NSNumber
let scalarDuration : Double = duration.doubleValue
}