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

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.

Related

store array of custom class with nested custom class to standardUserDefaults

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")

Encoding/Decoding an array of objects which implements a protocol in Swift 2

I have got a class that inherits from NSObject and I want it to be NSCoding compliant. But I ran into trouble while encoding an array of objects which should implement a protocol.
protocol MyProtocol {
var myDescription: String { get }
}
class DummyClass: NSObject, NSCopying, MyProtocol {
var myDescription: String {
return "Some description"
}
func encodeWithCoder(aCoder: NSCoder) {
// does not need to do anything since myDescription is a computed property
}
override init() { super.init() }
required init?(coder aDecoder: NSCoder) { super.init() }
}
class MyClass: NSObject, NSCoding {
let myCollection: [MyProtocol]
init(myCollection: [MyProtocol]) {
self.myCollection = myCollection
super.init()
}
required convenience init?(coder aDecoder: NSCoder) {
let collection = aDecoder.decodeObjectForKey("collection") as! [MyProtocol]
self.init(myCollection: collection)
}
func encodeWithCoder(aCoder: NSCoder) {
aCoder.encodeObject(myCollection, forKey: "collection")
}
}
For aCoder.encodeObject(myCollection, forKey: "collection") I get the error:
Cannot convert value of type '[MyProtocol]' to expected argument type 'AnyObject?'
OK, a protocol obviously is not an instance of a class and so it isn't AnyObject? but I've no idea how to fix that. Probably there is a trick that I'm not aware? Or do you do archiving/serialization differently in Swift as in Objective-C?
There's probably a problem with let collection = aDecoder.decodeObjectForKey("collection") as! [MyProtocol], too but the compiler doesn't complain yet…
I've just found the solution myself: The key is to map myCollection into [AnyObject] and vice-versa, like so:
class MyClass: NSObject, NSCoding {
let myCollection: [MyProtocol]
init(myCollection: [MyProtocol]) {
self.myCollection = myCollection
super.init()
}
required convenience init?(coder aDecoder: NSCoder) {
let collection1 = aDecoder.decodeObjectForKey("collection") as! [AnyObject]
let collection2: [MyProtocol] = collection1.map { $0 as! MyProtocol }
self.init(myCollection: collection2)
}
func encodeWithCoder(aCoder: NSCoder) {
let aCollection: [AnyObject] = myCollection.map { $0 as! AnyObject }
aCoder.encodeObject(aCollection, forKey: "collection")
}
}
I know your title specifies Swift 2, but just for reference, for a similar problem I was working on, I found that in Swift 3, you don't need to convert anymore to AnyObject.
The following works for me in Swift 3 (using your example):
class MyClass: NSObject, NSCoding {
let myCollection: [MyProtocol]
init(myCollection: [MyProtocol]) {
self.myCollection = myCollection
super.init()
}
required convenience init?(coder aDecoder: NSCoder) {
let collection = aDecoder.decodeObject(forKey: "collection") as! [MyProtocol]
self.init(myCollection: collection)
}
func encodeWithCoder(aCoder: NSCoder) {
aCoder.encode(aCollection, forKey: "collection")
}
}

How to implement a failable initializer for a class conforming to NSCoding protocol in Swift?

How to implement a failable initializer for a class conforming to NSCoding protocol?
I'm getting the following errors:
1. Line override init() {}: Property 'self.videoURL' not initialized at implicitly generated super.init call
2. Line return nil: All stored properties of a class instance must be initialized before returning nil from an initializer
I've seen Best practice to implement a failable initializer and All stored properties of a class instance must be initialized before returning nil which helped me a lot, but since my class also conforms to NSCoding protocol I don't know how to implement a failable initializer in my case.
Any suggestions on how to implement a failable initializer?
class CustomMedia : NSObject, NSCoding {
var videoTitle: String?
let videoURL: NSURL!
override init() {}
init?(title: String?, urlString: String) {
// super.init()
if let url = NSURL(string: urlString) {
self.videoURL = url
self.videoTitle = title
} else {
return nil
}
}
func encodeWithCoder(aCoder: NSCoder) {
aCoder.encodeObject(self.videoTitle, forKey: PropertyKey.videoTitle)
aCoder.encodeObject(self.videoURL, forKey: PropertyKey.videoURL)
}
required init(coder aDecoder: NSCoder) {
videoTitle = aDecoder.decodeObjectForKey(PropertyKey.videoTitle) as? String
videoURL = aDecoder.decodeObjectForKey(PropertyKey.videoURL) as! NSURL
}
}
UPDATE: This was addressed in the Swift 2.2 update, and you no longer have to assign a nil value and call super prior to failing an initializer.
For version of Swift prior to 2.2:
You actually have to initialize your values before returning nil, unfortunately.
Here's the working solution:
class CustomMedia : NSObject, NSCoding {
var videoTitle: String?
var videoURL: NSURL!
init?(title: String?, urlString: String) {
super.init()
if let url = NSURL(string: urlString) {
self.videoURL = url
self.videoTitle = title
} else {
self.videoURL = nil
self.videoTitle = nil
return nil
}
}
func encodeWithCoder(aCoder: NSCoder) {
aCoder.encodeObject(self.videoTitle, forKey: PropertyKey.videoTitle)
aCoder.encodeObject(self.videoURL, forKey: PropertyKey.videoURL)
}
required init(coder aDecoder: NSCoder) {
videoTitle = aDecoder.decodeObjectForKey(PropertyKey.videoTitle) as? String
videoURL = aDecoder.decodeObjectForKey(PropertyKey.videoURL) as! NSURL
}
}

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

Passing Multiple Objects To WatchKit In A NSUserDefaults

With the help of some great tutorials and users here, I've had success implementing SwiftyJSON in my app and getting a basic WatchKit app built alongside. My last hurdle to pass is getting my whole set of parsed JSON data to be passed to WatchKit, as to allow me to choose from a cell in a TableView and pull up more specific detail on a piece of criteria.
I'm parsing JSON data in my Minion.swift file, like so;
import UIKit
class Minion {
var name: String?
var age: String?
var height: String?
var weight: String?
class func fetchMinionData() -> [Minion] {
let dataURL = NSURL(string: "http://myurl/json/")
var dataError: NSError?
let data = NSData(contentsOfURL: dataURL!, options: NSDataReadingOptions.DataReadingMappedIfSafe, error: &dataError)
let minionJSON = JSONValue(data)
var minions = [Minion]()
for minionDictionary in minionJSON {
minions.append(Minion(minionDetails: minionDictionary))
}
return minions
}
init(minionDetails: JSONValue) {
name = minionDetails["san"].string
age = minionDetails["age"].string
height = minionDetails["height"].string
weight = minionDetails["free"].string
}
}
For my iOS app, this is working well to populate my UITableView and subsequent Detail View. I have my ViewController.Swift like so;
import UIKit
class ViewController: UITableViewController {
let minions: [Minion]
required init(coder aDecoder: NSCoder!) {
minions = Minion.fetchMinionData()
super.init(coder: aDecoder)
}
override func viewDidLoad() {
super.viewDidLoad()
let defaults = NSUserDefaults(suiteName: "group.com.mygroup.data")
let key = "dashboardData"
defaults?.setObject(minions, forKey: key)
defaults?.synchronize()
}
// MARK: Table view data source
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
}
I've truncated much of the code as I don't believe it's relevant to WatchKit. In the WatchKit extension, I have my InterfaceController.swift like so;
import WatchKit
class InterfaceController: WKInterfaceController {
#IBOutlet weak var minionTable: WKInterfaceTable!
let defaults = NSUserDefaults(suiteName: "group.com.mygroup.data")
var dashboardData: String? {
defaults?.synchronize()
return defaults?.stringForKey("dashboardData")
}
let minions = ???
When I run the iOS app, it throws me the error "Property list invalid for format: 200 (property lists cannot contain objects of type 'CFType')" because I am passing the whole set of JSON data as "minions." If I set my NSUserDefaults key to "minions[0].name" it will pass the single string, but passing the whole set of data so the WatchKit table can allow me to choose a row seems to be evading me.
In advance, as always, I am most grateful.
Your Minion class need to implement the NSCoding. Then in your view controller you need to transfer your Minion object to NSData object.
class Minion: NSObject, NSCoding {
.....
init(coder aDecoder: NSCoder!) {
aCoder.encodeObject(name, forKey: "name")
aCoder.encodeObject(age, forKey: "age")
aCoder.encodeObject(height, forKey: "height")
aCoder.encodeObject(weight, forKey: "weight")
}
func encodeWithCoder(aCoder: NSCoder) {
name = aDecoder.decodeObjectForKey("name") as String
age = aDecoder.decodeObjectForKey("age") as String
height = aDecoder.decodeObjectForKey("height") as String
weight = aDecoder.decodeObjectForKey("weight") as String
}
}
In your ViewController class:
NSKeyedArchiver.setClassName("Minion", forClass: Minion.self)
defaults?.setObject(NSKeyedArchiver.archivedDataWithRootObject(minions), forKey: "minions")
If you want to retrieve the data from NSUserDefaults:
if let data = defaults?.objectForKey("minions") as? NSData {
NSKeyedUnarchiver.setClass(Minion.self, forClassName: "Minion")
let minions = NSKeyedUnarchiver.unarchiveObjectWithData(data) as! [Minion]
}