Accessing sprite nodes added in loop to call class inherited method? - ios

I have created a number of SKSpriteNodes, adding them to the scene in a for loop.
func addBlocks(number : Int) {
for _ in 1...number {
let block = Obstacle()
addChild(block)
}
}
Since they're created in this loop, I can't call block.myMethod(). I can do of course callchildNodeWithName("block") to perform things it inherits by being a node, like .removeFromParent().
What should I do if I want to call the method implemented in the Obstacle class (subclass of SKSpriteNode).

Obstacle subclass of SKSpriteNode
Let's say you have Obstacle defined as follow.
class Obstacle: SKSpriteNode {
func explode() { }
}
Obstacles with names
If you add an Obstacle to the scene you can add a name to the node like
func addBlocks(number : Int) {
for i in 1...number {
let block = Obstacle()
block.name = "block_\(i)" // <-- like here
addChild(block)
}
}
Retrieving an SKNode by name and casting it to Obstacle
Next when you invoke self.childNodeWithName(...) you get something like this SKNode?.
It means you can receive nil or something that is an SKNode. If you believed (like in this case) that the returned object is something more specific of an SKNode (like Obstacle) the you can try to cast it to your custom class type.
func explodeObstacle(i: Int) {
guard let obstacle = self.childNodeWithName("block_\(i)") as? Obstacle else {
debugPrint("Could not find an Obstacle with name block_\(i)")
return
}
obstacle.explode()
}
In the code below, if the cast does fail (which mean the returned value is not an Obstacle, then an error message is printed on the console).
Otherwise if the value is successfully casted do Obstacle, you can invoke any method you added to Obstacle.

Related

Extend SKAction to override timingMode

I have many SKActions in a SpriteKit project. The default timingMode for SKActions is "linear". Is it possible to use an extension to override this timingMode default to e.g. "easeInEaseOut" so ALL SKActions have timingMode = easeInEaseOut?
I have tried various "extension" styles but none will compile - normally returning "'timingMode' used within its own type" or "Initializer 'init()' with Objective-C selector 'init' conflicts with implicit initializer 'init()' with the same Objective-C selector"
The docs don't seem to give any examples of this, but surely this would be a useful thing to be able to do? Especially when you have hundreds of SKActions in your game?
Pick your poison, one extends the action to allow you to quickly call .easeInEaseOut timing mode, the other extends SKNode to allow you to run using a specific timing mode.
There is no way to change default behavior, the only other way is to create your own static methods for every action that exists, which can become cumbersome.
extension SKAction
{
//e.g. SKAction.move(to: CGPoint.zero, duration: 10).easeInEaseOut()
func easeInEaseOut() -> SKAction
{
self.timingMode = .easeInEaseOut
return self
}
}
extension SKNode
{
func runWithEaseInEaseOut(action:SKAction,withKey key: String = "")
{
action.timingMode = .easeInEaseOut
if key != ""
{
self.run(action,withKey:key)
}
else
{
self.run(action)
}
}
}

Updating multiple Spritekit nodes with the same name

I am currently making a game with Spritekit & Swift3 for the first time; it is a 2D, side-scrolling endless runner. I'm trying to have certain nodes move with my camera, but the problem is that they will be procedurally generated, belonging to the same class, so I need them all to have the same name. Here's the relevant sections of code, before I go any further:
(All variables have been initialized)
//move these nodes with the camera
private var ChoosePieces: ChoosePiecesClass?;
override func didMove(to view: SKView) {
initializeGame();
}
override func update(_ currentTime: TimeInterval) {
enumerateChildNodes(withName: "ChoosePieces") {
node, stop in
_ = node as! SKSpriteNode
self.ChoosePieces?.moveWithCamera();
}
}
private func initializeGame() {
ChoosePieces = childNode(withName: "ChoosePieces") as? ChoosePiecesClass;
}
I have tested the moveWithCamera() function and it works well--it simply increments the node's x-value by 10, moving at the same pace as the camera so that the node remains on screen at the same location.
My problem is that I'm not exactly sure how to use enumerateChildNodes(withName: "String") with my nodes, so that it will recognize all of them and move them. Right now the pieces just stay still. I found out about this function from another person's post--he/she was trying to spawn "enemies" in his/her game. Thanks in advance!
Usually with enumerateChildNodes you do something with the node that is returned e.g. modifying your own example:
override func update(_ currentTime: TimeInterval) {
enumerateChildNodes(withName: "ChoosePieces") {
node, stop in
if let piece = node as? SKSpriteNode { // Cast to Sprite if we can
piece.moveWithCamera()
}
}
}
So for every node that is returned, we cast it to a new SKSpriteNode called 'piece (if we can - using the as? operator - because enumerateChildNodes returns SKNodes) ' and then call moveWithCamera on the 'piece' node.
(the line piece.moveWithCamera() I made up - your original code appeared to be calling a class method(?) to do something. You might want self.moveWithCamera(piece) etc)
In your code, you did nothing with the nodes returned by your enumeration.

Swift 2.x: searching the node tree for multiple hits

I want to search my SKScene class for childNodes that begin with "spaceship". Basically I have several spaceship nodes named "spaceship1", "spaceship2", "spaceship3", etc...
However I'm not getting the syntax right. This:
self.subscript("spaceship[0-9]")
results to :
Expected ',' separator
And this:
self.objectForKeyedSubscript("spaceship[0-9]")
Results to :
'objectForKeyedSubscript' is unavailable: use subscripting
There's a better approach to this problem than assigning tags like "spaceship" + counter.
The Spaceship class
Yes, for a number of reasons you should create a Spaceship class, like this
class Spaceship: SKSpriteNode {
init() {
let texture = SKTexture(imageNamed: "spaceship")
super.init(texture: texture, color: .clearColor(), size: texture.size())
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
Retrieving all the spaceships
class GameScene:SKScene {
var spaceships: [Spaceship] {
return self.children.flatMap { $0 as? Spaceship }
}
}
Why a custom class is better than "numbered tags"
For several reasons
You don't get crazy assigning a new tag-with-counter value to each new sprite that should act like a Spaceship
You can add behaviour to your spaceship entity simply adding methods to the Spaceship class.
The compiler will block you if you erroneously use another node as a spaceship
You code is cleaner
This is a great handy reference for working with Strings in Swift.
http://useyourloaf.com/blog/swift-string-cheat-sheet/
As per that site
let spaceshipString = "spaceship1"
spaceshipString.hasPrefix("spaceship") // true
spaceshipString.hasSuffix("1") // true
With that in mind, you can just enumerate through all your nodes to find the ones with spaceship by doing the following.
func findSpaceShipNodes() {
self.enumerateChildNodesWithName("//*") {
node, stop in
if (( node.name?.hasSuffix("spaceship") ) != nil) {
// Code for these nodes in here //
}
}
}

How to know if an object is an instance of subclass or superclass

I have 2 classes:
class Parent
{
func a() {
self.b()
}
func b() {
// I want to check here
if self is Parent // warning "'is' test is always true"
{
log("instance of parent")
}
}
}
class Child:Parent
{
}
I want to check like this
//
var child = Child()
child.a() // don't see log
var parent = Parent()
parent.a() // see log
I know that I can create a method like description in superclass, and override it in subclass.
I wonder if Swift can check it without implement description
Thanks for your help
Its really simple, use is keyword.
if child is Child
This can be accomplished using the as type cast operator:
var child = Child()
if let child = child as? Child {
//you know child is a Child
} else if let parent = child as? Parent {
//you know child is a Parent
}
There is also the is keyword:
if child is Child {
//is a child
}
Note that in your code you're seeing a warning using is - it will always be true, because comparing self to Parent from within class Parent will always be true. If you were comparing it against some other instance of a class instead of self, or if you were comparing against some other type besides Parent, this warning would disappear.
I would recommend reading more about this in the Swift Programming Language book, found on the iBooks Store - see the Type Casting chapter.
You get the warning because the the function is attached to the class it's defined within. As such, the compiler already knows ahead of time what type self is. But your design is not a good one at all. For example, let's define a printMe function on the parent:
class Parent
{
func printMe() {
if self is Parent {
print("Parent")
}
}
}
class Child: Parent {}
let child = Child()
child.printMe() // Output: Parent
Okay, the Child class inherits printMe from Parent, so let's override it:
class Child: Parent {
func printMe() {
if self is Child {
print("Child")
}
}
}
And you have to redefine printMe for every subclass. In each case, the compiler already know what class the function belongs to so why bother doing the is test?
The proper design pattern is to use a protocol:
class Parent: MyProtocol {}
class Child: Parent {}
protocol MyProtocol {
func printMe()
}
extension MyProtocol {
func printMe() {
if let me = self as? Child {
print("Child")
} else if let me = self as? Parent {
print("Parent")
}
// Do something with `me` or you gonna get another warning
}
}
let parent = Parent()
let child = Child()
parent.printMe()
child.printMe()
The answer should be more simple, so it handles unexpected errors. ie. maybe you added a new subclass, but you forgot to update the switch statement, hence incorrectly printing Parent.
The quick example I gave above, should either not print anything, or print Unexpected Class or something like that so everything remains true, consistent, and future-proof. Seems like a no brainer to check the boolean child is Child but you gotta do that carefully in a timely manner because the is keyword always returns true for subclasses (hence, the warning mentioned in the question 'is' test is always true occurs when doing self is Parent)
Here is my solution:
class Parent {
// MARK: Main method
func printClass() {
print(className)
}
// MARK: Helper methods
var isParent: Bool {
return type(of: self) === Parent.self
}
static var className: String {
return String(describing: self)
}
var className: String {
return type(of: self).className
}
// MARK: Implementation to recognize Parent, Child and any other Subclass
// Approach #1: Identify if "self" == "Parent"
func printClassOrSubclass() {
guard isParent else {
// Case #1: Class of "self" != "Parent", so "self" must be a subclass of "Parent"
print("\(className), subclass of Parent")
return
}
// Case #2: Class of "self" == "Parent", its not a subclass
printClass()
}
// Approach #2: Identify what class is "self" exactly. This approach is useful for calling a method or do something with "self" as a subclass instance (ie. "child == self as! Child")
func printClassOrSubclassWithDowncasting() {
guard !isParent else {
// Case #1: Class of "self" == "Parent", so "self" is not a subclass of "Parent"
printClass()
return
}
// Switch #1: Attempt to identify the exact class of "self", which is definitely a subclass of "Parent" by now.
switch self {
case let child as Child where className == Child.className:
// Case #2: Class of "self" == "Child"
child.printChildClass()
// In each case of Switch #1, returning is important so we dont continue to the Switch #2 by accident
return
default:
// Switch #1 failed to identify the exact class of self. Code will continue with Switch #2
break
}
// Switch #2: Attempt to identify if "self" is a subclass of a subclass of Parent (aka, subclass of "Child")
switch self {
case let subChild as Child:
// Case #3: Class of "self" != "Child" but is a subclass of "Child". (Maybe a different dev introduced "GrandChild" Class and forgot to tell you, classic)
print("\(className), unexpected subclass of Child")
subChild.printChildClass()
default:
// Case #4: Class of "self" could not be identified at all, but its still a subclass of "Parent". (Maybe marketing is testing an "Uncle" Class?)
print("\(className), unexpected subclass of Parent")
break
}
}
}
class Child: Parent {
func printChildClass() {
print("\(className), subclass of \(String(describing: class_getSuperclass(type(of: self))!))")
}
}
// Unexpected Subclasses
class GrandChild: Child {}
class Uncle: Parent {}
Test:
let parent = Parent()
parent.printClassOrSubclass()
parent.printClassOrSubclassWithDowncasting()
print("\n")
let child = Child()
child.printClassOrSubclass()
child.printClassOrSubclassWithDowncasting()
print("\n")
let grandChild = GrandChild()
grandChild.printClassOrSubclass()
grandChild.printClassOrSubclassWithDowncasting()
print("\n")
let uncle = Uncle()
uncle.printClassOrSubclass()
uncle.printClassOrSubclassWithDowncasting()
Result:
Parent
Parent
Child, subclass of Parent
Child, subclass of Parent
GrandChild, subclass of Parent
GrandChild, unexpected subclass of Child
GrandChild, subclass of Child
Uncle, subclass of Parent
Uncle, unexpected subclass of Parent
Notes:
I did say simple, but I wanted to make a thorough response addressing most cases. That said, you can just adjust/simplify the code for your specific use case. Also the 2 switch statements could be combined into 1, but you must have the switch cases within in perfect order (must always check for exact class, before checking subclass), otherwise you ll get wrong results. For my answer, I was having in mind that multiple devs could be handling and adding new classes and subclasses. So, imo the 2 switches should encourage having a better structure and less confusion for a longer term.

iOS Swift - Accessing method in Child from Parent

In my app I have a Storyboard with a bunch of elements laid out. I am setting properties of these elements from "ViewController.swift".
On the storyboard are two UIViews, which have been subclassed to allow for drawing methods. They are to be used as "signature" fields, where a user can sign their signature into the box. This is working fine.
One of the subclassed methohds of the UIViews is "eraseBitmap" which clears the UIView of the drawing.
class SigView: UIView {
...
func eraseBitmap() {
path = nil
cache = nil
}
}
I have a button that calls a function "erase" in the parent VC.
I have tried doing
func erase() {
SigView.eraseBitmap()
}
However that generates an error saying that I'm missing an argument. eraseBitmap, however, accepts no arguments.
If I add an argument, regardless what it is, I get a "SigView not convertible to..." error.
Any ideas, or a better way of coding this part?
Your SigView class defines a method eraseBitmap() - something like:
class SigView : UIView {
func eraseBitmap () { /* ... */ }
}
You'll apply this method to instances of SigView. So, somewhere you've got an instance of SigView in your UI, like:
var aView : SigView = ...
You'll then use:
func erase () {
aView.eraseBitmap()
}
or perhaps
func erase (view:SigView) {
view.eraseBitmap()
}
The error you are getting is caused by attempting to invoke a non-class method on a class. Non-class methods can only be invoked on instances of classes.

Resources