frankibanki frankibanki - 3 months ago 8
Swift Question

swift error in didBeginContact if running newer iPhone in simulator

this is the Code in the "didBeginContact" method:

if contact.bodyA.categoryBitMask < contact.bodyB.categoryBitMask
{

if contact.bodyA.node!.name == "0" && contact.bodyB.node!.name == "1000003"{

contact.bodyB.node!.removeFromParent()
}
if contact.bodyA.node!.name == "0" && contact.bodyB.node!.name == "1000004"{

contact.bodyB.node!.removeFromParent()
}

}

else

{
if contact.bodyB.node!.name == "0" && contact.bodyA.node!.name == "1000003"{

contact.bodyA.node!.removeFromParent()
}
if contact.bodyB.node!.name == "0" && contact.bodyA.node!.name == "1000004"{

contact.bodyA.node!.removeFromParent()
}
}


it works in "iPhone 4s" and "iPhone 5" simulation and fail in a higher version with this error message:

"fatal error: unexpectedly found nil while unwrapping an Optional value
(lldb)"

this is the line it marks red:

if contact.bodyA.node!.name == "0" && contact.bodyB.node!.name == "1000004"{


nice to know:
the object with the name "1000004" is a SKSpriteNode I create in the "GameScene" class:
let TestPixel = SKSpriteNode(imageNamed: "6")


the property for this object are in the "touchesBegan" method.
here is the code for that:

TestPixel.name = "1000004"
TestPixel.anchorPoint = CGPointMake(0.5, 0.5)
TestPixel.userInteractionEnabled = false
TestPixel.zPosition = 1000003
TestPixel.size = CGSizeMake(1, 1)
TestPixel.position = CGPointMake(positionInScene.x, positionInScene.y)
TestPixel.physicsBody = SKPhysicsBody(texture: SKTexture(imageNamed: "6"), size: TestPixel.size)
TestPixel.physicsBody?.categoryBitMask = ClickOnCategory
TestPixel.physicsBody?.contactTestBitMask = BuildingsCategory
TestPixel.physicsBody?.affectedByGravity = false
TestPixel.physicsBody?.dynamic = true
TestPixel.physicsBody?.collisionBitMask = 0
addChild(TestPixel)


the error occur if this SpriteNode(name:"1000004") hit another SpriteNode which I create in the "didMoveToView" method, here is the code:

func addBG(){

let background = SKSpriteNode(imageNamed: "0")
background.name = "0"
background.anchorPoint = CGPointMake(0.5, 0.5)
background.zPosition = 1
background.position = CGPointMake(CGRectGetMinX(self.frame)+self.frame.width/4,CGRectGetMaxY(self.frame)-self.frame.height/4)
background.physicsBody = SKPhysicsBody(texture: SKTexture(imageNamed: "0"), size: background.size)
background.physicsBody?.categoryBitMask = BuildingsCategory
background.physicsBody?.affectedByGravity = false
background.physicsBody?.dynamic = true
background.physicsBody?.collisionBitMask = 0
self.addChild(background)

}

addBG()


and in the "didMoveToView" also is:

physicsWorld.contactDelegate = self


the categoryBitMask in the "GameScene" class is set like this:

class GameScene: SKScene, SKPhysicsContactDelegate{

let BuildingsCategory : UInt32 = 0x1 << 1
let ClickOnCategory : UInt32 = 0x1 << 2


yeah ok as I said it works in simulator with "iPhone 4s" && "iPhone 5" but not in higher versions... any ideas why?
thanks

Answer

Yes, I can tell you why:

if contact.bodyA.node!.name == "0" && contact.bodyB.node!.name == "1000003"{
    contact.bodyB.node!.removeFromParent() <----PROBLEM
}
if contact.bodyA.node!.name == "0" && contact.bodyB.node!.name == "1000004"{
    contact.bodyB.node!.removeFromParent()
}

What is happening, is you are hitting your 1st if statement, which does the remove from parent, then you are trying to access it again after. Well if you remove from parent, what is maintaining the node in memory? Probably nothing, so at that first line, it nils out the node. Your code then continues into another if condition, and you are now trying to unwrap a nil. This is where you would use ELSE IF instead of if.

Something else I strongly recommend doing with sprite kit is to never remove nodes during the contact phase. You could have multiple contacts happening, and you may need to handle all of these contact situations, so it is best to keep the node alive.

Instead, whenever you want to remove nodes, place them into a queue (array or set), then on your didFinishUpdate method, loop through the queue and remove all the nodes.

Another trick I do, is I reserve bit 31 on the categoryBitMask to determine if a sprite is alive or dead, then on the didContact methods, I place a guard against it.

E.G.

func didBeginContact(contact: SKPhysicsContact!)
{
    guard let bodies = (a:contact.bodyA,b:contact.bodyB) where a.categoryBitMask < 1 << 31 && b.categoryBitMask < 1 << 31
    else
    {
        return
    }
    // do other work here
}
Comments