I'm trying to pass an argument to a method that is called by NSTimer in my code. It is throwing an exception. This is how I'm doing it. Circle is my custom class.
var circle = Circle()
var timer = NSTimer.scheduledTimerWithInterval(1.0, target: self, selector: animate, userInfo: circle, repeats: true)
Below is the method that is being called
func animate(circle: Circle) -> Void{
//do stuff with circle
}
Note: The method is in the same class that it is being called. So I believe i've set the target correctly.
The selector you use with NSTimer is passed the NSTimer object as it's one and only parameter. Put the circle object in it as userInfo and you can extract it when the timer fires.
var circle = Circle()
var timer = NSTimer.scheduledTimerWithInterval(1.0, target: self, selector: "animate:", userInfo: circle, repeats: true)
func animate(timer:NSTimer){
var circle = timer.userInfo as Circle
//do stuff with circle
}
Your selector needs to be a string unless that's supposed to be an ivar. Also, your animate function has the wrong signature. The following changes should get you moving again:
var circle = Circle()
var timer = NSTimer.scheduledTimerWithInterval(1.0, target: self, selector: "animate", userInfo: circle, repeats: true)
func animate(circle: Circle) -> () {
//do stuff with circle
}
The function really doesn't need to return the empty tuple; it can be written without the -> ()
I've also seen the selector string wrapped in a "Selector()" method: Selector("animate"). It works either way.
I've been messing with NSTimer and closures myself and wrote an article on it: Using Swift's Closures With NSTimer
Related
I want to implement an action that when I press and hold begins to repeatedly do an action (similar to a scroll button on a Desktop UI). Is there first class support for this in the UIGestureRecognizer/events framework, or do I just roll my own?
E.g.
var timer:Timer?
func killDing() {
self.timer?.invalidate()
self.timer = nil
}
func startDing() {
self.killTimer()
self.timer = Timer.scheduledTimer(withTimeInterval: 0.1, repeats: true) {
self.ding() // this is where the repeated code happens
}
}
override func beginTracking(_ touch:UITouch, with event:UIEvent?) -> Bool {
self.startDing()
}
override func endTracking(_ touch: UITouch?, with event: UIEvent?) {
super.endTracking(touch, with: event)
self.killDing()
}
I can of course do this with a LongPressGestureRecognizer as well. My question is whether I need to roll my own ding loop as shown above, or if there's something more first class in UIKit that I'm currently not aware of and should be taking advantage of.
I think you are on the right way. You can use use timers to repeat some actions, but you should add the created timer into a run loop with a common mode, without this mode, the run loop will not call the timer's action while a user is touching the screen
let timer = Timer(timerInterval: interval, repeats: true, block: block)
RunLoop.current.add(timer, forMode: . common)
Also you can use CADisplayLink, to call your action. You can find example of using CADisplayLink in my library, witch can help to you implement animation based on CADisplayLink:
let link = CADisplayLink(target: self, selector: #selector(updateAction(sender:)));
link.add(to: RunLoop.main, forMode: .commonModes);
I'm new to Swift's #selector paradigm. Ultimately I'm trying to move SKSpriteNode of a spaceShip over an CGMutablePath and add exhaust fumes as SKEmitterNodes on the path that the spaceShip has travelled.
However I'm stuck on #selector paradigm. I'm trying to send position of a the SpaceShipSprite via a timer to a addFumeToPosition(_ point: CGPoint) function so later I can add the SKEmitternode to that position.
For some reason that I cannot comprehend the #selector thing sends out something totally different than what I'm putting into the variable. Please see prints below. What's going on here?
let spaceShipSprite = SKSpriteNode(texture: SKTexture(image: #imageLiteral(resourceName: "spaceShip")))
spaceShipSprite.position = CGPoint(x: 142.0, y:160.0)
spaceShipSprite.name = "spaceShip"
self.addChild(spaceShipSprite)
print("spaceShipSprite.position before timer point x:\(spaceShipSprite.position.x) y:\(spaceShipSprite.position.y)")
let gameTimer = Timer.scheduledTimer(timeInterval: 0.5, target: self, selector:#selector(addFumeToPosition(_: )), userInfo: spaceShipSprite.position, repeats: true)
print("spaceShipSprite.position before timer point x:\(spaceShipSprite.position.x) y:\(spaceShipSprite.position.y)")
...
func addFumeToPosition(_ point: CGPoint){
print("spaceShipSprite.position during point x:\(point.x) y:\(point.y)")
}
spaceShipSprite.position before timer point x:142.0 y:160.0
spaceShipSprite.position after timer point x:142.0 y:160.0
spaceShipSprite.position during timer point x:196092657881833.0 y:1000000000.0
You cannot do that.
The two supported forms of target/action are
Without parameter
func addFumeToPosition()
With one parameter representing the affected class (in this case the Timer instance)
func addFumeToPosition(_ timer: Timer)
If a parameter is passed it must be the Timer instance.
All custom parameters have to be passed and handled via the userInfo parameter.
func addFumeToPosition(_ timer: Timer){
let point = timer.userInfo as! CGPoint
print("spaceShipSprite.position during point x:\(point.x) y:\(point.y)")
}
Note: In Swift 4 you might have to add the #objc attribute to the function.
Sorry if this is a really basic questions, but I can’t seem to work it out, so I thought I would ask the experts.
I’ve got a timer for my project that counts down and updates a label according to what is stored in an array.
var array : String[]()
var x = 0
#IBAction func playBtnPressed(sender: UIButton)
{
timer = NSTimer.scheduledTimerWithTimeInterval(60, target: self, selector: #selector(PlayVC.update), userInfo: nil, repeats: true)
}
func update()
{
if x < array.count {
let item = array[x]
aLbl.text = array.itemTitle
x += 1
}
}
My problem is that the text is only updated after the first countdown and 60 seconds is a long time to wait lol.
I would actually like the first String in my array to appear as soon as the button is tapped.
Is there a way to set the text at the very beginning of the countdown?
Thank you for your help :)
So you want to update a label every minute. And you also want to update it immediately after the button is pressed. Hopefully I didn't misunderstand the question.
It's actually as easy as adding this line before the timer = NSTimer ... line:
update()
Note that your current code can cause two or more timers to be created and run when the button is pressed more than once. You might not want this.
To stop the timer when the button is pressed a second time, do this:
if timer == nil {
timer = NSTimer.scheduledTimerWithTimeInterval(60, target: self, selector: #selector(PlayVC.update), userInfo: nil, repeats: true)
} else {
timer.invalidate()
timer = nil
}
To do nothing when the button is pressed a second time jus remove the else part.
I'm building my first game in Swift and I wanted to know how to go about handling multiple on screen sprites at once. My game pushes sprites on to screen with addChild continuously, so there are many active at once. I realized that I didn't have a proper way of simultaneously affecting all of them- like if I wanted to affect a physics property of all enemy sprites at once. So far I created an empty array var enemySprites = [enemyType1]() at the begining of GameScene and have been appending the sprite instances to it instead of using addChild to draw them directly to the scene. However, I'm not able to simply loop through and draw them to screen with:
for enemy in enemySprites{
addChild(enemy)
}
this bit of code is in the override func update(currentTime: CFTimeInterval) function, so maybe I'm just misplacing it? Any help on how to go about this would be great!
Sam,
Here's some sample code to update enemies when your lives reach 0:
First, we set a property observer on the lives property so we can call a function when you lose all lives:
var lives = 3 {
didSet {
if lives == 0 {
updateEnemies()
}
}
And then a function to enumerate over all the enemies and change each one's velocity to (0, 0):
func update enemies() {
enumerateChildNodesWithName("type1") {
node, stop in
let enemy = node as! SKSpriteNode
enemy.physicsBody?.velocity = CGVector(dx: 0, dy: 0)
}
}
Instead of use update method, you could use a timer. From sources:
public class func scheduledTimerWithTimeInterval(ti: NSTimeInterval, target aTarget: AnyObject, selector aSelector: Selector, userInfo: AnyObject?, repeats yesOrNo: Bool) -> NSTimer
So if you follow Apple guide, it will be for example:
NSTimer.scheduledTimerWithTimeInterval(0.5, target: self, selector: Selector("spawnAlien:"), userInfo: myParameter, repeats: true)
func spawnAlien(timer : NSTimer) {
if let myUserInfo = timer.userInfo {
print(myUserInfo) // a parameters passed to help you to the alien creation
}
timer.invalidate()
}
BUT according to Whirlwind I agree with him and with LearnCocos2d work, sprite-kit don't work well with timers (as explained in the link by LearnCocos2d) and the better way, especially as you say you develop your first game, it's to use SKAction, a combination of actions to achieve the similar behavior obtained by NSTimer.
I've think about a function or an extension, let me know if it's work as expected:
extension SKAction {
class func scheduledTimerWithTimeInterval(time:NSTimeInterval, selector: Selector, repeats:Bool)->SKAction {
let call = SKAction.customActionWithDuration(0.0) { node, _ in
node.performSelector(selector)
}
let wait = SKAction.waitForDuration(time)
let seq = SKAction.sequence([wait,call])
let callSelector = repeats ? SKAction.repeatActionForever(seq) : seq
return callSelector
}
}
Usage:
let spawn = SKAction.scheduledTimerWithTimeInterval(time, selector: #selector(GenericArea.spawnAlien), repeats: true)
self.runAction(spawn,withKey: "spawnAlien")
I'm trying to animate some bezier paths in swift, and I need to get some of them to launch after a set delay. To do this, I have to write both of these sets of code in the viewDidLoad function.
Here is some sample code with the same idea:
override func viewDidLoad() {
super.viewDidLoad
func testFunc() {
println("Hello")
}
var frontOfBaseTimer = NSTimer.scheduledTimerWithTimeInterval(3, target: self, selector: Selector("testFunc"), userInfo: nil, repeats: false)
}
This would crash the app 3 seconds after the view loaded. The error message leads me to believe that the only problem in this case is the target property.
What should I change here to get this to work?
If you're bound and determined to use your own delay loop, consider using GCD and the dispatch_after method. That method takes a closure and invokes the closure after a specified delay, which is pretty much exactly what you want. You would pass nil for the queue parameter so your closure would be run on the main queue.
I created a global function delay that lets me invoke dispatch_async painlessly without having to figure out it's confusing parameters:
func delay(delay: Double, block:()->())
{
let nSecDispatchTime = dispatch_time(DISPATCH_TIME_NOW,
Int64(delay * Double(NSEC_PER_SEC)));
let queue = dispatch_get_main_queue()
dispatch_after(nSecDispatchTime, queue, block)
}
You call it like this:
delay(2.0)
{
//code to fire after a delay
}
try this:-
override func viewDidLoad() {
super.viewDidLoad()
var frontOfBaseTimer =
NSTimer.scheduledTimerWithTimeInterval(3,
target: self,
selector:"testFunc",
userInfo: nil,
repeats: false)
}
func testFunc(){
println("hello its me")
}
Do like this way, Your app get crash because, timer call the function which has been added in viewdidload method.
override func viewDidLoad() {
super.viewDidLoad()
var frontOfBaseTimer =
NSTimer.scheduledTimerWithTimeInterval(3,
target: self,
selector: Selector("testFunc"),
userInfo: nil,
repeats: false)
}
func testFunc() {
println("Hello")
}
I just tested it and confirmed that you can't use a nested function as the selector for an NSTimer. The method the timer calls needs to be defined at the global scope of the target.
If you're trying to do animation, why not use UIView animation? That family of methods includes a delay parameter.
You could also use Core Animation. You can animate a path very easily by installing the CGPath from a bezierPath into a shape layer, then changing the shape layer's path as part of a CABasicAnimation.
Using Core Animation makes for very smooth, clean animations.
I have a project called RandomBlobs on Github that shows how to animate Bezier paths using CAShapeLayers and CABasicAnimations. It's written in Objective-C, but the techniques are directly applicable in Swift.
You can see a short video of the bezier path animation on Youtube.