Casting SKSpriteNode from .sks File to a Custom Class in iOS8

Hi All.


I've been experimenting with Xcode 7 and the updated SpriteKit Editor. For our current project we wish to design as much of the interface as possible visually. As there is no SKButton, I've created a simple Button class which extends SKSpriteNode


import SpriteKit

class Button: SKSpriteNode
{
    // MARK: Init
    init(texture: SKTexture, size: CGSize)
    {
        super.init(texture: texture, color: UIColor.whiteColor(), size: size)
        userInteractionEnabled = true
    }

    required init?(coder aDecoder: NSCoder)
    {
        super.init(coder: aDecoder)
        userInteractionEnabled = true
    }

    // MARK: User Interaction
    override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?)
    {
        print("Button touchesBegan")
    }

    override func touchesMoved(touches: Set<UITouch>, withEvent event: UIEvent?)
    {
        print("Button touchesMoved")
    }

    override func touchesEnded(touches: Set<UITouch>, withEvent event: UIEvent?)
    {
        print("Button touchesEnded")
    }

    override func touchesCancelled(touches: Set<UITouch>?, withEvent event: UIEvent?)
    {
        print("Button touchesCancelled")
    }
}


and a simple GameScene.sks file which contains a single, colored SKSpriteNode named button.


Now in GameScene.swift I can get a reference to this SKSpriteNode and cast it as a Button (regardless if a custom class is mentioned in the inspector panel or not).


let button = self.childNodeWithName("button") as! Button


Everything works perfectly fine when running on iOS 9, but on iOS 8 I receive the error


Could not cast value of type 'SKSpriteNode' to 'buttonTestProject.Button'


I am aware that the ability to assign a custom class in SpriteKit Editor is new in Xcode7, but the ability to cast a SKSpriteNode to a subclass surely isn't.


An ugly work-around is that the visual SKSpriteNode can be used as a placeholder to create a Button


let button: Button!
if #available(iOS 9, *)
{
  button = self.childNodeWithName("button") as! Button
}
else
{
  let node = self.childNodeWithName("button") as! SKSpriteNode
  button = Button(texture: node.texture!, size: node.size)
  button.position = node.position
  node.removeFromParent()
  addChild(button)
}


Any feedback would be greatly appreciated. I know that from the .sks file the NSCoder init method is called, but I don't see what could be different there for iOS 8. I suppose the issue is that Editor supports custom classes for iOS 9 onwards. Any alternative methods would be greatly appreciated also 😀 Thanks!

Accepted Reply

Hi techspaghetti,


Custom classes are only supported on Xcode 7 and iOS 9. We understand this can be difficult when trying to also support iOS 8. Your workaround looks viable, but be careful to copy all properties that will be important to maintain (e.g., name, zPosition, xScale, yScale, zRotation, etc). Additionally, make sure to insert the button as a child at the same index. Otherwise you may end up with a different draw order when zPositions are identical and SKView's ignoresSiblingOrder = false.


Thank you.

Replies

Hi techspaghetti,


Custom classes are only supported on Xcode 7 and iOS 9. We understand this can be difficult when trying to also support iOS 8. Your workaround looks viable, but be careful to copy all properties that will be important to maintain (e.g., name, zPosition, xScale, yScale, zRotation, etc). Additionally, make sure to insert the button as a child at the same index. Otherwise you may end up with a different draw order when zPositions are identical and SKView's ignoresSiblingOrder = false.


Thank you.

Thanks for the helpful reply!

How does one get custom classes and referenced sks files to work? This simply doens't seem to work, at least not in Swift.

Hi helloniklas,


Are you using iOS 9 and Xcode 7?


Within Xcode 7's scene editor, you're able to define the custom class of a node via its inspector. Reference nodes can be created by either dragging & dropping an existing scene sks file into your level, adding a reference node to your scene and specifying the reference, or within code via SKReferenceNode's init(fileNamed fileName: String).


Thanks.

Yes I got that far, Using Xcode 7 and iOS9. But, when I then try and use the referenced node in the scene it's been dragged into, it won't let me allow to cast that node as the custom class. It will complain about not having blendMode and filter set on it, acn it'll crash, meaning the custom class needs to be a SKScene rather than an SKNode.


All I want to do is have a SKNode in a seperate sks file. This node has various sprite children and the node is a custom class exteending SKNode.


I then drag this sks file in as a reference in my main scene sks file. Assign it a name. When trying to get this node and use as the custom class it is set to, it won't work.

For iOS < 9 you could use this approach to load a scene and convert it to a custom class:(You could adjust it to use a template parameter)


func particleEmitterWithName(name : NSString) -> SKEmitterNode? 
{
     let path = NSBundle.mainBundle().pathForResource(name, ofType: "sks")
     var sceneData = NSData.dataWithContentsOfFile(path!, options: .DataReadingMappedIfSafe, error: nil)
     var archiver = NSKeyedUnarchiver(forReadingWithData: sceneData)
     archiver.setClass(SKEmitterNode.self, forClassName: "SKEditorScene")
     let node = archiver.decodeObjectForKey(NSKeyedArchiveRootObjectKey) as SKEmitterNode?
     archiver.finishDecoding()
     return node
}


Or you could google for "node-archive-delegate", this is a solution on github which allows you to specify the class for the nodes by name.

韩万年

You can do it in iOS9 - https://forums.developer.apple.com/thread/12304