How can I insert this entry into my dictionary? - swift

I have a swift class called ArticleArchive as follows
import Foundation
class ArticleArchive: NSObject, NSCoding {
var entries: Dictionary<String, Dictionary<String, String>>?
var userDefaultsKey = "savedArticles"
override init() {
self.entries = Dictionary<String, Dictionary<String, String>>()
}
init(articles: Dictionary<String, Dictionary<String, String>>) {
self.entries = articles
}
required init(coder aDecoder: NSCoder) {
self.entries = aDecoder.decodeObjectForKey(userDefaultsKey) as? Dictionary
}
func encodeWithCoder(aCoder: NSCoder) {
if let articles = self.entries {
aCoder.encodeObject(articles, forKey: userDefaultsKey)
}
}
func populateArticles(articles: Dictionary<String, Dictionary<String, String>>) {
self.entries = articles
}
func save() {
let data = NSKeyedArchiver.archivedDataWithRootObject(self)
NSUserDefaults.standardUserDefaults().setObject(data, forKey: userDefaultsKey)
}
func clear() {
NSUserDefaults.standardUserDefaults().removeObjectForKey(userDefaultsKey)
}
class func loadSaved() -> ArticleArchive? {
if let data = NSUserDefaults.standardUserDefaults().objectForKey("savedArticles") as? NSData {
return NSKeyedUnarchiver.unarchiveObjectWithData(data) as? ArticleArchive
}
return nil
}
}
In a viewController, I have the following var of type ArticleArchive at the top of the class
var savedArticles : ArticleArchive? = nil
In viewDidLoad I do the following to initialise the variable
if let savedArticles:ArticleArchive = ArticleArchive.loadSaved() {
self.savedArticles = savedArticles
} else {
self.savedArticles = ArticleArchive()
}
I am trying to insert a Dictionary<String, String> into the savedArticles variable like so but it doesn't work.
let currentDictionary = filtered[indexPath.row] as Dictionary<String, String>
self.savedArticles?.entries[currentDictionary["link"]] = currentDictionary
I get an error that states "Could not find an overload for 'subscript' that accepts the supplied arguments"
I've gone around in circles trying different things but no luck, maybe someone can help me out. How can I insert currentDictionary (a Dictionary<String, String>) into savedArticles?

The insertion works, if the dictionary entries is non-optional
There is a good reason to use non-optional types.
Let's assume that when the object ArticleArchive exists, there exists also the entries dictionary which is empty by default.
The encodeWithCoder and init(coder) methods are also safe, because it's ensured that the non-optional entries dictionary is saved anyway even if it's empty, so it's never nil.
Here's the code
Edit : I added a type alias ArticleDictionary for better readability
class ArticleArchive: NSObject, NSCoding {
typealias ArticleDictionary = Dictionary<String, Dictionary<String, String>>
var entries: ArticleDictionary
var userDefaultsKey = "savedArticles"
override init() {
self.entries = ArticleDictionary()
}
init(articles: ArticleDictionary) {
self.entries = articles
}
required init(coder aDecoder: NSCoder) {
self.entries = aDecoder.decodeObjectForKey(userDefaultsKey) as! ArticleDictionary
}
func encodeWithCoder(aCoder: NSCoder) {
aCoder.encodeObject(self.entries, forKey: userDefaultsKey)
}
func populateArticles(articles: ArticleDictionary) {
self.entries = articles
}
func save() {
let data = NSKeyedArchiver.archivedDataWithRootObject(self)
NSUserDefaults.standardUserDefaults().setObject(data, forKey: userDefaultsKey)
}
func clear() {
NSUserDefaults.standardUserDefaults().removeObjectForKey(userDefaultsKey)
}
class func loadSaved() -> ArticleArchive? {
if let data = NSUserDefaults.standardUserDefaults().objectForKey("savedArticles") as? NSData {
return NSKeyedUnarchiver.unarchiveObjectWithData(data) as? ArticleArchive
}
return nil
}
}
as the link key might not exist, the value must be unwrapped.
savedArticles.entries[currentDictionary["link"]!] = currentDictionary

I modified your class to have this method:
func addEntry(key:String, dict:Dictionary<String,String>){
self.entries![key] = dict;
}
Then you can do this:
self.savedArticles?.addEntry(currentDictionary["link"]!, dict: currentDictionary)
Seems to work.
EDIT : If for some reason entries has to remain optional, then this can prevent a runtime crash (bit hacky but works)
func addEntry(key:String, dict:Dictionary<String,String>){
if let e = self.entries{
self.entries![key] = dict;
}else{
print("avoided runtime crash!")
}
}

Related

How to save array of objects (with image variables) in Swift and Xcode?

I am wondering how to save an array of objects from the following class:
class CustomDocument: NSObject, NSCoding {
let name : String
let image : UIImage
init(n: String, i: UIImage){
name = n
image = i
}
//other code excluded
}
Originally, I saved this array to User Defaults. Because the objects took up a lot of space, it caused a lot of lag in the app.
What is the best way to save an array of data that takes up a lot of space?
Thank you so much for the help and all responses are appreciated.
Try this code, Hope it helps:
class CustomDocument: NSObject, NSCoding {
var name : String?
var image : UIImage?
func encode(with aCoder: NSCoder) {
aCoder.encode(name, forKey: "namekey")
if let imageData = image!.jpegData(compressionQuality: 1.0){
aCoder.encode(imageData, forKey: "imagekey")
}
UserDefaults.standard.synchronize()
}
required convenience init?(coder aDecoder: NSCoder) {
self.init()
if let name = (aDecoder.decodeObject(forKey: "namekey") as? String){
self.name = name
}
if let imageData = (aDecoder.decodeObject(forKey: "imagekey") as? Data){
if let image = UIImage(data: imageData){
self.image = image
}
}
}
}
func archiveDocument(document:CustomDocument) -> Data? {
do {
let archivedObject = try NSKeyedArchiver.archivedData(withRootObject: document, requiringSecureCoding: false)
return archivedObject
} catch {
// do something with the error
}
return nil
}
func unarchiveDocument(unarchivedObject:Data) -> CustomDocument? {
do {
if let document = try NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(unarchivedObject) as? CustomDocument {
return document
}
} catch {
// do something with the error
}
return nil
}
Example:
//Set the object, also you can use an array instead of an object
let obj = CustomDocument()
obj.name = "doc1"
obj.image = UIImage(named: "my_image")
if let archivedObject = archiveDocument(document: obj){
UserDefaults.standard.set(archivedObject, forKey: "obj")
}
//Get the object
if let archivedObject = UserDefaults.standard.data(forKey: "obj"){
obj = unarchiveDocument(unarchivedObject: archivedObject)
let myImage = obj?.image
}

Getting optional String from singleton

I've created a class with some vars and lets. One of these vars is a String. I store them in UserDefaults. If I want to access the string of this class over a singleton class, I will always get an optional String. I don't know why.
Here is the class of the object:
import Foundation
import SpriteKit
class BallSkinsClass: NSObject, NSCoding {
let id: Int
var name: String
var isBuyed: Bool
let ID = "id"
let NAME = "name"
let ISBUYED = "isBuyed"
init(id: Int, name: String, isBuyed: Bool) {
self.id = id
self.name = name
self.isBuyed = isBuyed
}
required init?(coder aDecoder: NSCoder) {
self.id = aDecoder.decodeInteger(forKey: ID)
self.name = String(describing: aDecoder.decodeObject(forKey: NAME))
self.isBuyed = aDecoder.decodeBool(forKey: ISBUYED)
}
#objc func encode(with aCoder: NSCoder) {
aCoder.encode(id, forKey: ID)
aCoder.encode(name, forKey: NAME)
aCoder.encode(isBuyed, forKey: ISBUYED)
}
}
To declare the skins, access, save and load I have these functions in my BallSkinsClass:
import Foundation
import SpriteKit
import GameKit
class BallSkins {
static var sharedInstance = BallSkins()
private init() {
}
let BALLSKINS = "ballSkins"
var standard: BallSkinsClass! = BallSkinsClass(id: 0, name: "Standard", isBuyed: true)
var billiard: BallSkinsClass! = BallSkinsClass(id: 1, name: "Billard", isBuyed: false)
var emoji: BallSkinsClass! = BallSkinsClass(id: 2, name: "Emojis", isBuyed: false)
func archiveBallSkins(ballSkins:[BallSkinsClass]) -> NSData {
print("archiving Skins")
let archivedBallSkins = NSKeyedArchiver.archivedData(withRootObject: ballSkins as Array)
return archivedBallSkins as NSData
}
func saveBallSkins(ballSkins:[BallSkinsClass]) {
let archivedBallSkins = archiveBallSkins(ballSkins: ballSkins)
UserDefaults.standard.set(archivedBallSkins, forKey: BALLSKINS)
print("saving Skins")
}
func retrieveBallSkins() -> [BallSkinsClass]? {
print("retrieving Skins")
if let unarchivedBallSkins = UserDefaults.standard.object(forKey: BALLSKINS) as? NSData {
return NSKeyedUnarchiver.unarchiveObject(with: unarchivedBallSkins as Data) as? [BallSkinsClass]
}
return nil
}
func loadBallSkins() {
print("loading Skins")
let archivedBallSkins = retrieveBallSkins()
for ballSkin in archivedBallSkins! {
switch ballSkin.id {
case 0 :
standard.isBuyed = ballSkin.isBuyed
case 1:
billiard.isBuyed = ballSkin.isBuyed
case 2:
emoji.isBuyed = ballSkin.isBuyed
default:
standard.isBuyed = ballSkin.isBuyed
}
}
}
}
If I want to access the name of the skin in any other scene or view I call:
ballSkins.sharedInstance.billiard.name
But this is an optional every time! I don't know why or where the error is.
I suppose it is caused by
required init?(coder aDecoder: NSCoder) {
self.id = aDecoder.decodeInteger(forKey: ID)
self.name = String(describing: aDecoder.decodeObject(forKey: NAME))
self.isBuyed = aDecoder.decodeBool(forKey: ISBUYED)
}
3rd line generates optional string because according to documentation
func decodeObject() -> Any?
and String(describing: ...) does not unwrap your value. You must unwrap all values from UserDefaults by yourself, providing defaultValue if nil is not possible

Why is retrieval from NSUserDefaults failing for my custom object?

I have a class PredicPair which inherits from NSCoding and NSObject as such:
class PredicPair: NSObject, NSCoding {
var weight : Float
var prediction : String
init(weight: Float, prediction: String) {
self.weight = weight
self.prediction = prediction
super.init()
}
func encode(with aCoder: NSCoder) {
aCoder.encode(weight, forKey: "weight")
aCoder.encode(prediction, forKey: "prediction")
}
required convenience init(coder aDecoder: NSCoder) {
let unarchivedWeight = aDecoder.decodeObject(forKey: "weight") as! Float
let unarchivedPrediction = aDecoder.decodeObject(forKey: "prediction") as! String
self.init(weight: unarchivedWeight, prediction: unarchivedPrediction)
}
class func saveToUserDefaults(pairs: [PredicPair]) {
let dataBlob = NSKeyedArchiver.archivedData(withRootObject: pairs)
UserDefaults.standard.set(dataBlob, forKey: "test")
UserDefaults.standard.synchronize()
}
class func loadFromUserDefaults() -> [PredicPair]? {
guard let decodedNSDataBlob = UserDefaults.standard.object(forKey: "test") as? NSData,
let loadedFromUserDefault = NSKeyedUnarchiver.unarchiveObject(with: decodedNSDataBlob as Data) as? [PredicPair]
else {
return nil
}
return loadedFromUserDefault
}
}
I am trying to store an array of the class in UserDefaults and retrieving it, but the latter always returns nil. Any reason why?
let predicPair1 = PredicPair(weight: 0, prediction: "there")
let predicPair2 = PredicPair(weight: 1, prediction: "hello")
let array = [predicPair1, predicPair2]
PredicPair.saveToUserDefaults(pairs: array)
if let retreivedArray = PredicPair.loadFromUserDefaults() {
print("loaded \(retreivedArray.count) players from NSUserDefaults")
} else {
print("failed")
}
I've also tried saving to a file using NSKeyedArchiver but retrieval fails as well.

Unarchiving a dictionary with unarchiveObjectWithFile using swift

I am facing problem with the data that I got from using unarchiveObjectWithFile. I send a dictionary for archiving. The data, I got shows the value while debugging but I can't extract the exact value for key from the data. Always getting nil value
Here is my code
class Person : NSObject, NSCoding {
struct Keys {
static let Name = "name"
static let Age = "age"
}
var name: [String] = []
var age: [Int] = []
init(dictionary: [String : AnyObject]) {
name = dictionary[Keys.Name] as! [String]
age = dictionary[Keys.Age] as! [Int]
}
func encodeWithCoder(archiver: NSCoder) {
archiver.encodeObject(name, forKey: Keys.Name)
archiver.encodeObject(age, forKey: Keys.Age)
}
required init(coder unarchiver: NSCoder) {
if let namesList = unarchiver.decodeObjectForKey(Keys.Name) as? [String] {
name = namesList
print("name\(name)")
}
if let agesList = unarchiver.decodeObjectForKey(Keys.Age) as? [Int] {
age = agesList
print("age \(age)")
}
super.init()
}
}
// View controller
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let persons: [String:AnyObject] = [
"name" : ["a","b","c"],
"age" : [2,3,4]
]
let personObj = Person(dictionary: persons)
NSKeyedArchiver.archiveRootObject(personObj, toFile: "/Users/shuvo/Desktop/Data.json")
let responseObject = NSKeyedUnarchiver.unarchiveObjectWithFile("/Users/shuvo/Desktop/Data.json") as? Person
if let unwrapData: AnyObject = responseObject {
let nameArr = unwrapData["name"] as? [String]
let ageArr = unwrapData["age"] as? [Int]
print("age \(ageArr) name \(nameArr)")
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
That's because you are archiving the Person object and not the [String: AnyObject].
You can either archive the Person, and extract it as a Person or archive the [String: AnyObject] and extract that. You can't use both as they are different types.
If you want to archive the Person object, make sure you implement the NSCoding protocol and have something similar to this in your Person class...
func encode(with aCoder: NSCoder) {
aCoder.encode(name, forKey: "name")
aCoder.encode(age, forKey: "age")
}
required init?(coder aDecoder: NSCoder) {
self.name = aDecoder.decodeObject(forKey: "name") as! [String]
self.age = aDecoder.decodeObject(forKey: "age") as! [Int]
}
After that, you can just do...
if let responseObject = NSKeyedUnarchiver.unarchiveObject(withFile: path) as? Person {
// You can use `responseObject` as a `Person` object here...
}

Could not cast value of type 'Swift.Array<(Swift.String, Swift.String)>' to 'Swift.AnyObject'

My swift code look like below
Family.arrayTuple:[(String,String)]? = []
Family.arrayTupleStorage:String?
Family.arrayTupleStorage:String = (newDir! as NSString).stringByAppendingPathComponent("arrayTuple.archive")
NSKeyedArchiver.archiveRootObject(Family.arrayTuple! as! AnyObject, toFile: Family.arrayTupleStorage!)
I have error massage in console window while building code.
'Could not cast value of type 'Swift.Array<(Swift.String, Swift.String)>' (0xcce8098) to 'Swift.AnyObject' (0xcc8f00c).'
How can I archive Family.arrayTuple and unarchive Family.arrayTupleStorage?
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let obj = SomeClass()
obj.foo = (6,5)
let data = NSKeyedArchiver.archivedDataWithRootObject(obj)
NSUserDefaults.standardUserDefaults().setObject(data, forKey: "books")
if let data = NSUserDefaults.standardUserDefaults().objectForKey("books") as? NSData {
let o = NSKeyedUnarchiver.unarchiveObjectWithData(data) as SomeClass
println(o.foo) // (Optional(6), Optional(5))
}
}
}
class SomeClass: NSObject, NSCoding {
var foo: (x: Int?, y: Int?)!
required convenience init(coder decoder: NSCoder) {
self.init()
let x = decoder.decodeObjectForKey("myTupleX") as Int?
let y = decoder.decodeObjectForKey("myTupleY") as Int?
foo = (x,y)
}
func encodeWithCoder(coder: NSCoder) {
coder.encodeObject(foo.x, forKey: "myTupleX")
coder.encodeObject(foo.y, forKey: "myTupleY")
}
}