1

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.

Marcus Rossel
  • 3,196
  • 1
  • 26
  • 41
Ben Carroll
  • 138
  • 2
  • 11
  • What is the type of `SectorCoordinate`? – Imanou Petit Aug 23 '14 at 15:53
  • 1
    The NSUserDefaults documentation explicitly mentions that you can store an instance of (or a combination of instances of): NSData, NSString, NSNumber, NSDate, NSArray, or NSDictionary. Everything else must be archived. – Martin R Aug 23 '14 at 15:59
  • possible duplicate of [How to store custom objects in NSUserDefaults](http://stackoverflow.com/questions/2315948/how-to-store-custom-objects-in-nsuserdefaults) – Martin R Aug 23 '14 at 16:02
  • possible duplicate of [How to save in NSUserDefaults a array of object?](http://stackoverflow.com/questions/25229478/how-to-save-in-nsuserdefaults-a-array-of-object) – Martin R Aug 23 '14 at 16:04
  • 1
    When all else fails, but before posting to SO, read the documentation. – zaph Aug 23 '14 at 16:08
  • Thanks for your help and patience all. – Ben Carroll Aug 23 '14 at 21:33

4 Answers4

2

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.

Imanou Petit
  • 89,880
  • 29
  • 256
  • 218
  • 1
    Gotcha, thanks. I guess I let the other information about NSUserDefaults flow out of my brain when I read that AnyObject could be ANY object. Silly me, I thought Apple had figured out some awesome way of serializing any object for generic storage. – Ben Carroll Aug 23 '14 at 21:36
2

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.

Tal Brown
  • 126
  • 6
1

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("//---------------------------------//")
Mountain Man
  • 252
  • 2
  • 10
1

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)

}
Mountain Man
  • 252
  • 2
  • 10