I'm using script (below), to use as a countdown for my game start, the script I've used is from Gourav Nayyar's YouTube video and works great for the first time it is called. However once the game goes through the reset process and the script is called again I only see 5 rather than 5 - 4 - 3 - 2 - 1 - GO!. If I remove one of the cals from my script then it works fine either in the reset func or when gameScene loads.
Here is the two calls in GameScene.swift
override func didMoveToView(view: SKView) {
var gamelaunchTimerView:TimerView = TimerView.loadingCountDownTimerViewInView(self.view!)
gamelaunchTimerView.startTimer()
func resetScene() {
//code removed from here
return countdown()
}
func countdown() {
var gamelaunchTimerView:TimerView = TimerView.loadingCountDownTimerViewInView(self.view!)
gamelaunchTimerView.startTimer()
}
Here is the Timer Code in GameLaunchTimer.swift as this is set up the countdown only works when first called and hangs on the second call.
//
// TimerView.swift
// GameLaunchTimer
//
// Created by Gourav Nayyar on 7/3/14.
// Copyright (c) 2014 Gourav Nayyar. All rights reserved.
//
let VIEW_ALPHA:CGFloat = 0.5
let TIMERVIEW_RADIUS:CGFloat = 50
let TIMER_LABEL_INITIAL_VAL:Int = 5
let BORDER_WIDTH:CGFloat = 2
var timerVal:Int = TIMER_LABEL_INITIAL_VAL;
var timer:NSTimer!
import UIKit
import QuartzCore
class TimerView :UIView {
struct Stored {
static var timerLbl:UILabel!
}
class func loadingCountDownTimerViewInView (_superView:UIView)-> TimerView
{
var timerView:TimerView = TimerView(frame:_superView.frame)
// timerView.backgroundColor = UIColor.blackColor().colorWithAlphaComponent(VIEW_ALPHA)
_superView.addSubview(timerView)
/* add a custom Circle view */
let refFrame:CGRect = CGRectMake(_superView.center.x-TIMERVIEW_RADIUS, _superView.center.y-TIMERVIEW_RADIUS, 2*TIMERVIEW_RADIUS, 2*TIMERVIEW_RADIUS)
var circleView:UIView = UIView(frame:refFrame)
circleView.layer.cornerRadius = TIMERVIEW_RADIUS
circleView.layer.borderColor = UIColor.whiteColor().CGColor
circleView.layer.borderWidth = BORDER_WIDTH
/* add a custom Label */
Stored.timerLbl = UILabel(frame:circleView.bounds)
Stored.timerLbl.text = "\(TIMER_LABEL_INITIAL_VAL)"
Stored.timerLbl.textColor = UIColor.whiteColor()
Stored.timerLbl.font = UIFont(name: "MarkerFelt-Thin", size: 40)
Stored.timerLbl.textAlignment = NSTextAlignment.Center
circleView.addSubview(Stored.timerLbl)
timerView.addSubview(circleView)
return timerView
}
func startTimer()
{
timer = NSTimer.scheduledTimerWithTimeInterval(1.0
, target: self, selector: Selector("updateTimer:"), userInfo: nil, repeats: true)
}
func updateTimer(dt:NSTimer)
{
timerVal--
if timerVal==0{
Stored.timerLbl.text = "GO!"
}else if timerVal<0{
timer.invalidate()
removeCountDownTimerView()
} else{
Stored.timerLbl.text = "\(timerVal)"
}
}
func removeCountDownTimerView()
{
var mySuperView:UIView = self.superview!
mySuperView.userInteractionEnabled = true
super.removeFromSuperview()
}
}
Define your variables under the class body;
import UIKit
import QuartzCore
class TimerView :UIView {
let VIEW_ALPHA:CGFloat = 0.5
let TIMERVIEW_RADIUS:CGFloat = 50
let TIMER_LABEL_INITIAL_VAL:Int = 5
let BORDER_WIDTH:CGFloat = 2
var timerVal:Int = TIMER_LABEL_INITIAL_VAL;
var timer:NSTimer!
... // other code
or, may be
let VIEW_ALPHA:CGFloat = 0.5
let TIMERVIEW_RADIUS:CGFloat = 50
let TIMER_LABEL_INITIAL_VAL:Int = 5
let BORDER_WIDTH:CGFloat = 2
import UIKit
import QuartzCore
class TimerView :UIView {
var timerVal:Int = TIMER_LABEL_INITIAL_VAL;
var timer:NSTimer!
... //other code
Assign nil to the timer after invalidating it: even though you are invalidating, the object state is still kept, resulting on states conflicts when creating a new instance of the timer, once that it runs in a different thread.
Related
I want to make jump rope counter, but I don't know how to detect jump and I did't find any solution for it. Please, help!
final class WorkoutViewController: UIViewController {
private var motionManager = CMMotionManager()
private var jumpCounter = 0
func jump() {
let xAxis = motionManager.deviceMotion!.userAcceleration.x
let yAxis = motionManager.deviceMotion!.userAcceleration.y
let zAxis = motionManager.deviceMotion!.userAcceleration.z
let xGravity = motionManager.deviceMotion!.gravity.x
let yGravity = motionManager.deviceMotion!.gravity.y
let zGravity = motionManager.deviceMotion!.gravity.z
// I don't know how to detect a jump here
}
override func viewDidLoad() {
super.viewDidLoad()
motionManager.deviceMotionUpdateInterval = self.updateDataInterval
motionManager.startDeviceMotionUpdates(to: .main) { (_, _) in
self.jump()
}
}
}
I want to achieve the same function as the IOS original clock app.But I meet one barrier: I can't close the notification content extension programmatically when the snooze remaining time has been set zero.
The picture is below:
my NotificationViewController.swift:
import UIKit
import UserNotifications
import UserNotificationsUI
class NotificationViewController: UIViewController, UNNotificationContentExtension {
#IBOutlet var label: UILabel?
var previousNotification: UNNotification?
var secondsDown = Int()
var countDownTimer: Timer?
override func viewDidLoad() {
super.viewDidLoad()
let size = view.bounds.size
preferredContentSize = CGSize(width: size.width, height: size.width * 0.7)
}
func didReceive(_ notification: UNNotification) {
previousNotification = notification
// Calculate the actual remaining seconds.
let timeInterval = -(notification.date.timeIntervalSinceNow)
secondsDown = (notification.request.content.userInfo["snoozeTime"] as! Int) * 60 - Int(timeInterval) - 1
// Achieve count down timer.
if countDownTimer == nil {
countDownTimer = Timer(timeInterval: 1, target: self, selector: #selector(self.countDownAction), userInfo: nil, repeats: true)
RunLoop.main.add(countDownTimer!, forMode: .defaultRunLoopMode)
}
// print the remaining time to text label.
let miniute = String(format:"%ld", (secondsDown / 60))
let second = String(format:"%02ld", (secondsDown % 60))
let formatTime = String(format: "%#:%#", miniute, second)
self.label?.text = String(format: "%#", formatTime)
}
func countDownAction(timer: Timer) {
secondsDown -= 1
let miniute = String(format:"%ld", (secondsDown / 60))
let second = String(format:"%02ld", (secondsDown % 60))
let formatTime = String(format: "%#:%#", miniute, second)
self.label?.text = String(format: "%#", formatTime)
if secondsDown == 0 {
countDownTimer?.invalidate()
}
}
}
It seems that from iOS 12 there is a solution to this problem:
if #available(iOSApplicationExtension 12.0, *) {
//self.extensionContext?.performNotificationDefaultAction()
self.extensionContext?.dismissNotificationContentExtension()
}
Initially I had this code working when I was just animating the one UIImageView that I had. But then I changed it to animate several dynamically created UIImageViews, however since they are dynamically created inside a for loop, I'm finding it difficult to animate them as I did the initial one.
override func viewDidLoad() {
super.viewDidLoad()
var sprite: UIImage = UIImage(named: "sprites/areaLocatorSprite.png")!
var locations:NSArray = animal[eventData]["locations"] as NSArray
for var i = 0; i < locations.count; i++ {
println(locations[i]["locationx"])
var locationx = locations[i]["locationx"] as String
var locationy = locations[i]["locationy"] as String
let x = NSNumberFormatter().numberFromString(locationx)
let y = NSNumberFormatter().numberFromString(locationy)
let cgfloatx = CGFloat(x!)
let cgfloaty = CGFloat(y!)
var mapSprite: UIImageView
mapSprite = UIImageView(image: sprite)
mapSprite.frame = CGRectMake(cgfloatx,cgfloaty,10,10)
townMap.addSubview(mapSprite)
timer = NSTimer.scheduledTimerWithTimeInterval(0.35, target: self, selector: Selector("flash"), userInfo: nil, repeats: true)
}
}
func flash() {
var mapSprite:UIImageView?
if mapSprite?.alpha == 1 {
mapSprite?.alpha = 0
} else {
mapSprite?.alpha = 1
}
}
This does not work as the mapSprite in the flash function is different to the one in the for loop. How can I refer to the one in the for loop and then animate it? Or would there be a better alternative to what I'm currently doing?
Many thanks!
EDIT
Using Xcode 6.2
You need to store the views into a property and then enumerate that property each time your timer event is fired
var sprites: [UIImageView]?
override func viewDidLoad() {
super.viewDidLoad()
var sprite = UIImage(named: "sprites/areaLocatorSprite.png")!
var locations:NSArray = animal[eventData]["locations"] as NSArray
self.sprites = map(locations) {
var locationx = $0["locationx"] as String
var locationy = $0["locationy"] as String
let x = NSNumberFormatter().numberFromString(locationx)
let y = NSNumberFormatter().numberFromString(locationy)
let cgfloatx = CGFloat(x!)
let cgfloaty = CGFloat(y!)
var mapSprite = UIImageView(image: sprite)
mapSprite.frame = CGRectMake(cgfloatx,cgfloaty,10,10)
townMap.addSubview(mapSprite)
return mapSprite
}
timer = NSTimer.scheduledTimerWithTimeInterval(0.35, target: self, selector: Selector("flash"), userInfo: nil, repeats: true)
}
func flash() {
if let sprites = self.sprites {
for sprite in sprites {
sprite.alpha = sprite.alpha == 0 ? 1 : 0
}
}
}
Here is the method inside a class:
import UIKIt
import Foundation
class notMoving {
var drumPlayerObject = drumPlayer()
var fileManagerObject = fileManager1()
let drumStrength = 1
var bassStrength = 1
var synthStrength = 1
var indexToPlay: Int = 0
// here we start the drum player.
func startToPlay() {
fileManagerObject.clearPlayedListDrum(drumStrength, KeyNoteOfInstDrum: "C")
if let indexToPlay = fileManager1().randomizeTheNextInstrument(fileManager1().drums, Strength: drumStrength, KeyNote: "C") {
fileManager1().drums[indexToPlay].4 = true
self.indexToPlay = indexToPlay
}
let instrument = fileManager1().drums[self.indexToPlay].0
let name = fileManager1().drums[self.indexToPlay].1
let length = fileManager1().drums[self.indexToPlay].2
let power = fileManager1().drums[self.indexToPlay].3
let ifplayed = fileManager1().drums[self.indexToPlay].4
let tempo = Double(fileManager1().drums[self.indexToPlay].5)
let bridge: Bool = false
let extention = fileManagerObject.extentionOfFile
let loops = fileManager1().drumNumberOfLoops()
drumPlayerObject.play(instrument, name: name, extentionOfFile: extention,
length: length, power: power, ifplayed: ifplayed, tempo: tempo, loops:
loops, bridge: bridge)
fileManager1().clearPlayedListDrum(drumStrength, KeyNoteOfInstDrum: "C")
}
}
And here is AVAudioPlayerDelegate extension for a drumPlayer class.
extension drumPlayer : AVAudioPlayerDelegate {
func audioPlayerDidFinishPlaying(player: AVAudioPlayer!, successfully flag: Bool) {
println("finished playing \(flag)")
var notMovingObject = notMoving()
notMovingObject.startToPlay()
}
func audioPlayerDecodeErrorDidOccur(player: AVAudioPlayer!, error: NSError!) {
println("\(error.localizedDescription)")
}
}
But audioPlayerDidFinishPlaying doesn't call the startToPlay method after the file is finished.It just only prints "finished playing true"
What I'm doing wrong?
Your notMovingObject is not retained anywhere, thus after program exits audioPlayerDidFinishPlaying() object is deallocated.
I found a solution by creating an instance self.notMovingObject = NotMoving() inside the "func play" of my player. So the instance is created only after the player starts.
How can I create a timer that fires every two seconds that will increment the score by one on a HUD I have on my screen? This is the code I have for the HUD:
#implementation MyScene
{
int counter;
BOOL updateLabel;
SKLabelNode *counterLabel;
}
-(id)initWithSize:(CGSize)size
{
if (self = [super initWithSize:size])
{
counter = 0;
updateLabel = false;
counterLabel = [SKLabelNode labelNodeWithFontNamed:#"Chalkduster"];
counterLabel.name = #"myCounterLabel";
counterLabel.text = #"0";
counterLabel.fontSize = 20;
counterLabel.fontColor = [SKColor yellowColor];
counterLabel.horizontalAlignmentMode = SKLabelHorizontalAlignmentModeCenter;
counterLabel.verticalAlignmentMode = SKLabelVerticalAlignmentModeBottom;
counterLabel.position = CGPointMake(50,50); // change x,y to location you want
counterLabel.zPosition = 900;
[self addChild: counterLabel];
}
}
In Sprite Kit do not use NSTimer, performSelector:afterDelay: or Grand Central Dispatch (GCD, ie any dispatch_... method) because these timing methods ignore a node's, scene's or the view's paused state. Moreover you do not know at which point in the game loop they are executed which can cause a variety of issues depending on what your code actually does.
The only two sanctioned ways to perform something time-based in Sprite Kit is to either use the SKScene update: method and using the passed-in currentTime parameter to keep track of time.
Or more commonly you would just use an action sequence that starts with a wait action:
id wait = [SKAction waitForDuration:2.5];
id run = [SKAction runBlock:^{
// your code here ...
}];
[node runAction:[SKAction sequence:#[wait, run]]];
And to run the code repeatedly:
[node runAction:[SKAction repeatActionForever:[SKAction sequence:#[wait, run]]]];
Alternatively you can also use performSelector:onTarget: instead of runBlock: or perhaps use a customActionWithDuration:actionBlock: if you need to mimick the SKScene update: method and don't know how to forward it to the node or where forwarding would be inconvenient.
See SKAction reference for details.
UPDATE: Code examples using Swift
Swift 5
run(SKAction.repeatForever(SKAction.sequence([
SKAction.run( /*code block or a func name to call*/ ),
SKAction.wait(forDuration: 2.5)
])))
Swift 3
let wait = SKAction.wait(forDuration:2.5)
let action = SKAction.run {
// your code here ...
}
run(SKAction.sequence([wait,action]))
Swift 2
let wait = SKAction.waitForDuration(2.5)
let run = SKAction.runBlock {
// your code here ...
}
runAction(SKAction.sequence([wait, run]))
And to run the code repeatedly:
runAction(SKAction.repeatActionForever(SKAction.sequence([wait, run])))
In Swift usable:
var timescore = Int()
var actionwait = SKAction.waitForDuration(0.5)
var timesecond = Int()
var actionrun = SKAction.runBlock({
timescore++
timesecond++
if timesecond == 60 {timesecond = 0}
scoreLabel.text = "Score Time: \(timescore/60):\(timesecond)"
})
scoreLabel.runAction(SKAction.repeatActionForever(SKAction.sequence([actionwait,actionrun])))
I've taken the swift example above and added in leading zeros for the clock.
func updateClock() {
var leadingZero = ""
var leadingZeroMin = ""
var timeMin = Int()
var actionwait = SKAction.waitForDuration(1.0)
var timesecond = Int()
var actionrun = SKAction.runBlock({
timeMin++
timesecond++
if timesecond == 60 {timesecond = 0}
if timeMin / 60 <= 9 { leadingZeroMin = "0" } else { leadingZeroMin = "" }
if timesecond <= 9 { leadingZero = "0" } else { leadingZero = "" }
self.flyTimeText.text = "Flight Time [ \(leadingZeroMin)\(timeMin/60) : \(leadingZero)\(timesecond) ]"
})
self.flyTimeText.runAction(SKAction.repeatActionForever(SKAction.sequence([actionwait,actionrun])))
}
Here's the full code to build a timer for SpriteKit with Xcode 9.3 and Swift 4.1
In our example the score label will be incrementd by 1 every 2 seconds.
Here's final result
Good, let's start!
1) The score label
First of all we need a label
class GameScene: SKScene {
private let label = SKLabelNode(text: "Score: 0")
}
2) The score label goes into the scene
class GameScene: SKScene {
private let label = SKLabelNode(text: "Score: 0")
override func didMove(to view: SKView) {
self.label.fontSize = 60
self.addChild(label)
}
}
Now the label is at the center of the screen. Let's run the project to see it.
Please note that at this point the label is not being updated!
3) A counter
We also want to build a counter property which will hold the current value displayed by the label. We also want the label to be updated as soon as the counter property is changed so...
class GameScene: SKScene {
private let label = SKLabelNode(text: "Score: 0")
private var counter = 0 {
didSet {
self.label.text = "Score: \(self.counter)"
}
}
override func didMove(to view: SKView) {
self.label.fontSize = 60
self.addChild(label)
// let's test it!
self.counter = 123
}
}
4) The actions
Finally we want to build an action that every 2 seconds will increment counter
class GameScene: SKScene {
private let label = SKLabelNode(text: "Score: 0")
private var counter = 0 {
didSet {
self.label.text = "Score: \(self.counter)"
}
}
override func didMove(to view: SKView) {
self.label.fontSize = 60
self.addChild(label)
// 1 wait action
let wait2Seconds = SKAction.wait(forDuration: 2)
// 2 increment action
let incrementCounter = SKAction.run { [weak self] in
self?.counter += 1
}
// 3. wait + increment
let sequence = SKAction.sequence([wait2Seconds, incrementCounter])
// 4. (wait + increment) forever
let repeatForever = SKAction.repeatForever(sequence)
// run it!
self.run(repeatForever)
}
}
The following code creates a new thread and waits 2 seconds before doing something on the main thread:
BOOL continueIncrementingScore = YES;
dispatch_async(dispatch_queue_create("timer", NULL);, ^{
while(continueIncrementingScore) {
[NSThread sleepForTimeInterval:2];
dispatch_async(dispatch_get_main_queue(), ^{
// this is performed on the main thread - increment score here
});
}
});
Whenever you want to stop it - just set continueIncrementingScore to NO