I must be incorrectly using Custom Objects for NSUserDefaults. The error " Property list invalid for format: 200 (property lists cannot contain objects of type 'CFType')". Below is my code, the Goal class is of particular interest, since this is where I am adopting the NSCoding protocol.
This code is global.
func saveGoals (goals : [Goal]) {
var updatedGoals = NSKeyedArchiver.archivedDataWithRootObject(goals)
NSUserDefaults.standardUserDefaults().setObject(updatedGoals, forKey: "Goals")
NSUserDefaults.standardUserDefaults().synchronize()
}
func loadCustomObjectWithKey() -> [Goal] {
if let encodedObject : NSData = NSUserDefaults.standardUserDefaults().objectForKey("Goals") as? NSData {
var encodedObject : NSData? = NSUserDefaults.standardUserDefaults().objectForKey("Goals") as? NSData
var goal : [Goal] = NSKeyedUnarchiver.unarchiveObjectWithData(encodedObject!) as [Goal]
return goal
} else {
return [Goal]()
}
}
This code is in GoalsViewController.
class GoalsViewController: MainPageContentViewController, UITableViewDelegate, UITableViewDataSource {
@IBOutlet var tableView: GoalsTableView!
var cell = GoalTableViewCell()
var goalsArray : Array<Goal> = [] //
override func viewDidLoad() {
super.viewDidLoad()
self.tableView.delegate = self
self.tableView.dataSource = self
if var storedGoals: [Goal] = loadCustomObjectWithKey() as [Goal]? {
goalsArray = storedGoals
}
//retrieve data.
var goal = Goal(title: "Walk the Dog")
goalsArray.append(goal)
saveGoals(goalsArray)
self.tableView?.reloadData()
tableView.estimatedRowHeight = 44.0
tableView.rowHeight = UITableViewAutomaticDimension
var notification = NSNotificationCenter.defaultCenter()
notification.addObserver(self, selector: "finishCreatingGoal:", name: "FinishCreatingGoal", object: nil)
}
func finishCreatingGoal(notification : NSNotification) {
if (notification.name == "FinishCreatingGoal") {
var userInfo = notification.userInfo!
var text = userInfo["text"]! as String
var index = userInfo["index"]! as Int
var cell = self.tableView.cellForRowAtIndexPath(NSIndexPath(forRow: index, inSection: 0)) as GoalTableViewCell
goalsArray[index].title = cell.goalTextField.text
saveGoalList(goalsArray)
self.tableView.reloadData()
self.tableView.scrollToRowAtIndexPath(NSIndexPath(forRow: index, inSection: 0), atScrollPosition: UITableViewScrollPosition.Middle, animated: true)
}
}
This code is in the Goal class.
import UIKit
class Goal : NSObject, NSCoding {
var title : String? = ""
var checkmarked : Bool? = false
var isLastCell : Bool? = false
var enabled : Bool? = true
var priority = Priority.defaultPriority
override init() {
}
init(title : String) {
self.title = title
}
func encodeWithCoder(aCoder: NSCoder) {
aCoder.encodeObject(title!, forKey: "title")
aCoder.encodeBool(checkmarked!, forKey: "checkmarked")
aCoder.encodeBool(isLastCell!, forKey: "isLastCell")
aCoder.encodeBool(enabled!, forKey: "enabled")
}
required init(coder aDecoder: NSCoder) {
title = aDecoder.decodeObjectForKey("title") as String!
checkmarked = aDecoder.decodeBoolForKey("checkmarked") as Bool
isLastCell = aDecoder.decodeBoolForKey("isLastCell") as Bool
enabled = aDecoder.decodeBoolForKey("enabled") as Bool
}
}
I am going to just copy code from a working project I have:
here is the Game object class with data from a math flash card game:
import Foundation
class GameData: NSObject {
var sign: String = "+"
var level: Int = 1
var problems: Int = 10
var time: Int = 30
var skipWrong: Bool = true
var usedTime: Int = 0
var correctCount: Int = 0
var correctTopNumber: [Int] = [Int]()
var correctBottomNumber: [Int] = [Int]()
var wrongTopNumber: [Int] = [Int]()
var wrongBottomNumber: [Int] = [Int]()
var date: NSDate = NSDate()
func encodeWithCoder(aCoder: NSCoder!) {
aCoder.encodeObject(sign, forKey: "sign")
aCoder.encodeInteger(level, forKey: "level")
aCoder.encodeInteger(problems, forKey: "problems")
aCoder.encodeInteger(time, forKey: "time")
aCoder.encodeBool(skipWrong, forKey: "skipWrong")
aCoder.encodeInteger(usedTime, forKey: "usedTime")
aCoder.encodeInteger(correctCount, forKey: "correctCount")
aCoder.encodeObject(correctTopNumber, forKey: "correctTopNumber")
aCoder.encodeObject(correctBottomNumber, forKey: "correctBottomNumber")
aCoder.encodeObject(wrongTopNumber, forKey: "wrongTopNumber")
aCoder.encodeObject(wrongBottomNumber, forKey: "wrongBottomNumber")
aCoder.encodeObject(date, forKey: "date")
}
init(coder aDecoder: NSCoder!) {
sign = aDecoder.decodeObjectForKey("sign") as String
level = aDecoder.decodeIntegerForKey("level")
problems = aDecoder.decodeIntegerForKey("problems")
time = aDecoder.decodeIntegerForKey("time")
skipWrong = aDecoder.decodeBoolForKey("skipWrong")
usedTime = aDecoder.decodeIntegerForKey("usedTime")
correctCount = aDecoder.decodeIntegerForKey("correctCount")
correctTopNumber = aDecoder.decodeObjectForKey("correctTopNumber") as Array
correctBottomNumber = aDecoder.decodeObjectForKey("correctBottomNumber") as Array
wrongTopNumber = aDecoder.decodeObjectForKey("wrongTopNumber") as Array
wrongBottomNumber = aDecoder.decodeObjectForKey("wrongBottomNumber") as Array
date = aDecoder.decodeObjectForKey("date") as NSDate
}
override init() {
}
}
This part looks about the same as yours, but with more variable types. The archiver and retriever classes differ from you:
import Foundation
class ArchiveGameData:NSObject {
var documentDirectories:NSArray = []
var documentDirectory:String = ""
var path:String = ""
func ArchiveResults(#dataSet: [GameData]) {
documentDirectories = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true)
documentDirectory = documentDirectories.objectAtIndex(0) as String
path = documentDirectory.stringByAppendingPathComponent("results3.archive")
if NSKeyedArchiver.archiveRootObject(dataSet, toFile: path) {
//println("Success writing to file!")
} else {
println("Unable to write to file!")
}
}
func RetrieveGameData() -> NSObject {
var dataToRetrieve = [GameData]()
documentDirectories = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true)
documentDirectory = documentDirectories.objectAtIndex(0) as String
path = documentDirectory.stringByAppendingPathComponent("results3.archive")
if let dataToRetrieve2 = NSKeyedUnarchiver.unarchiveObjectWithFile(path) as? [GameData] {
dataToRetrieve = dataToRetrieve2
}
return(dataToRetrieve)
}
}
Finally, the code for storing and retrieving from within a ViewController:
//retrieveing
var gameDataArray = ArchiveGameData().RetrieveGameData() as [GameData]
//Archiving
gameData = GameData() //create local object then append all the new data, then store it
gameData.sign = buttonStates.sign
gameData.level = buttonStates.level
gameData.problems = buttonStates.problems
gameData.time = buttonStates.time
//etc. for all properties
ArchiveGameData().ArchiveResults(dataSet: gameDataArray)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With