store array of custom class with nested custom class to standardUserDefaults - swift

Swift 2
Xcoode 7.3
I try to store this:
var someArray = [Class1(id: 1, titel: "Titel 2", something: Class2(somevar: 20))]
with NSUSerDefaults:
let arrayData = NSKeyedArchiver.archivedDataWithRootObject(someArray)
NSUserDefaults.standardUserDefaults().setObject(arrayData, forKey: "array")
My classes look like this:
class Class1 {
var id: Int
var titel: String!
var something: Class2
init(id: Int, titel: String, something: Class2) {
self.id = id
self.titel = titel
self.something = something
}
required init(coder aDecoder: NSCoder) {
self.id = aDecoder.decodeIntForKey("id")
self.titel = aDecoder.decodeStringForKey("titel")
???
}
func encodeWithCoder(aCoder: NSCoder) {
aCoder.encodeObject(id, forKey: "id")
aCoder.encodeObject(titel, forKey: "titel")
???
}
class Class2: Class1
{
var somevar: Int
init(setback: Int) {
self.somevar = somevar
}
}
What do I need to add in those classes?
(Have to add some mor details; but I think it's self-explaining)

Uses #FLX code sample and ensure every object in your object's tree also conforms to NSCoding.

it's very easy.
write:
self.something = aDecoder.decodeObjectForKey("something") as! Class2
read:
aCoder.encodeObject(something, forKey: "something")

Related

NSUserDefaults Initializer

Im learning about NSUserDefaults and I have a class that instantiates a person with a first and last name.
class Person: NSObject, NSCoding {
var firstName: String!
var lastName: String!
init(first:String, last:String) {
firstName = first
lastName = last
}
override init() {
}
required convenience init?(coder aDecoder: NSCoder) {
self.init()
self.firstName = aDecoder.decodeObjectForKey("firstName") as? String
self.lastName = aDecoder.decodeObjectForKey("lastName") as? String
}
func encodeWithCoder(aCoder: NSCoder) {
aCoder.encodeObject(self.firstName, forKey: "firstName")
aCoder.encodeObject(self.lastName, forKey: "lastName")
}
}
I am wondering why I have to override my first initializer that has initializes first and last names with an empty initializer?
Also, what exactly is a "required convenience" initializer?
Thank you
You need to call NSObject's initializer in your custom initializer:
init(first:String, last:String) {
firstName = first
lastName = last
super.init()
}

How to make singleton class from class, which inherits from nsobject and nscoding?

I am trying to make the game settings which loads, saves itself, and makes it singleton class. All my efforts lead to failure, XCode asks me "Cannot invoke initializer for type "Settings" with no arguments". How can I fix this?
This is the code:
class Settings: NSObject, NSCoding {
static let sharedInstance = Settings()
var currentLevel: Int
var positionOfPlayer: [Int]?
var sounds: Bool
var shape: String
var completedLevels: [Int: Bool]
init?(currentLevel: Int, positionOfPlayer: [Int]?, sounds: Bool, shape: String, completedLevels: [Int: Bool]) {
self.currentLevel = currentLevel
self.sounds = sounds
self.shape = shape
self.completedLevels = completedLevels
if let position = positionOfPlayer as [Int]? {
self.positionOfPlayer = position
}
super.init()
}
required convenience init?(coder aDecoder: NSCoder) {
let currentLevel = aDecoder.decodeObjectForKey(SettingNames.nameOfCurrentLevel) as? Int
let completedLevels = aDecoder.decodeObjectForKey(SettingNames.nameOfCompletedLevels) as? [Int: Bool]
let positionOfPlayer = aDecoder.decodeObjectForKey(SettingNames.positionOfPlayerOnCurrentLevel) as? [Int]
let sounds = aDecoder.decodeObjectForKey(SettingNames.nameOfSounds) as? Bool
let shape = aDecoder.decodeObjectForKey(SettingNames.nameOfShapes) as? String
self.init(currentLevel: currentLevel!, positionOfPlayer: positionOfPlayer, sounds: sounds!, shape: shape!, completedLevels: completedLevels!)
}
func encodeWithCoder(aCoder: NSCoder) {
aCoder.encodeObject(currentLevel, forKey: SettingNames.nameOfCurrentLevel)
aCoder.encodeObject(completedLevels, forKey: SettingNames.nameOfCompletedLevels)
if let position = positionOfPlayer as [Int]? {
// If game canceled or ended during playing, it saves the current player position.
// Next time, when player open the game, it will continue
aCoder.encodeObject(position, forKey: SettingNames.positionOfPlayerOnCurrentLevel)
}
aCoder.encodeBool(sounds, forKey: SettingNames.nameOfSounds)
aCoder.encodeObject(shape, forKey: SettingNames.nameOfShapes)
}
}
You're trying to access a constructor which accepts no parameters which was not implemented for the current class. Try overriding the init method, that should remove the error.
override init(){
// some code
}
Here's a full example I've tried:
import Foundation
class Settings : NSObject, NSCoding {
static let sharedInstance = Settings()
var a: String?
var b: String?
convenience init(a: String, b: String){
self.init()
self.a = a
self.b = b
}
override init(){
}
required init?(coder aDecoder: NSCoder) {
}
func encodeWithCoder(aCoder: NSCoder) {
}
}
Just a point of view: a Singleton that requires you to pass parameters in order to configure it does no longer behave like a singleton.

swift protocol init func error with SpriteKit

Can anyone show me what wrong with my code?
code in playground
error detail
protocol PersonWithName: class {
var personName: String {get set}
init(name: String)
}
class NameCard<PersonType: SKSpriteNode where PersonType: PersonWithName> {
var person: PersonType
init() {
self.person = PersonType(name: "No Name") // this line error.
}
}
For shorter explanation I replaced SKSpriteNode with Any because you have to deal with the designated init() of SKSpriteNode later in more detail.. For more details on Initializer Requirements in protocols, take a look at this section of Apple's Developer Guide
protocol PersonWithName: class {
var personName: String {get set}
init(name: String)
}
class NameCard<PersonType: Any where PersonType: PersonWithName> {
var person: PersonType
init() {
self.person = PersonType(name: "No Name")
}
}
class ExampleClass: PersonWithName {
var personName: String = ""
required init(name: String) {
personName = name
}
}
var test = NameCard<ExampleClass>()
print(test.person.personName) // "No Name"
My current imperfect solution.#FlorianWeßling
protocol PersonWithName: class {
var personName: String {get set}
init(name: String)
}
protocol SKSpriteNodeProtocol {
var position: CGPoint {get set}
var size: CGSize {get set}
//or more properties you want
}
class NameCard<PersonType: SKSpriteNodeProtocol where PersonType: PersonWithName> {
var person: PersonType
init() {
self.person = PersonType(name: "No Name")
}
}
extension SKSpriteNode : SKSpriteNodeProtocol {}
class ExampleClass: SKSpriteNode, PersonWithName {
var personName: String = ""
required init(name: String) {
personName = name
super.init(texture: nil, color: UIColor.clearColor(), size: CGSizeZero)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
var test = NameCard<ExampleClass>()
print(test.person.personName) // "No Name"
If it is only important to access a SKSpriteNode within the NameCard then you might add a simple variable node and remove the inheritance from SKSpriteNode:
protocol PersonWithName {
var personName: String {get set}
init(name: String)
}
class NameCard<PersonType: PersonWithName> {
var person: PersonType
var node: SKSpriteNode?
init() {
self.person = PersonType(name: "No Name")
}
}
class ExampleClass: PersonWithName {
var personName: String = ""
required init(name: String) {
personName = name
}
}
var test = NameCard<ExampleClass>()
print(test.person.personName) // "No Name"
print(test.node?.description) // currently nil

Saving Array with NSCoding

I have a small app that has a few saving functionalities. I have a data model class called: Closet:
class Department: NSObject, NSCoding {
var deptName = ""
var managerName = ""
var Task: [Assignment]? // <----- assignment class is in example 2
func encodeWithCoder(aCoder: NSCoder) {
aCoder.encodeObject(deptName, forKey: "deptName")
aCoder.encodeObject(managerName, forKey: "mngName")
// aCoder.encodeObject(Task, forKey: "taskArray")
}
required init(coder aDecoder: NSCoder) {
super.init()
course = aDecoder.decodeObjectForKey("deptName") as! String
instructor = aDecoder.decodeObjectForKey("mngName") as! String
// Task = aDecoder.decodeObjectForKey("tasKArray") as? [Assignment]
}
override init() {
super.init()
}
}
So this is the main controller data model which in the first View Controller, a user is able to tap the "+" button to add a department name and manager name. The problem is not with saving this as i save it successfully using NSKeyedArchive and loads it back when the app starts.
The Problem:
I want to add an array of assignments on this data model Department called Assignment which would have a title and a notes variable. This is the Data model for Assignment:
Assignment.swift
class Assignment: NSObject, NSCoding {
var title = ""
var notes = ""
func encodeWithCoder(aCoder: NSCoder) {
// Methods
aCoder.encodeObject(title, forKey: "Title")
aCoder.encodeObject(notes, forKey: "notepad")
}
required init(coder aDecoder: NSCoder) {
// Methods
title = aDecoder.decodeObjectForKey("Title") as! String
notes = aDecoder.decodeObjectForKey("notepad") as! String
super.init()
}
override init() {
super.init()
}
}
So what i am essentially trying to achieve is an app where a user enters different departments with different manager names which work now in my app, but within a department, the user can click the "+" button to add an assignment title and notes section that can be editable when clicked which i can handle afterwards. These assignments are different from department to department.
My big problem is achieving this functionality. I can't seem to get this working.
I want this array assigment property to be part of the Department Class so each cell can have their own sort of To-Do list. any help would definitely help me out a lot. Thanks :)
You are using NSCoder correctly, but there are two errors in capitalization. The first error affects the functionality of the application, and the second error is a stylistic mistake. You encoded Task with the key "taskArray", but you tried to decode it with the key "tasKArray". If you fix the capital K in the latter, then your code will work.
The second capitalization error is a stylistic mistake: Task, like all properties in Swift, should be written in lowerCamelCase (llamaCase).
Be sure to pay close attention to indentation. In programming, there are special indentation rules we follow that help make code clear. Here is the corrected code with proper capitalization and indentation:
class Department: NSObject, NSCoding {
var deptName = ""
var managerName = ""
var task: [Assignment]?
func encodeWithCoder(aCoder: NSCoder) {
aCoder.encodeObject(deptName, forKey: "deptName")
aCoder.encodeObject(managerName, forKey: "mngName")
aCoder.encodeObject(task, forKey: "taskArray")
}
required init(coder aDecoder: NSCoder) {
super.init()
course = aDecoder.decodeObjectForKey("deptName") as! String
instructor = aDecoder.decodeObjectForKey("mngName") as! String
task = aDecoder.decodeObjectForKey("taskArray") as? [Assignment]
}
override init() {
super.init()
}
}
class Assignment: NSObject, NSCoding {
var title = ""
var notes = ""
func encodeWithCoder(aCoder: NSCoder) {
// Methods
aCoder.encodeObject(title, forKey: "Title")
aCoder.encodeObject(notes, forKey: "notepad")
}
required init(coder aDecoder: NSCoder) {
// Methods
title = aDecoder.decodeObjectForKey("Title") as! String
notes = aDecoder.decodeObjectForKey("notepad") as! String
super.init()
}
override init() {
super.init()
}
}
Updated for Swift 5 / Xcode Version 12.4 (12D4e)
Thanks for the example above Tone416 -- I've reworked it for Swift 5 as the protocols and methods have changed. I've also included a simple test to prove it out so you should be able to just cut and paste this into a playground a run it.
import Foundation
class Department: NSObject, NSCoding {
var deptName = ""
var managerName = ""
var task: [Assignment]?
func encode(with coder: NSCoder) {
coder.encode(deptName, forKey: "deptName")
coder.encode(managerName, forKey: "mngName")
coder.encode(task, forKey: "taskArray")
}
required init(coder aDecoder: NSCoder) {
super.init()
deptName = aDecoder.decodeObject(forKey: "deptName") as! String
managerName = aDecoder.decodeObject(forKey: "mngName") as! String
task = aDecoder.decodeObject(forKey: "taskArray") as? [Assignment]
}
override init() {
super.init()
}
convenience init(deptName: String, managerName: String, task: [Assignment]?) {
self.init()
self.deptName = deptName
self.managerName = managerName
self.task = task
}
}
class Assignment: NSObject, NSCoding {
var title = ""
var notes = ""
func encode(with coder: NSCoder) {
// Methods
coder.encode(title, forKey: "Title")
coder.encode(notes, forKey: "notepad")
}
required init(coder aDecoder: NSCoder) {
// Methods
title = aDecoder.decodeObject(forKey: "Title") as! String
notes = aDecoder.decodeObject(forKey: "notepad") as! String
super.init()
}
override init() {
super.init()
}
convenience init(title: String, notes: String) {
self.init()
self.title = title
self.notes = notes
}
}
// Create some data for testing
let assignment1 = Assignment(title: "title 1", notes: "notes 1")
let assignment2 = Assignment(title: "title 2", notes: "notes 2")
let myDepartment = Department(deptName: "My Dept", managerName: "My Manager", task: [assignment1, assignment2])
// Try archive and unarchive
do {
// Archive
let data = try NSKeyedArchiver.archivedData(withRootObject: myDepartment, requiringSecureCoding: false)
print ("Bytes in archive: \(data.count)")
// Unarchive
let obj = try NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(data) as! Department
// Print the contents of the unarchived object
print("Department: \(obj.deptName) Manager: \(obj.managerName)")
if let task = obj.task {
for i in 0...task.count-1 {
print("Task: \(task[i].title) \(task[i].notes)")
}
}
} catch {
let nsError = error as NSError
fatalError("Unresolved error \(nsError), \(nsError.userInfo)")
}
Enjoy

Get "does not implement methodSignatureForSelector" when try to store Array in NSUserDefaults,Swift?

I try to store Array of objects in NSUserDefaults.
I have following snippets of code:
var accounts = MyAccounts()
var array:Array<MyAccounts.MyCalendar> = accounts.populateFromCalendars()
NSUserDefaults.standardUserDefaults().
setObject(array, forKey: "test_storeAccounts_array") // <- get error here
NSUserDefaults.standardUserDefaults().synchronize()
But I get Exception:
does not implement methodSignatureForSelector: -- trouble ahead
my class structure:
class MyAccounts {
/* ... */
class MyCalendar {
var title:String?
var identifier:String?
var email:String?
var calType:String?
var isActive:Bool?
var isMainAcount:Bool?
init(){}
}
}
Any ideas?
Make sure your class inherits from NSObject
class MyAccounts:NSObject {
/* ... */
class MyCalendar {
var title:String?
var identifier:String?
var email:String?
var calType:String?
var isActive:Bool?
var isMainAcount:Bool?
init(){}
}
}
I was getting this exception in Swift 3.0. In my case, my model class was not inherit from NSObject base class. just inherit my class from NSObject base class and implements NSCoding protocol (if your container array has custom objects)
class Stock: NSObject, NSCoding {
var stockName: String?
override init() {
}
//MARK: NSCoding protocol methods
func encode(with aCoder: NSCoder){
aCoder.encode(self.stockName, forKey: "name")
}
required init(coder decoder: NSCoder) {
if let name = decoder.decodeObject(forKey: "name") as? String{
self.stockName = name
}
}
func getStockDataFromDict(stockDict stockDict:[String:AnyObject]) -> Stock {
if let stockName = stockDict["name"] {
self.stockName = stockName as? String
}
return self
}
}
In Swift 2, I experienced similar error while using the Notification Pattern within a custom class. Note that when the same notification(Observe) is implemented in a ViewController class , it doesn't complain. Its only with the custom class, created from a Swift file without subclassing this error was thrown
class myClass : NSObject {
override init(){
super.init()
NSNotificationCenter.defaultCenter().addObserver(self, selector: Selector("functionCall:"), name: "NotificationName", object: nil)
}
//Implement function
func functionCall(notification: NSNotification) {
//Extract the object and implement the function
}
}
You need to convert the class into NSData first. Something like this:
var data = NSKeyedArchiver.archivedDataWithRootObject(accounts.populateFromCalendars())
var userDefaults = NSUserDefaults.standardUserDefaults();
userDefaults.setObject(data, forKey: "test_storeAccounts_array");