2 Replies
      Latest reply on Oct 22, 2018 4:21 AM by igorland
      igorland Level 1 Level 1 (0 points)

        I am a little bit confused about how to decode an SKSpriteNode.

         

        I have the following SKSpriteNode class:

         

        class Person : SKSpriteNode
            {
              var gender : Gender
              var age : Age
              var positionX : CGFloat!
              var positionY : CGFloat!
              let frontTexture : SKTexture
              let backTexture : SKTexture
        
        
              required convenience init?(coder aDecoder: NSCoder)
              {
                print("INITIALIZED WITH DECODER????")
               
                guard let myPerson = aDecoder.decodeObject(forKey: "person") as? Person
                    else { return nil }
               
                self.init(coder: aDecoder)
              }
        
              func encodeWithCoder(coder: NSCoder)
              {
                coder.encode(self, forKey: "person")
              }
        
              init(gender: Gender, age: Age, positionX: CGFloat, positionY: CGFloat, sceneSize: CGSize)
              {
                self.gender = gender
                self.age = age
               
                self.backTexture = SKTexture(imageNamed: "person_back")
                self.frontTexture = SKTexture(imageNamed: "person_front")
        
                var currentTexture = self.frontTexture
               
                super.init(texture: currentTexture, color: .clear, size: frontTexture.size())
               
                // Position of the person in the scene
                self.position = updatePosition(positionX: positionX, positionY: positionY, sceneSize: sceneSize)
              }

         

        In a MultipeerConnection subclass, I use this to send encoded data (data seems to be archived correctly):

         

        func sendData(dictionaryWithData dictionary: Dictionary<string, any="">, toPeer targetPeers: [MCPeerID])
              {
                let dataToSend = NSKeyedArchiver.archivedData(withRootObject: dictionary)
               
                do
                {
                    try session.send(dataToSend, toPeers: targetPeers, with: MCSessionSendDataMode.reliable)
                }
                catch
                {
                    print("ERROR")
                }
              }

         

        with data made as follows (crowd is an array [Person]):

         

        func makeMessageCrowd() -> Dictionary<string, any="">
              {
                var messageToSend = Dictionary<string, any="">()
                messageToSend["move"] = "start"
                messageToSend["action"] = appDelegate.persons
                return messageToSend
              }

         

        I use the following to receive the data:

         

        func session(_ session: MCSession,
                         didReceive data: Data,
                         fromPeer peerID: MCPeerID)
              {
               
                let message = NSKeyedUnarchiver.unarchiveObject(with: data) as! Dictionary<string, any="">
               
                if let moveType = message["move"] as? String
                {
                    switch(moveType)
                    {
                    case "start":
                        appDelegate.persons = message["action"] as! [Person]
                       
                    default:
                        print("default")
                    }
                }
              }

         

        Right now, it starts initializing object Person with the convenience initializer, and then crashes here:

         

         let message = NSKeyedUnarchiver.unarchiveObject(with: data) as! Dictionary<string, any="">

         

        saying that it found a nil value. However, data seems to have 15000 bytes, so it was passed well.

         

        If instead of a convenience initializer, I use this:

         

         required init?(coder aDecoder: NSCoder)
              {
                //fatalError("NSCoding not supported")
                self.gender = Gender.male
                self.age = Age.young
                self.frontTexture = SKTexture(imageNamed: "person_front")
                self.backTexture = SKTexture(imageNamed: "person_back")
                self.positionX = 0
                self.positionY = 0
        
                super.init(coder: aDecoder)
              }

         

        I do not have a crash, however the array [Person] on the peer device is created using the default values initialized as above, and not as passed.

         

        The culprit is definitely this as it returns nil in the first case:

           

        guard let myPerson = aDecoder.decodeObject(forKey: "person") as? Person
                    else { return nil }

         

        How do I do encode and decode a custom class properly?

         

        Thanks a lot!

        • Re: Encoding and decoding SKSpriteNode
          OOPer Level 8 Level 8 (5,425 points)

          Seems you are misunderstanding how NSCoding works.

           

          Unless you implement NSCoding methods properly, `decodeObject(forKey:)` can never decode an instance of `Person` class, and `encode(_:forKey:)` can never  encode an instance of `Person` class correctly.

           

          Which means, you cannot use `encode(_:forKey)`  with an instance of `Person` to implement `encode(with:)` (not `encodeWithCoder(coder:)`) of Person,

          and you cannot use `decodeObject(forKey:)` to decode an instance of `Person` inside the initializer `init(coder:)` of `Person`.

           

          I cannot show you a concrete example as you are not showing the definition of `Gender` or `Age`, but your `Person` would be something like this, when your want it to comform to `NSCoding` properly:

          class Person : SKSpriteNode {
              var gender : Gender
              var age : Age
              var positionX : CGFloat //<- Do not use implicitly unwrapped Optional
              var positionY : CGFloat //<- Do not use implicitly unwrapped Optional
              let frontTexture : SKTexture
              let backTexture : SKTexture
              
              required init?(coder aDecoder: NSCoder) {
                  print("INITIALIZED WITH DECODER????")
                  self.gender = Gender(rawValue: aDecoder.decodeInteger(forKey: "gender"))! //This may not work
                  self.age = Age(rawValue: aDecoder.decodeInteger(forKey: "age"))! //This may not work
                  self.positionX = CGFloat(aDecoder.decodeDouble(forKey: "positionX"))
                  self.positionY = CGFloat(aDecoder.decodeDouble(forKey: "positionY"))
                  self.frontTexture = aDecoder.decodeObject(forKey: "frontTexture") as! SKTexture
                  self.backTexture = aDecoder.decodeObject(forKey: "backTexture") as! SKTexture
                  super.init(coder: aDecoder) //<- you need to use `super`, `self.init(coder:)` just calls the initializer of `Person` recursively
              }
              
              override func encode(with aCoder: NSCoder) { //<- not `encodeWithCoder(coder: NSCoder)`
                  super.encode(with: aCoder)
                  aCoder.encode(gender.rawValue, forKey: "gender") //This may not work
                  aCoder.encode(age.rawValue, forKey: "age") //This may not work
                  aCoder.encode(Double(positionX), forKey: "positionX")
                  aCoder.encode(Double(positionY), forKey: "positionY")
                  aCoder.encode(frontTexture, forKey: "frontTexture")
                  aCoder.encode(backTexture, forKey: "backTexture")
            
              }
          
              // You may need to fix other parts of your code...
          
          }