0

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
    }
}
Hedylove
  • 1,724
  • 16
  • 26
  • 1
    [`NSCoder` doesn't understand Swift-only types like tuples and structs. Encode the components of the tuple separately.](http://stackoverflow.com/questions/28929897/swift-encode-tuple-using-nscoding) – Kurt Revis Nov 26 '16 at 06:27
  • Thank you. I did do that suggestion now but I found that after it's saved once, and when I tried to load the collection view, nothing is being displayed again. – Hedylove Nov 26 '16 at 06:39
  • I think I know what the problem is. The JKCollectionView and JKCollectionViewCell had coder and decoder functions. I put a breakpoint to read the values of the objects and realized that the JKButtonNode properties were not set, meaning I should've included coder and decoder functions for JKButtonNode. – Hedylove Nov 26 '16 at 07:12
  • @JozemiteApps did you get this figured out? I'm having a similar amount of "fun" with NSCoding – Confused Dec 22 '16 at 03:29
  • I didn't know but I found out the problem is that you have to apply NSCoding to all the objects that are custom classes. For example, my JKCollectionView and JKCollectionViewCell both had coder and decoder, but the JKButtonNode (which the cells were made of) did not have coding and decoding, so even though it could save the JKCollectionViewCell, it wasn't fully saving since that class was a subclass of JKButtonNode. All classes must conform if you are subclassing from a superclass. – Hedylove Dec 25 '16 at 03:27

0 Answers0