Can I use a Selector with parenthesis in Swift? - ios

I was wondering how can I use a Selector in Swift 3 including a value in the parenthesis that the func requires.
let fireRecogniser = UISwipeGestureRecognizer(target: self, action: Selector(("shootShot")))
^ That is the recogniser I have but the method 'shootShot' has a parameter for an Element which is an enum that I have.
Here is the 'shootShot' function:
func shootShot(type: Element) {
let shot = SKSpriteNode(imageNamed: "\(type)Shot")
shot.texture?.filteringMode = SKTextureFilteringMode.nearest
shot.position = CGPoint(x: -self.frame.width / 2 /*playerframe*/, y: -(self.frame.height / 2) + grnd.frame.height)
shot.setScale(1)
shot.physicsBody = SKPhysicsBody(circleOfRadius: (shot.frame.height / 2))
shot.physicsBody?.affectedByGravity = false
shot.physicsBody?.allowsRotation = true
shot.physicsBody?.isDynamic = true
shot.physicsBody?.restitution = 0
shot.physicsBody?.angularDamping = 0
shot.physicsBody?.linearDamping = 0
shot.physicsBody?.friction = 0
shot.physicsBody?.categoryBitMask = Contact.Shot.rawValue
shot.physicsBody?.contactTestBitMask = Contact.Enemy.rawValue
self.addChild(shot)
// THIS WILL DEPEND ON DIFFICULTY
let spin = SKAction.rotate(byAngle: 1, duration: 0.3)
shot.run(SKAction.repeatForever(spin))
let move = SKAction.moveTo(x: self.frame.width / 2, duration: 3.0)
let remove = SKAction.removeFromParent()
shot.run(SKAction.sequence([move, remove]))
}
As you can see, the method has an Element that the function requires.
Any help as to how I can include that parameter in my Selector?
Thanks. :)

Type your selector like this in Swift 3
let fireRecogniser = UISwipeGestureRecognizer(target: self, action: #selector(shootShot(element:)))

It is possible... if you are the one performing the selector.
There is an overload of perform() that has a with: argument. The argument you pass in the with: argument will be passed to the selector method.
Example:
// in some NSObject subclass's method
perform(#selector(myMethod), with: "Hello")
// in the same class
func myMethod(x: String) {
print(x)
}
If the first line is executed, "Hello" will be printed.
However, in yor case, since you are not the performer of the selector, you can't perform the selector with the arguments you want.
You can workaround this by adding a class level variable that indicates which Element you want to call the method with:
var shotElement: Element!
You can set this to some value before you pass the target and action to the gesture recognizer.
Then, access it in shootShot:
let shot = SKSpriteNode(imageNamed: "\(shotElement)Shot")
I admit this isn't the perfect workaround, but it's the most straightforward.

Related

Why can't I call my function with gestureTapRecogniser

I have this simple function that when someone taps the screen, the code inside the function executes, I know the code in the function works and I get no error when it is typed.
func reload(gestureRecognizer: UITapGestureRecognizer) {
let skView = self.view!
skView.presentScene(scene)
}
I have this if statement, and all the code work except for when I ry to run the function after a certain event has occurred. In the line where I try and call 'reload()' I get the error 'Use of unresolved identifier reload'. Can you please tell me what I've done wrong. Thanks!
if score[0] >= 10 {
pauseGame()
let textLabel = SKLabelNode(fontNamed: "Helvetica")
textLabel.fontSize = 30
textLabel.fontColor = SKColor.white
textLabel.position = CGPoint(x: 20, y: 20)
textLabel.horizontalAlignmentMode = SKLabelHorizontalAlignmentMode.center
textLabel.text = "HELLO"
addChild(textLabel)
reload()
}
else if
score [1] >= 10 {
pauseGame()
sleep(5)
let textLabel = SKLabelNode(fontNamed: "ChalkboardSE-Bold")
textLabel.fontSize = 30
textLabel.fontColor = SKColor.white
textLabel.position = CGPoint(x: 20, y: 20)
textLabel.horizontalAlignmentMode = SKLabelHorizontalAlignmentMode.center
textLabel.text = "HELLO"
addChild(textLabel)
reload()
}
Just change
reload()
to
reload(gestureRecognizer: nil)
and reload method parameter should be UITapGestureRecognizer? optional.
func reload(gestureRecognizer: UITapGestureRecognizer?) {
let skView = self.view!
skView.presentScene(scene)
}
Another short way is
change action method that declared with UITapGestureRecognizer
...action: #selector(self.reload())...
means remove (_:) no need to do anythings more just call reload() function.

Finish action running on a SpriteNode without disabling touches

i have a player who's physicsBody is divided to parts (torso, legs, head, ect), now i have a button on screen that control the movement of the player, if the button is pressed to the right the player moves to the right, and that part works fine. the problem is every time the movement changes the walk() method is called and what it does is animate the player legs to look like its walking, but if the movement doesn't stop then is keeps calling the walk() without it finishing the animation, so it looks like its stuck back and forth. what i am trying to achieve is for the player to be able to walk constantly but for the walk() (animation method) to be called once, finish, then called again(as long as the button to walk is still pressed). here what i have so far:
func walk(){
let leftFootWalk = SKAction.run {
let action = SKAction.moveBy(x: 1, y: 0.1, duration: 0.1)
let actionReversed = action.reversed()
let seq = SKAction.sequence([action, actionReversed])
self.leftUpperLeg.run(seq)
self.leftLowerLeg.run(seq)
}
let rightFootWalk = SKAction.run {
let action = SKAction.moveBy(x: 0.4, y: 0.1, duration: 0.1)
let actionReversed = action.reversed()
let seq = SKAction.sequence([action, actionReversed])
self.rightUpperLeg.run(seq)
self.rightLowerLeg.run(seq)
}
let group = SKAction.sequence([leftFootWalk, SKAction.wait(forDuration: 0.2), rightFootWalk])
run(group)
}
extension GameScene: InputControlProtocol {
func directionChangedWithMagnitude(position: CGPoint) {
if isPaused {
return
}
if let fighter = self.childNode(withName: "fighter") as? SKSpriteNode, let fighterPhysicsBody = fighter.physicsBody {
fighterPhysicsBody.velocity.dx = position.x * CGFloat(300)
walk()
if position.y > 0{
fighterPhysicsBody.applyImpulse(CGVector(dx: position.x * CGFloat(1200),dy: position.y * CGFloat(1200)))
}else{
print("DUCK") //TODO: duck animation
}
}
}
First of all you can use SKAction.runBlock({ self.doSomething() })
as the last action in your actions sequence instead of using completion.
About your question, you can use hasAction to determine if your SKNode is currently running any action, and you can use the key mechanism for better actions management.
See the next Q&A
checking if an SKNode is running a SKAction
I was able to solve this, however i'm still sure there is a better approach out there so if you know it be sure to tell. here is what i did:
first i added a Boolean called isWalking to know if the walking animation is on(true) or off(false) and i set it to false. then i added the if statement to call walk() only if the animation is off(false) and it sets the boolean to on(true).
func directionChangedWithMagnitude(position: CGPoint) {
if isPaused {
return
}
if let fighter = self.childNode(withName: "fighter") as? SKSpriteNode, let fighterPhysicsBody = fighter.physicsBody {
fighterPhysicsBody.velocity.dx = position.x * CGFloat(300)
print(fighterPhysicsBody.velocity.dx)
if !isWalking{
isWalking = true
walk()
}
}
}
and i changed walk() to use completions blocks and set the Boolean to off(false) again
func walk(){
let walkAction = SKAction.moveBy(x: 5, y: 5, duration: 0.05)
let walk = SKAction.sequence([walkAction, walkAction.reversed()])
leftUpperLeg.run(walk) {
self.rightUpperLeg.run(walk)
}
leftLowerLeg.run(walk) {
self.rightLowerLeg.run(walk, completion: {
self.isWalking = false
})
}
}

Swift Array Append Overwriting Other Array Values

I'm new to Swift, but I'm not new to coding. I thought I would try my hand at making a little game. It's something I like to do when learning.
Here's what I'm doing. I start of by calling a method that initialized my "object templates"
internal func initializeObjectTemplates(){
objectTemplates.append(GameObject(passed_x: 0,
passed_y: 0,
passed_max: 25,
passed_min: 25,
passed_points: 100,
passed_img: "BeerGlass1",
passedLengthOfTimeOnScreen: 5,
passedBottomChance: 1,
passedTopChance: 50,
passedId: 0))
objectTemplates.append(GameObject(passed_x: 0,
passed_y: 0,
passed_max: 25,
passed_min: 25,
passed_points: 300,
passed_img: "BeerGlass2",
passedLengthOfTimeOnScreen: 2,
passedBottomChance: 51,
passedTopChance: 100,
passedId: 0))
}
I have a timer that runs a func named "update" every second. The "update" func randomly selects one of the two templates, adds some other information to the template object, does some other logic, and appends the object to an array I have. Everything up to the append seems to be working. I have added various breakpoints and the GameObject object seems to be getting populated correctly.
When I append the object it is overwriting other items in the objects array. I can't figure it out. I've googled it as much as possible, and can't really seem to find anything that fits my issue. Here's the rest of my code related to this.
This is the top of the ViewController and the viewDidLoad
internal var step = 0
internal var objects = [GameObject]()
#IBOutlet weak var points: UILabel!
internal var objectTemplates = [GameObject]()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
view.backgroundColor = UIColor.blackColor()
initializeObjectTemplates()
let timer = NSTimer(timeInterval: 1, target: self, selector: #selector(ViewController.update), userInfo: nil, repeats: true)
NSRunLoop.currentRunLoop().addTimer(timer, forMode: NSRunLoopCommonModes)
//var i = 1
//while i <= 100{
// update()
// i += 1
//}
}
Update Func - the commented out/hard coded append works, but obviously not what I want
internal func update(){
step += 1
//removeExpiredButtons()
objects.append(populateObjectTemplate(selectObjectTemplate(), step: step))
//objects.append(GameObject(passed_x: 0,
// passed_y: 0,
// passed_max: 25,
// passed_min: 25,
// passed_points: 300,
// passed_img: "BeerGlass2",
// passedLengthOfTimeOnScreen: 2,
// passedBottomChance: 51,
// passedTopChance: 100,
// passedId: step))
print("There are \(objects.count) items and the first item has an id of \(objects[0].id)")
}
These are the methods that update calls
selectObjectTemplate
func selectObjectTemplate() -> GameObject {
let rand = Random.within(1...100)
return objectTemplates.filter(){
let isAbove = $0.bottomChance <= rand
let isBelow = $0.topChance >= rand
return isAbove && isBelow
}[0]
}
populateObjectTemplate
func populateObjectTemplate(obj: GameObject, step: Int) -> GameObject {
let widthBoundary = Float(self.view.frame.width - 20)
let heightBoundary = Float(self.view.frame.height - 20)
let placex = CGFloat(Random.within(20.0...widthBoundary))
obj.x = placex
let placey = CGFloat(Random.within(50.0...heightBoundary))
obj.y = placey
obj.expiration = obj.lengthOfTimeOnScreen + step
obj.id = step
obj.button = populateObjectButton(obj)
return obj
}
populateObjectButton
func populateObjectButton(obj: GameObject) -> UIButton {
let button = UIButton(type: UIButtonType.Custom)
let image = UIImage(named: obj.img)
button.setBackgroundImage(image, forState: UIControlState.Normal)
button.frame = CGRectMake(obj.x, obj.y, obj.minSize, obj.minSize)
button.layer.cornerRadius = 0.5 * button.bounds.size.width
button.layer.masksToBounds = true
button.addTarget(self, action: #selector(ViewController.objectTapped(_:)), forControlEvents: .TouchUpInside)
button.tag = obj.id
self.view.addSubview(button)
return button
}
Sorry for such a lengthy post. I just wanted to include as much information as possible. I don't usually post to places like this, because I try as hard as possible to find the solution myself. The swift file is also on Git.
https://github.com/JoeBrewing/TapGlassMasterChallenge/blob/master/Glass%20Tap%20Master%20Challenge/ViewController.swift
Your objectTemplates array stores references to two GameObject instances that were created in initializeObjectTemplates. When your timer fires you select one of these 'template' objects, modify it and add it to the array. However, the array is merely storing references to the objects you add to it, and you only have two objects, even though you are adding those objects to the array multiple times.
When you call populateObjectTemplate you modify one of the two object templates, which means that all other entries in the array that refer to that particular template will reflect those modifications. You need populateObjectTemplate to return a new GameObject, using the information in the template;
func populateObjectTemplate(obj: GameObject, step: Int) -> GameObject {
let newGameObject=GameObject(passed_x: 0,
passed_y: obj.passed_y,
passed_max: obj.passed_max,
passed_min: obj.passed_min,
passed_points: obj.passed_points,
passed_img: obj.passed_img,
passedLengthOfTimeOnScreen: obj.passedLengthOfTimeOnScreen,
passedBottomChance: obj.passedBottomChance,
passedTopChance: obj.passedTopChance,
passedId: obj.passedId)
let widthBoundary = Float(self.view.frame.width - 20)
let heightBoundary = Float(self.view.frame.height - 20)
let placex = CGFloat(Random.within(20.0...widthBoundary))
newGameObject.x = placex
let placey = CGFloat(Random.within(50.0...heightBoundary))
newGameObject.y = placey
newGameObject.expiration = obj.lengthOfTimeOnScreen + step
newGameObject.id = step
newGameObject.button = populateObjectButton(obj)
return newGameObject
}

Lag using runAction with a SKLabelNode on swift

I am having a lag issue with this function that is used a lot of times in my app...
plusOne(scorelabel.position,plus: 1)
And:
func plusOne(position: CGPoint, plus : Int) {
myLabel.setScale(1)
myLabel.text = "+"+String(plus)
myLabel.position = position
myLabel.hidden = false
let action1 = SKAction.scaleTo(2, duration: 0.5)
let action2 = SKAction.fadeOutWithDuration(0.5)
let actionGroup = SKAction.group([action1,action2])
myLabel.runAction(actionGroup,completion: {
self.myLabel.hidden = true
})
}
The first time I use the plusOne function, always make my app be freezed for a little time...
I do not know if I have been doing the things well... myLabel has been declared global but it is the same... always with lag on the first execution.
You need to set the font of your label with a fix font at start.
Like that:
let yourFont = UIFont(name: "yourfontName", size: 17)
var myLabel = SKLabelNode(fontNamed: yourFont?.fontName)
Otherwise, your font gets loaded at the first usage and not on app-start.

How i can call functions from other class? Swift, Xcode

I have a class ACCreateObject. They have a function circlePhysicsDefault.
I want to call this function other file by push the Button.
In GameViewController i type:
#IBAction func addOneCircle(sender: AnyObject) {
ACCreateObject.circlePhysicsDefault(ACCreateObject)
}
But i have a issue: '(ACCreateObject).Type' is not convertible to 'ACCreateObject'. How can i call the function?
What am doing wrong?
Thanks!
ACCreateObject.swift
class ACCreateObject: SKScene {
func circlePhysicsDefault() {
var Circle = SKShapeNode(circleOfRadius: 40)
Circle.position = CGPointMake(500, 500)
Circle.name = "defaultCircle"
Circle.strokeColor = SKColor.blackColor()
Circle.glowWidth = 10.0
Circle.fillColor = SKColor.yellowColor()
Circle.physicsBody = SKPhysicsBody(circleOfRadius: 40)
Circle.physicsBody.dynamic = true
self.addChild(Circle)
}
}
You are calling an instance method on a class object. You have two solutions :
Create a new instance and calls it's method
ACCreateObject().circlePhysicsDefault()
OR
Make it a class function
// In your class file
class func circlePhysicsDefault() {
....
}
And then call
ACCreateObject.circlePhysicsDefault()
Not sure if I'm understanding you right but by what you are saying you want to call a method on the type instead on an object
The first thing that comes to mind is this:
var x = ACCreateObject()
x.circlePhysicsDefault()
I think this is the way you should do this.I know there are options of making a class circlePhysicsDefault

Resources