mint mint - 2 months ago 10
Swift Question

NSUserDefaults for custom object with other custom class variables

I have tried look at the other answers regarding NSUserDefaults, but so far they haven't helped.

I'm trying to save a Player into NSUserDefaults, but the Player class also has two variables for the custom classes Pokemon and Items.

How do I save and load the player? Do I need to separately encode each custom class variable and decode them as well? I'm not sure how it would work.

Here are my classes:
Player Class

import Foundation
class Player : NSCoder {

private var steps = Int()
private var money = Int()
private var items = [Item]()
private var pokemon = [Pokemon]()
var eggArray = [Int]()

struct User {
static var Gold = Player()
}

override init(){
steps = 0
money = 0
items = [Item]()
pokemon = [Pokemon]()
eggArray = [1, 4, 7, 10, 13, 16, 19, 21, 23, 27, 29, 32, 37, 41, 43, 43, 46, 48, 50, 52, 54, 56, 58, 60, 60, 63, 66, 69, 72, 74, 77, 79, 79, 81, 83, 84, 86, 88, 90, 92, 95, 96, 98, 100, 102, 104, 108, 109, 111, 113, 114, 115, 116, 118, 120, 122, 123, 127, 128, 129, 131, 133, 133, 133, 133, 133, 137, 138, 140, 142, 143, 147, 152, 155, 158, 161, 163, 165, 167, 170, 172, 173, 174, 175, 177, 179, 183, 185, 187, 190, 191, 193, 194, 198, 200, 201, 202, 203, 204, 206, 207, 209, 211, 213, 214, 215, 216, 218, 220, 222, 223, 225, 226, 227, 228, 231, 234, 235, 236, 236, 236, 238, 239, 240, 241, 246]
}

init(steps: Int, money: Int, items: [Item], pokemon: [Pokemon], eggArray: [Int]){
self.steps = steps
self.money = money
self.items = items
self.pokemon = pokemon
self.eggArray = eggArray
}

required convenience init(coder aDecoder: NSCoder) {
let steps = aDecoder.decodeIntegerForKey("steps")
let money = aDecoder.decodeIntegerForKey("money")
let items = aDecoder.decodeObjectForKey("items") as! [Item]
let pokemon = aDecoder.decodeObjectForKey("pokemon") as! [Pokemon]
let eggArray = aDecoder.decodeObjectForKey("eggArray") as! [Int]
self.init(steps: steps, money: money, items: items, pokemon: pokemon, eggArray: eggArray)
}

func encodeWithCoder(aCoder: NSCoder) {
aCoder.encodeInteger(steps, forKey: "steps")
aCoder.encodeInteger(money, forKey: "money")
aCoder.encodeObject(items, forKey: "items")
aCoder.encodeObject(pokemon, forKey: "pokemon")
aCoder.encodeObject(eggArray, forKey: "eggArray")
}

}


Item Class

import Foundation
import CoreData

class Item : NSObject{

var name : String
var price : Int


init(item: NSManagedObject){
name = item.valueForKey("name") as! String
price = item.valueForKey("price") as! Int

super.init()

}

init(name: String, price: Int){
self.name = name
self.price = price

super.init()

}

}


Pokemon Class

import Foundation
import SwiftyJSON

class Pokemon : NSObject {

var dexNo = Int()
var name = String()

var type1 = String()
var type2 = String()

var hp = Int()
var atk = Int()
var def = Int()
var spatk = Int()
var spdef = Int()
var spd = Int()

var move1 = String()
var move2 = String()
var move3 = String()
var move4 = String()

var evolves = String()
var method = String()
var detail = String()

var happiness = Int()
var MAXHAPPINESS = Int()

var currentEXP = Double()
var MAXEXP = Double()
var level = Double()

var isEgg = Bool()
var stepsToHatch = Int()

override init(){

}

init(jsonNumber: Int){
let path : String = NSBundle.mainBundle().pathForResource("PKMNdata", ofType: "json") as String!
let jsonData = NSData(contentsOfFile: path) as NSData!
let readableJSON = JSON(data: jsonData, options: NSJSONReadingOptions.MutableContainers, error: nil)

currentEXP = 0
MAXEXP = 1000000
level = 1

happiness = 0
MAXHAPPINESS = 255

isEgg = true
stepsToHatch = 10000

self.dexNo = readableJSON["Pokemon"]["Pokemon\(jsonNumber)"]["DEXNO"].int!
self.name = readableJSON["Pokemon"]["Pokemon\(jsonNumber)"]["NAME"].string!
self.type1 = readableJSON["Pokemon"]["Pokemon\(jsonNumber)"]["TYPE1"].string!
self.type2 = readableJSON["Pokemon"]["Pokemon\(jsonNumber)"]["TYPE2"].string!
self.evolves = readableJSON["Pokemon"]["Pokemon\(jsonNumber)"]["EVOLVES"].string!
self.method = readableJSON["Pokemon"]["Pokemon\(jsonNumber)"]["METHOD"].string!
self.detail = readableJSON["Pokemon"]["Pokemon\(jsonNumber)"]["DETAIL"].string!
self.hp = readableJSON["Pokemon"]["Pokemon\(jsonNumber)"]["HP"].int!
self.atk = readableJSON["Pokemon"]["Pokemon\(jsonNumber)"]["ATK"].int!
self.def = readableJSON["Pokemon"]["Pokemon\(jsonNumber)"]["DEF"].int!
self.spatk = readableJSON["Pokemon"]["Pokemon\(jsonNumber)"]["SPATK"].int!
self.spdef = readableJSON["Pokemon"]["Pokemon\(jsonNumber)"]["SPDEF"].int!
self.spd = readableJSON["Pokemon"]["Pokemon\(jsonNumber)"]["SPD"].int!
self.move1 = readableJSON["Pokemon"]["Pokemon\(jsonNumber)"]["MOVE1"].string!
self.move2 = readableJSON["Pokemon"]["Pokemon\(jsonNumber)"]["MOVE2"].string!
self.move3 = readableJSON["Pokemon"]["Pokemon\(jsonNumber)"]["MOVE3"].string!
self.move4 = readableJSON["Pokemon"]["Pokemon\(jsonNumber)"]["MOVE4"].string!

}

init(dexNo: Int, name: String, type1: String, type2: String, evolves: String, method: String, detail: String, hp: Int, atk: Int, def: Int, spatk: Int, spdef: Int, spd: Int, move1: String, move2: String, move3: String, move4: String){
currentEXP = 0
MAXEXP = 1000000
level = 1

happiness = 0
MAXHAPPINESS = 255

isEgg = true
stepsToHatch = 10000

self.dexNo = dexNo
self.name = name
self.type1 = type1
self.type2 = type2
self.evolves = evolves
self.method = method
self.detail = detail
self.hp = hp
self.atk = atk
self.def = def
self.spatk = spatk
self.spdef = spdef
self.spd = spd
self.move1 = move1
self.move2 = move2
self.move3 = move3
self.move4 = move4
}

}


and the two buttons I am using to call save and load (for testing only)
there's a global variable:
var userDefaults = NSUserDefaults.standardUserDefaults()
if it matters.

@IBAction func savePlayer(sender: UIButton) {
print("save")
userDefaults = NSUserDefaults.standardUserDefaults()
let encodedData = NSKeyedArchiver.archivedDataWithRootObject(Player.User.Gold)
userDefaults.setObject(encodedData, forKey: "player")
userDefaults.synchronize()
}


@IBAction func loadPlayer(sender: UIButton) {
print("load")
let decoded = userDefaults.objectForKey("player") as! NSData
let decodedPlayer = NSKeyedUnarchiver.unarchiveObjectWithData(decoded) as! Player
Player.User.Gold = decodedPlayer
}


Right now I get an error when saving, which I assume is for the saving the item and Pokemon variables in the Player class.

Answer

Actually, you must make Player, Item and Pokemon conform to NSCoding, not NSCoder.

Conforming to NSCoding basically lets you define a way to encode and decode an object of the conforming class, while an NSCoder subclass does the actual encoding and/or decoding.

You want to archive a Player:

let encodedData = NSKeyedArchiver.archivedDataWithRootObject(Player.User.Gold)

However, since Player, Item and Pokemon don't conform to NSCoding, NSKeyedUnarchiver has no idea how to encode them! As a result, an error appears.

Comments