How can I load singleton class properties? - swift

I'am making a game, where I want to use Settings singleton class. I've got this sample of code from internet. But have a problem with loading this class. Can't understand how can I load these properties after saving.
class Settings: NSObject, NSCoding {
static let DocumentDirectory = FileManager().urls(for: .documentDirectory, in: .userDomainMask).first!
static let ArchiveURL = DocumentDirectory.appendingPathComponent("settings")
static let shared = Settings()
var level: Int = 1
var moves: Int = 0
var music: Bool = true
override init() {
super.init()
}
required init?(coder aDecoder: NSCoder) {
level = aDecoder.decodeInteger(forKey: SNames.CurrentLevelSName)
music = aDecoder.decodeBool(forKey: SNames.MusicSName)
}
func encode(with aCoder: NSCoder) {
aCoder.encode(level, forKey: SNames.CurrentLevelSName)
aCoder.encode(music, forKey: SNames.MusicSName)
}
func loadGame() -> Settings? {
return NSKeyedUnarchiver.unarchiveObject(withFile: Settings.ArchiveURL.path) as? Settings
}
func save() {
let isSuccessfulSave = NSKeyedArchiver.archiveRootObject(self, toFile: Settings.ArchiveURL.path)
if !isSuccessfulSave {
print("Failed to save Settings...")
}
}
}

You have to do a if let to get the settings and then you can access the level, moves and other properties. Heres some code.
if let settings = Settings.shared.loadGame() {
print(settings.level)
}
This will load the settings and print the level.

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

Custom Class Unarchive is nil in Cocoa Swift

I am trying to save and retrieve a custom class to UserDefaults in my macOS app. I am getting nil for newData
class countClass: NSObject, NSCoding {
var leftClickCount : Int = 0
init(leftClickCount: Int) {
self.leftClickCount = leftClickCount
super.init()
}
func encode(with coder: NSCoder) {
coder.encode(self.leftClickCount, forKey: "leftClickCount")
}
required convenience init?(coder decoder: NSCoder) {
guard let leftClickCount = decoder.decodeObject(forKey: "leftClickCount") as? Int
else {
return nil
}
self.init(
leftClickCount: leftClickCount
)
}
}
class ViewController: NSViewController {
override func viewDidLoad() {
super.viewDidLoad()
let leftC = countClass(leftClickCount: 25)
let ud = UserDefaults.standard
let archivedData = NSKeyedArchiver.archivedData(withRootObject: leftC)
ud.set(archivedData, forKey: "data")
ud.synchronize()
let tempData = ud.object(forKey: "data") as! Data
let newData = NSKeyedUnarchiver.unarchiveObject(with: tempData) as! countClass // Getting nil here
}
}
I was able to fix this problem by changing from:
decoder.decodeObject(forKey: "leftClickCount") as? Int
with:
decoder.decodeInteger(forKey: "leftClickCount")

Swift objects array to plist file

I am trying to save my object's array to array.plist but I get the following error:
Thread 1: signal SIGABRT error
My object class looks like this:
class Note {
// MARK: Properties
var title: String
var photo: UIImage?
var text: String
// MARK: Initialization
init?(title: String, photo: UIImage?, text: String) {
// Initialize stored properties.
self.title = title
self.photo = photo
self.text = text
// Initialization should fail if there is no name or if the rating is negative.
if title.isEmpty{
return nil
}
}
func encodeWithCoder(aCoder: NSCoder!) {
aCoder.encodeObject(title, forKey:"title")
aCoder.encodeObject(text, forKey:"text")
aCoder.encodeObject(photo, forKey:"photo")
}
init (coder aDecoder: NSCoder!) {
self.title = aDecoder.decodeObjectForKey("title") as! String
self.text = aDecoder.decodeObjectForKey("text") as! String
self.photo = aDecoder.decodeObjectForKey("photo") as! UIImage
}
}
In the controller, I try to save the array with the Notes object like this:
notes = [Notes]()
notes.append(note)
let paths = NSSearchPathForDirectoriesInDomains(NSSearchPathDirectory.DocumentDirectory,NSSearchPathDomainMask.AllDomainsMask, true)
let path: AnyObject = paths[0]
let arrPath = path.stringByAppendingString("/array.plist")
NSKeyedArchiver.archiveRootObject(notes, toFile: arrPath)
Not all the properties in your class are not optional, yet when you retrieve them from the plist, you are unwrapping all of them. This might cause your code to crash.
For example, if the photo is nil and you saved the object, when you are retrieving it, you are unwrapping it self.photo = aDecoder.decodeObjectForKey("photo") as! UIImage, which will crash if you did not save anything there.
Try removing the unwrapping and check again for your crash. Even if this was not the cause of your crash, it will cause a crash at some point.
If this does not fix your problem, please paste the complete error log so it is a bit more clear what is happening.
For swift 5. You can save an array of custom classes to a .plist file that inherits from NSObject and NSSecureCoding.
If we create a custom class called Person:
import Foundation
class Person: NSObject, NSSecureCoding {
//Must conform to NSSecureCoding protocol
public class var supportsSecureCoding: Bool { return true } //set to 'true'
//just some generic things to describe a person
private var name:String!
private var gender:String!
private var height:Double!
//used to create a new instance of the class 'Person'
init(name:String, gender:String, height:Double) {
super.init()
self.name = name
self.gender = gender
self.height = height
}
//used for NSSecureCoding:
func encode(with coder: NSCoder) {
coder.encode(name, forKey: "name") //encodes the name to a key of 'name'
coder.encode(gender, forKey: "gender")
coder.encode(height, forKey: "height")
}
//used for NSSecureCoding:
required init?(coder: NSCoder) {
super.init()
self.name = (coder.decodeObject(forKey: "name") as! String)
self.gender = (coder.decodeObject(forKey: "gender") as! String)
self.height = (coder.decodeObject(forKey: "height") as! Double)
}
//created just to print the data from the class
public override var description: String { return String(format: "name=%#,gender=%#,height%f", name, gender, height) }
}
Now we can create functions to save and load from a .plist file in the ViewController class:
We need to gather data from the directory system of the device:
func documentsDirectory()->String {
let paths = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)
let documentsDirectory = paths.first!
return documentsDirectory
}
func dataFilePath ()->String{
return self.documentsDirectory().appendingFormat("/your_file_name_here.plist")
}
function to save the array:
func saveData(_ people:[Person]) {
let archiver = NSKeyedArchiver(requiringSecureCoding: true)
archiver.encode(people, forKey: "your_file_name_here")
let data = archiver.encodedData
try! data.write(to: URL(fileURLWithPath: dataFilePath()))
}
function to load the array:
func loadData() -> [Person] {
let path = self.dataFilePath()
let defaultManager = FileManager()
var arr = [Person]()
if defaultManager.fileExists(atPath: path) {
let url = URL(fileURLWithPath: path)
let data = try! Data(contentsOf: url)
let unarchiver = try! NSKeyedUnarchiver(forReadingFrom: data)
//Ensure the unarchiver is required to use secure coding
unarchiver.requiresSecureCoding = true
//This is where it is important to specify classes that can be decoded:
unarchiver.setClass(Person.classForCoder(), forClassName: "parentModule.Person")
let allowedClasses =[NSArray.classForCoder(),Person.classForCoder()]
//Finally decode the object as an array of your custom class
arr = unarchiver.decodeObject(of: allowedClasses, forKey: "your_file_name_here") as! [Person]
unarchiver.finishDecoding()
}
return arr
}
In the ViewController class:
override func viewDidLoad() {
super.viewDidLoad()
let testPerson = Person(name: "Bill", gender: "Male", height: 65.5)
let people:[Person] = [testPerson]
//Save the array
saveData(people)
//Load and print the first index in the array
print(loadData()[0].description)
}
Output:
[name=Bill,gender=Male,height=65.5000000]

Unable to decode a NSObject in Swift

I am having a problem decoding an object after I encode it in Swift.
Here is my class:
class Player: NSObject, NSCoding {
var score:Int = 0
init(difficulty: Int!) {
super.init()
}
required init(coder aDecoder: NSCoder) {
score = aDecoder.decodeObjectForKey("score") as Int
}
func encodeWithCoder(aCoder: NSCoder) {
aCoder.encodeObject(score, forKey: "score")
}
Here is when I am encoding and decoding:
let data = NSKeyedArchiver.archivedDataWithRootObject(player)
let newPlayer = NSKeyedUnarchiver.unarchiveObjectWithData(data) as Player
It crashes everytime when I try to unarchive it. Anybody have any reasons why?
P.S. I have no idea why my code isn't formatted. I have it indented 4 spaces over! Sorry about that!
Here is what I did to get it working:
Created an instance of player in my App Delegate
You can call this anywhere if your application UIApplication.sharedApplication().player
For storing arrays of Player objects here are the NSObject classes:
import Foundation
class Player: NSObject {
var score:Int = 0
func encodeWithCoder(aCoder: NSCoder!) {
aCoder.encodeInteger(score, forKey: "score")
}
init(coder aDecoder: NSCoder!) {
score = aDecoder.decodeIntegerForKey("score")
}
override init() {
}
}
class ArchivePlayer:NSObject {
var documentDirectories:NSArray = []
var documentDirectory:String = ""
var path:String = ""
func savePlayerArray(#playerArray: [Player]) {
documentDirectories = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true)
documentDirectory = documentDirectories.objectAtIndex(0) as String
path = documentDirectory.stringByAppendingPathComponent("playersArray.archive")
if NSKeyedArchiver.archiveRootObject(playerArray, toFile: path) {
println("Success writing to file!")
} else {
println("Unable to write to file!")
}
}
func retrievePlayerArray() -> NSObject {
var dataToRetrieve = [Player]()
documentDirectories = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true)
documentDirectory = documentDirectories.objectAtIndex(0) as String
path = documentDirectory.stringByAppendingPathComponent("playersArray.archive")
if let dataToRetrieve2 = NSKeyedUnarchiver.unarchiveObjectWithFile(path) as? [Player] {
dataToRetrieve = dataToRetrieve2
}
return(dataToRetrieve)
}
}
Here is the ViewController:
import UIKit
class ViewController: UIViewController {
var player1 = Player()
var player2 = Player()
var player3 = Player()
var playerArchiveArray = [Player]()
override func viewDidLoad() {
super.viewDidLoad()
player1.score = 12
player2.score = 22
player3.score = 32
playerArchiveArray = [player1, player2, player3]
ArchivePlayer().savePlayerArray(playerArray: playerArchiveArray)
var playerRetrieveArray = ArchivePlayer().retrievePlayerArray() as [Player]
for player in playerRetrieveArray {
println(player.score)
}
}
}
This should work. I will post a separate answer if you wish to save an array of Player objects.
Two NSObject classes:
import Foundation
class Player: NSObject {
var score:Int = 0
func encodeWithCoder(aCoder: NSCoder!) {
aCoder.encodeInteger(score, forKey: "score")
}
init(coder aDecoder: NSCoder!) {
score = aDecoder.decodeIntegerForKey("score")
}
override init() {
}
}
class ArchivePlayer:NSObject {
var documentDirectories:NSArray = []
var documentDirectory:String = ""
var path:String = ""
func savePlayer(#player: Player) {
documentDirectories = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true)
documentDirectory = documentDirectories.objectAtIndex(0) as String
path = documentDirectory.stringByAppendingPathComponent("players.archive")
if NSKeyedArchiver.archiveRootObject(player, toFile: path) {
println("Success writing to file!")
} else {
println("Unable to write to file!")
}
}
func retrievePlayer() -> NSObject {
var dataToRetrieve = Player()
documentDirectories = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true)
documentDirectory = documentDirectories.objectAtIndex(0) as String
path = documentDirectory.stringByAppendingPathComponent("players.archive")
if let dataToRetrieve2 = NSKeyedUnarchiver.unarchiveObjectWithFile(path) as? Player {
dataToRetrieve = dataToRetrieve2 as Player
}
return(dataToRetrieve)
}
}
And then in the ViewController:
import UIKit
class ViewController: UIViewController {
var player = Player()
override func viewDidLoad() {
super.viewDidLoad()
player.score = 22
ArchivePlayer().savePlayer(player: player)
let playerToRetrieve = ArchivePlayer().retrievePlayer() as Player
println(playerToRetrieve.score)
}
}
This demonstrates archiving and unarchiving to file. Printing "Success writing to file!" to demonstrate the archiving. Printing the stored object player.score to demonstrate unarchiving.