I have a class called JKLevelCollection
which is a subclass of SKNode
. SKNode
is already a class that conforms to NSCoding
and NSCoder
because it's Apple's own class from the SpriteKit framework.
Here's the entire class. You don't have to worry what it does, but you can go down and see the Coder and Decoder functions. The class is basically a UICollectionView
but for SpriteKit games. Also, the JKLevelCollectionCell
class is a subclass of my custom class JKButtonNode
which is a subclass of SKSpriteNode
, which is a subclass of SKNode
.
class JKLevelCollection: SKNode {
//The entire placeholder where the collection of views is placed
var size: SKShapeNode!
//A collection of child views of the parent view
var views = [SKShapeNode]()
//The current view being displayed by the collection
var currentView = 0
//The number of level per view
var levelsPerView = 0
//Used to populate the table with the given rows and columns
var dimensions = (rows: 0, columns: 0)
//An array that holds the levels for each view
var levels = [[JKLevelCollectionCell]]()
/*The amount of space to be from the left and bottom edge of the screen in relation to the first cell of the collection (top left).*/
var edgeOffsets = (left: 270.0, bottom: 1800.0)
//Create a new collection with a certain number of views
init(size: CGRect, views: Int, dimensions: (rows: Int, columns: Int)) {
super.init()
//Creates the entire collection size and adds the minor views to the parent view
self.size = SKShapeNode(rect: size)
for index in 0..<views {
//Creates an SKShapeNode for each view from the parameter
self.views.append(SKShapeNode(rect: size))
self.views[index].lineWidth = 0
self.views[index].position = CGPoint(x: index * 1080, y: 0)
self.addChild(self.views[index])
}
//Creates an empty array for each view that will hold levels.
for _ in 0..<views {
levels.append([])
}
//Assigns the dimensions of the view
self.dimensions = dimensions
levelsPerView = dimensions.rows * dimensions.columns
//Creates levels depending on the number of rows and columns and appends them to each minor view
var tag = 1
for minorViewIndex in 0..<views {
//Position the cells depending on the dimensions
for row in 1...dimensions.rows {
for column in 1...dimensions.columns {
//Set the intial conditions of the levels
let cell = JKLevelCollectionCell(didUnlock: false, tag: tag)
cell.setBackgroundsForState(normal: "LevelButtonN", highlighted: "LevelButtonH", disabled: "LevelButtonD")
cell.setProperties(enabled: false, canPlaySound: true, canChangeState: true, withSounds: (active: buttonPushedSound, inactive: ""))
cell.setPropertiesForTitle(fontName: "TimesNewRomanPS-BoldMT", size: 100, color: SKColor(red: 1.0, green: 1.0, blue: 1.0, alpha: 0.3))
cell.position = CGPoint(x: edgeOffsets.left * Double(column), y: edgeOffsets.bottom - (edgeOffsets.left * Double(row)))
cell.name = "Cell: \(tag)"
tag += 1
levels[minorViewIndex].append(cell)
self.views[minorViewIndex].addChild(cell)
}
}
}
levels[0][0].didUnlock = true
}
//Required to save the object's data with NSKeyedArchiver and NSKeyedUnarchiver
override func encode(with aCoder: NSCoder) {
aCoder.encode(self.size, forKey: "sizeKey")
aCoder.encode(self.views, forKey: "viewsKey")
aCoder.encode(self.currentView, forKey: "currentViewKey")
aCoder.encode(self.levelsPerView, forKey: "levelsPerViewKey")
aCoder.encode(self.dimensions, forKey: "dimensionsKey")
aCoder.encode(self.levels, forKey: "levelsKey")
aCoder.encode(self.edgeOffsets, forKey: "edgeOffsetsKey")
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
self.size = aDecoder.decodeObject(forKey: "sizeKey") as! SKShapeNode!
self.views = aDecoder.decodeObject(forKey: "viewsKey") as! [SKShapeNode]
self.currentView = aDecoder.decodeInteger(forKey: "currentViewKey")
self.levelsPerView = aDecoder.decodeInteger(forKey: "levelsPerViewKey")
if let dimensionsObject = aDecoder.decodeObject(forKey: "dimensionsKey") as? (Int, Int) { self.dimensions = dimensionsObject }
self.levels = aDecoder.decodeObject(forKey: "levelsKey") as! [[JKLevelCollectionCell]]
if let edgeOffsetsObject = aDecoder.decodeObject(forKey: "edgeOffsetsKey") as? (Double, Double) { self.edgeOffsets = edgeOffsetsObject }
}
}
The problem is that the 2 lines in the decoder function that check for an optional value are not being found at all. I'm referring to the dimensionsObject
and edgeOffsetsObject
lines. If I don't check if they're optional, I keep getting "nil found while unwrapping optional value" but you can clearly see where I do save them and you can see that the class does initialize them too.
And this is a problem because this displays 3 views that have 15 levels each. But because no value is found for those variables, nothing is being displayed at all, the user can't even select a level. I am not getting a log error. It's just that the game displays no levels like it used to before implementing NSKeyedArchive/NSKeyedUnarchive.
Also, here are the functions used to save and load the object.
private func saveCollectionViewData() {
let data = NSKeyedArchiver.archivedData(withRootObject: levelCollection)
userDefault.set(data, forKey: "LevelCollectionKey")
}
private func retrieveCollectionViewData() {
if let data = userDefault.object(forKey: "LevelCollectionKey") as? NSData {
levelCollection = NSKeyedUnarchiver.unarchiveObject(with: (data as Data)) as! JKLevelCollection
}
}