Smart KeyPaths in Swift 4 not working properly - swift

I am trying to create an custom action block for an SKSpriteNode, I have the following code:
let sprite = SKSpriteNode(color: SKColor.red, size: CGSize(width: 50, height: 50))
sprite.position = CGPoint(x: 320, y: 240)
self.addChild(sprite)
let animation = SKAction.customAction(withDuration: 0, actionBlock: {
(node, elapsedTime) in
var initialValue : CGFloat?
initialValue = node[keyPath: \SKSpriteNode.position.x] //Extraneous argument label 'keyPath:' in subscript
node[keyPath: \SKSpriteNode.position.x] = 10 //Ambiguous reference to member 'subscript'
})
sprite.run(animation)
I am getting two errors, the first is that the compiler is thinking I have an Extraneous argument of 'keyPath', which is not the case, because if I were to remove it like it suggests, I get this error:
Cannot convert value of type 'ReferenceWritableKeyPath' to expected argument type 'String'
The second error I get is:
Ambiguous reference to member 'subscript'
I am not exactly sure why I am getting all of these errors, and I am not sure exactly what the errors mean. If somebody could explain them to me and propose a solution, that would be great. Thanks in advance.

The keyPath isn't working because node has type SKNode and not SKSpriteNode. You can use a conditional cast to establish that the node is an SKSpriteNode:
let animation = SKAction.customAction(withDuration: 0, actionBlock: {
(node, elapsedTime) in
var initialValue : CGFloat?
if let spritenode = node as? SKSpriteNode {
initialValue = spritenode[keyPath: \SKSpriteNode.position.x]
spritenode[keyPath: \SKSpriteNode.position.x] = 10
}
})

Related

Swift UIImage cant be animated with gravity

I've looked into everything on Google, and that is not much. Is the community for Swift really this small???
I have an image, and on a long-press, I want it to fall down with gravity and hit the bottom of the screen.
The error I get is
Cannot convert value of type 'UIView' to expected argument type
'[UIDynamicItem]'
I have tried UILabel, UIImage, UIImageView, Rect, UIView i get this error on what ever i do.
My goal is to use UIImage or UIImageView.
This is the code I'm using for the animation:
var animator: UIDynamicAnimator!
var gravity: UIDynamicBehavior!
var collision : UICollisionBehavior!
var redBoxView: UIView?
#IBOutlet weak var detailsImageWeather: UIImageView!
override func viewDidLoad() {
super.viewDidLoad()
animator = UIDynamicAnimator(referenceView: self.view)
let imageTap = UILongPressGestureRecognizer(target: self, action: #selector(imageTapped))
detailsImageWeather.addGestureRecognizer(imageTap)
}
#objc func imageTapped() {
var frameRect = CGRect(x: 150, y: 20, width: 60, height: 60)
redBoxView = UIView(frame: frameRect)
redBoxView?.backgroundColor = UIColor.red
self.view.addSubview(redBoxView!)
let image = detailsImageWeather.image // This is what i want to use instead of redBoxView
gravity = UIGravityBehavior(items: redBoxView!)
animator.addBehavior(gravity)
collision = UICollisionBehavior (items: redBoxView!)
collision.translatesReferenceBoundsIntoBoundary = true
animator.addBehavior(collision)
let behavior = UIDynamicItemBehavior(items: [redBoxView!])
behavior.elasticity = 2
}
What am i doing wrong? cant find any more things to try on google
The error
Cannot convert value of type 'UIView' to expected argument type '[UIDynamicItem]'
tells that you need an array of UIDynamicItem. It's easy to miss the little square brackets.
You're actually configuring your UIGravityBehavior and UICollisionBehavior with redBoxView (an object) instead of [redBoxView] (an array). That's why you get the error.
You need to change your configurations to this
gravity = UIGravityBehavior(items: [redBoxView!]) //redBoxView passed in an array
and this
collision = UICollisionBehavior (items: [redBoxView!]) //redBoxView passed in an array

nil exception when initializing an SKSpriteNode Object

I get a nil exception error during runtime and I can not explain myself why there is a problem.
First I declare a SKSpriteNode Object like this
var destinationSign: SKSpriteNode?
and later I define a function that creates this SpriteNode like this
func showDestination( x: CGFloat, y: CGFloat){
destinationSign = SKSpriteNode(fileNamed: "sign_1")
destinationSign?.position.x = x
destinationSign?.position.y = y
addChild(destinationSign!)
}
after that I have a different function and triggers this showDestination function
showDestination(x: 20 , y: 40)
Does anyone have an explanation for this behaviour?
Thanks in advance
Looks like destinationSign is nil when addChild is called. You should double check the name of the image used to init it, capital letters, underscore, etc…
Try also:
if let node = destinationSign as SKSpriteNode {
self.addChild(node)
}
to prevent the nil exception (but remember the node will not be added)
You should also consider to use a more generalized function, that could be used in other contexts, as:
func show (_ node: SKSpriteNode, at point: CGPoint) {
node = SKSpriteNode(fileNamed: "sign_1")
if let node = node as SKSpriteNode {
node.position.x = point.x
node.position.y = point.y
addChild(node)
}
}
But these solution are merely theoretical, given the fact you actually want an error if your sprite is not added to the scene.

Can not access an SKSpriteNode from within a function

I faced a problem, when accessing an object I added to my scene within my collision detection:
Within my GamScene.swift on the top I declared
var passenger: PassengerNode!
And I trigger a spawnPassenger method, which is looking like this
func spawnPassenger(x: CGFloat, y: CGFloat){
let passenger = SKSpriteNode(imageNamed: "passenger")
passenger.position = CGPoint(x: platformArray[2].position.x, y: platformArray[2].position.y)
passenger.position.x = x
passenger.position.y = y
//passenger.physicsBody!.categoryBitMask = PhysicsCategory.Passenger
passenger.zPosition = 5
passenger.anchorPoint = CGPointZero
//print("passenger spawned")
self.addChild(passenger)
}
The App builds fine, but as soon as the collision is fired and this Action is triggered:
func actionPassengerOnboarding(){
let moveToTaxi = SKAction.moveTo(CGPoint(x: taxiNode.position.x, y: platformNode3.position.y), duration: 2);
let removePassenger = SKAction.removeFromParent()
let setPassengerToOnBoard = SKAction.runBlock({ () -> Void in
self.passengerOnBoard = true
})
let onBoardActionSequence = SKAction.sequence([moveToTaxi, removePassenger, setPassengerToOnBoard])
self.passenger.runAction(onBoardActionSequence, withKey: "isOnboarding")
}
is triggered I get a crash and a fatal error printed out fatal error: unexpectedly found nil while unwrapping an Optional value
(lldb)
Can anyone point me to my error? I just cant figure it out
Your error is the first line of your spawnPassenger function. You're making a new constant with let that has scope to that function. Instead of re-declaring a passenger variable, I think you're intending to set it to the class variable you made before. Remove the let and just say:
passenger = SKSpriteNode(imageNamed: "passenger")
This way, you're only referencing that one passenger variable and not making a new one with local scope.

Repeating an action forever with a global function

I have a rectangle that needs to be constantly moving up, but is also declared globally like so so that I can call it in multiple places:
var obstacle = SKNode!
override func didMoveToView {
obstacle = rectangle()
}
func rectangle() -> SKNode {
let rect = SKSpriteNode(imageNamed: "Rectangle#x2")
rect.size = CGSizeMake(30, 30)
rect.position = CGPointMake(210, -250)
rect.physicsBody?.categoryBitMask = PhysicsCatagory.littleRect
rect.physicsBody?.contactTestBitMask = PhysicsCatagory.bigRect
rect.physicsBody?.collisionBitMask = 0
rect.physicsBody = SKPhysicsBody(rectangleOfSize: rect.size)
rect.physicsBody?.dynamic = true
rect.physicsBody?.affectedByGravity = false
rect.runAction(
SKAction.moveByX(0, y: 1200,
duration: NSTimeInterval(6.5)))
addChild(rect)
return rect
}
When I attempt to run it as an action repeating forever like so, i get the error "cannot convert value of type SKNode to argument runBlock" :
runAction(SKAction.repeatActionForever(
SKAction.sequence([
SKAction.runBlock(rectangle),
SKAction.waitForDuration(4.0)])))
So is there a way to declare this sort of action for a function set up like this? Thank you in advance.
First of all, this var obstacle = SKNode! will produce an error. You should declare an implicitly unwrapped optional like this:
var obstacle:SKNode!
About the main question (without analyzing the logic of what code actually does,)...You are passing an instance of SKNode class to +runBlock: method (which accepts a closure), thus the error. To fix this, you have to pass a closure, like this:
override func didMoveToView(view: SKView) {
runAction(SKAction.repeatActionForever(
SKAction.sequence([
SKAction.runBlock({[unowned self] in self.rectangle()}),
SKAction.waitForDuration(4.0)])))
}

SKPhysicsBody "Extra argument 'size' in call"

What I'm trying to achieve is to make a shape of the object(physical body) exactly as a texture, so that only the texture (and not the transparent layer) would be clickable in the future.
let fireLayer = SKSpriteNode(imageNamed: fireImage)
fireLayer.anchorPoint = CGPointMake(1, 0)
fireLayer.position = CGPointMake(size.width, 0)
fireLayer.zPosition = Layer.Z4st
var firedown = SKAction.moveToY(-200, duration: 0)
var fireup1 = SKAction.moveToY(10, duration: 1.2)
var fireup2 = SKAction.moveToY(0, duration: 0.2)
here, I'm trying to create physical body to "cut out" needed object from the node.
fireLayer.physicsBody = SKPhysicsBody(texture: fireLayer, size: fireLayer.texture!.size())
Now, here I got an error "Extra argument 'size' in call"... Although SKPhysicalBody has two parameters: texture and size.
fireLayer.name = "fireNode"
fireLayer.runAction(SKAction.sequence([firedown, wait1, fireup1, fireup2]))
addChild(fireLayer)
what have I done wrong?
Thank you in advance!
This error is misleading. Your problem actually is the first parameter texture: fireLayer. You need to set a SKTexture and not a SKSpriteNode.
Also you should change the way you initialize your SKSpriteNode and add an SKTexture:
let fireLayerTexture = SKTexture(imageNamed: fireImage)
And than:
let fireLayer = SKSpriteNode(texture: fireLayerTexture)
After you've don that, you can initialize your SKPhysicsBody like that:
fireLayer.physicsBody = SKPhysicsBody(texture: fireLayerTexture, size: fireLayer.texture!.size())