I have an custom struct called 'News' that I want to append into the array to NSUserDefault. But it's showing error "Type 'News' does not conform to protocol 'AnyObject'".
I don't want to change the 'News' struct to a class since it's being used for other code already. Is there anyway that I can change NSUserDefaults.standardUserDefaults().arrayForKey("savedNewsArray") type to [News]?
var savedNews = NSUserDefaults.standardUserDefaults().arrayForKey("savedNewsArray")
var addSavedNews = savedNews as? [News]
addSavedNews.append(News(id: "00", title: newsTitle, source: source, imageURL: imageURL, url: url))
NSUserDefaults.standardUserDefaults().setObject(addSavedNews, forKey: "savedNewsArray")
NSUserDefaults.standardUserDefaults().synchronize()
Here is the 'News' struct.
public struct News {
public var id: String
public var title: String
public var source: String?
public var imageURL: String?
public var date: NSDate?
public var url: String
init(id: String, title: String, source: String, imageURL: String, url: String) {
self.id = id
self.title = title
self.source = source
self.imageURL = imageURL
self.url = url
}
}
NSUserDefaults can only save a very small set of types: NSData
, NSString
, NSNumber
, NSDate
, NSArray
containing only these types, or NSDictionary
containing only these types. So your best bet is to encode your struct using an NSKeyedUnarchiver, which required a value that conforms to NSCoding. You could make your type conform to this, but I think it's cleaner to hide that from your users and simply have a private class for the internal representation, like this:
struct Foo {
var a : String
var b : String?
}
extension Foo {
init?(data: NSData) {
if let coding = NSKeyedUnarchiver.unarchiveObjectWithData(data) as? Encoding {
a = coding.a as String
b = coding.b as String?
} else {
return nil
}
}
func encode() -> NSData {
return NSKeyedArchiver.archivedDataWithRootObject(Encoding(self))
}
private class Encoding: NSObject, NSCoding {
let a : NSString
let b : NSString?
init(_ foo: Foo) {
a = foo.a
b = foo.b
}
@objc required init?(coder aDecoder: NSCoder) {
if let a = aDecoder.decodeObjectForKey("a") as? NSString {
self.a = a
} else {
return nil
}
b = aDecoder.decodeObjectForKey("b") as? NSString
}
@objc func encodeWithCoder(aCoder: NSCoder) {
aCoder.encodeObject(a, forKey: "a")
aCoder.encodeObject(b, forKey: "b")
}
}
}
Then to save your array you can simply map .encode
over your array:
let fooArray = [ Foo(a: "a", b: "b"), Foo(a: "c", b: nil) ]
let encoded = fooArray.map { $0.encode() }
NSUserDefaults.standardUserDefaults().setObject(encoded, forKey: "my-key")
and to get it back you can simply pass the NSData
to the init:
let dataArray = NSUserDefaults.standardUserDefaults().objectForKey("my-key") as! [NSData]
let savedFoo = dataArray.map { Foo(data: $0)! }
Following is the flow that I have follow
- Make your struct object
- Convert struct to Json object (I will give you file
link to convert struct to JsonObject and jsonString -> https://github.com/vijayvir/SwiftApi/blob/master/StructureToJson/StructToJson.swift ) import this file in your project :-)
- Append that value to array
If you want to save in user default I prefer you to make class object of array as follow ,(as you can access from any where )
save your array in user default as follow
Struct to save
struct CardDetails : StructJSONSerializable {
var emailId :String
var accountNo : String
var expDate : String
var cvc : String
var isCurrrent : Bool = false
init(card : Dictionary<String, Any> )
{
emailId = card["emailId"]! as! String
accountNo = card["accountNo"]! as! String
expDate = card["expDate"]! as! String
cvc = card["cvc"]! as! String
isCurrrent = card["isCurrrent"]! as! Bool
}
}
Save Array to User Default
class var cards : [AnyObject] {
get
{
if (UserDefaults.standard.object(forKey:"cardss") != nil)
{
if let data = UserDefaults.standard.object(forKey: "cardss") as? Data
{
let unarc = NSKeyedUnarchiver(forReadingWith: data)
let newBlog = unarc.decodeObject(forKey: "root")
return newBlog as! [AnyObject]
}
else
{
return []
}
}
else
{
return []
}
}
set
{
//print(type(of: newValue) , newValue)
let archiveData = NSKeyedArchiver.archivedData(withRootObject: newValue)
let ud = UserDefaults.standard
ud.set( archiveData ,forKey: "cardss")
ud.synchronize()
}
}
How to save Struct element to array
Here convert you struct object to json Object
let firstCard = CardDetails(emailId: "asdf",
accountNo: "dada",
expDate: "nvn",
cvc: "e464w",
isCurrrent: true)
CardsViewController.cards.append(firstCard.toJsonObect())
How to access object from array
Here convert you json Object object to Struct
let sameCardIs = CardDetails(card: CardsViewController.cards.last
as! Dictionary<String, Any>)
print ("Element Function " , sameCardIs )
:-)
WORKING IN SWIFT 3
Following a similar approach to ahruss, I ended up implementing in the following way:
struct MyObject {
var foo : String
var bar : String?
}
extension MyObject {
init?(data: NSData) {
if let coding = NSKeyedUnarchiver.unarchiveObject(with: data as Data) as? Encoding {
foo = coding.foo as String
bar = coding.bar as String?
} else {
return nil
}
}
func encode() -> NSData {
return NSKeyedArchiver.archivedData(withRootObject: Encoding(self)) as NSData
}
private class Encoding: NSObject, NSCoding {
let foo : NSString
let bar : NSString?
init(_ myObject: MyObject) {
foo = myObject.foo
bar = myObject.bar
}
@objc required init?(coder aDecoder: NSCoder) {
if let foo = aDecoder.decodeObject(forKey: "foo") as? NSString {
self.foo = foo
} else {
return nil
}
bar = aDecoder.decodeObject(forKey: "bar") as? NSString
}
@objc func encode(with aCoder: NSCoder) {
aCoder.encode(foo, forKey: "foo")
aCoder.encode(bar, forKey: "bar")
}
}
}
Oh, and to use it just type the following:
let myObject = MyObject(foo: "foo", bar: "bar")
UserDefaults.standard.set(myObject.encode(), forKey: "test")
To fetch it,
let objectData = UserDefaults.standard.object(forKey: "test") as? Data?
guard let storedObject = objectData else {
return
}
let fetchedObject: MyObject = MyObject(data: storedObject as NSData)