Game Timer using NSDate with NSTimer? swift - swift

I want to start a timer at a specific date and time, then use that start time as a game timer for the rest of the game. Using "timeIntervalSinceDate" will give me seconds but then trying to get the seconds to display on the gameTimerLabel won't work. I might be coming at this the wrong way. Any advice is welcome.
override func viewWillAppear(animated: Bool) {
let dateFormatter = NSDateFormatter()
dateFormatter.dateFormat = "EEE, dd MMM yyyy HH:mm:ss zzz"
let dateAsString1 = "Fri, 1 April 2016 11:30:00 MST"
let date1 = dateFormatter.dateFromString(dateAsString1)!
var currentTime = NSDate()
var counter = 0
gameTimerLabel.text = String(counter)
gameTimerLabel.text = counter //<- Error:Cannot assign value to type 'Int' to type 'String?'
counter = date1.timeIntervalSinceDate(currentTime) //<- Error:Cannot assign value of type 'NSTimeInterval' (aka 'Double') to type 'Int'
}

A couple of things here
First, when you declare counter, it's inferred to be of type Int
var counter = 0
You can declare it as a double by adding a .0 or specifying it's type:
var counter: NSTimeInternval = 0.0
Next, you can use string interoperability to display the count variable in a string, like this:
gameTimerLabel.text = "\(counter)"
Here's an example view controller using an NSTimer as a counter, it counts in seconds:
class ViewController: UIViewController {
// Setup a timer and a counter for use
var timer = NSTimer()
var counter : NSTimeInterval = 0
#IBOutlet weak var label: UILabel!
// Invalidest the timer, resets the counter and udpates the label
#IBAction func resetTimer(sender: AnyObject) {
// Invalidate the timer, reset the label.
self.timer.invalidate()
self.label.text = ""
self.counter = 0
}
// A button that when pressed starts and stops the timer
// There's no pause/resume, so it's invalidated & created again
// But the counter value reamins the same
#IBAction func timerBttnTouched(sender: AnyObject) {
if self.timer.valid {
self.timer.invalidate()
} else {
self.setupTimer()
}
}
// Does the actual counting everytime the timer calls this method
func timerFired() {
self.counter += 1
self.label.text = "\(self.counter)"
}
// Setups a timer, adds it to the run loop and specifies what method should fire when the timer fires
func setupTimer() {
// Setupt the timer, this will call the timerFired method every second
self.timer = NSTimer(
timeInterval: 1,
target: self,
selector: #selector(self.timerFired),
userInfo: nil,
repeats: true)
// Add the timer to the run loop
NSRunLoop.currentRunLoop().addTimer(
self.timer,
forMode: NSDefaultRunLoopMode)
}
}
An important thing to note when using timers is they may not always be called exactly when you need them to, this should be taken into account according to your desired precision when using a timer.
As discussed in comments, here's the solution using a timer to fire a method that compares two dates and uses a NSDateComponentsFormatter to generate a string for display. The initial date is generated in viewDidLoad but can be created anywhere:
class ViewController: UIViewController {
// Setup a timer and a counter for use
var timer = NSTimer()
var counter : NSTimeInterval = 0
var startDate: NSDate?
override func viewDidLoad() {
// Set the initial date
self.startDate = NSDate()
}
#IBOutlet weak var label: UILabel!
// Invalidest the timer, resets the counter and udpates the label
#IBAction func resetTimer(sender: AnyObject) {
// Invalidate the timer, reset the label.
self.timer.invalidate()
self.label.text = ""
self.counter = 0
}
// A button that when pressed starts and stops the timer
// There's no pause/resume, so it's invalidated & created again
// But the counter value reamins the same
#IBAction func timerBttnTouched(sender: AnyObject) {
if self.timer.valid {
self.timer.invalidate()
} else {
self.setupTimer()
}
}
// Does the actual counting everytime the timer calls this method
func timerFired() {
let now = NSDate()
let difference = now.timeIntervalSinceDate(self.startDate!)
// Format the difference for display
// For example, minutes & seconds
let dateComponentsFormatter = NSDateComponentsFormatter()
dateComponentsFormatter.stringFromTimeInterval(difference)
self.label.text = dateComponentsFormatter.stringFromTimeInterval(difference)
}
// Setups a timer, adds it to the run loop and specifies what method should fire when the timer fires
func setupTimer() {
// Setupt the timer, this will call the timerFired method every second
self.timer = NSTimer(
timeInterval: 1,
target: self,
selector: #selector(self.timerFired),
userInfo: nil,
repeats: true)
// Add the timer to the run loop
NSRunLoop.currentRunLoop().addTimer(
self.timer,
forMode: NSDefaultRunLoopMode)
}
}

Related

Trying to Save My Timer to UserDefaults When The App Gets "Killed"

This is a timer App and I would like for the timer to pause when the app gets killed and when you reopen the app I would like the timer to display the last known time.
I have setup a struct for the data to be saved to.
let defaults = UserDefaults.standard
private enum TimeData {
static let allCurrentTime = "allCurrentTime"
}
override func viewDidLoad() {
super.viewDidLoad()
checkForSavedTimer()
marksSavedData()
theTimer.text = "00:00:00"
theTimer.font = UIFont(name: "Menlo-BoldItalic", size: 40)
marksTableView.isHidden = false
}
//I set the timer info to the struct (see below)
#objc func updateTimerLabel() {
seconds += 1
if seconds == 60 {
minutes += 1
seconds = 0
}else if minutes == 60 {
hours += 1
minutes = 0
}
timerString = "\(hours):\(minutes):\(seconds)"
theTimer.text = timerString
defaults.set(theTimer.text, forKey: TimeData.allCurrentTime)
}
// I then created a function which I called in the viewdidload above
func checkForSavedTimer() {
let allCurrentTime = defaults.value(forKey: TimeData.allCurrentTime) as? String ?? ""
theTimer.text = allCurrentTime
}
I thought this would take the string from theTimer save it to the UserDefaults and then in viewDidLoad bring it back out and save it to "theTimer.text" BUT the result is 00:00:00
When you are calling your checkForSavedTimer() function, it is probably getting the correct value from UserDefaults but then you are setting the timer text back to 00:00:00 two lines later. You will need to remove that line and add some logic within your checkForSavedTimer() function to set the value to 00:00:00 if the value from UserDefaults is nil
override func viewDidLoad() {
super.viewDidLoad()
checkForSavedTimer()
marksSavedData()
// Remove this -> theTimer.text = "00:00:00"
theTimer.font = UIFont(name: "Menlo-BoldItalic", size: 40)
marksTableView.isHidden = false
}
func checkForSavedTimer() {
// Set the timer text to the value from UserDefaults or "00:00:00" if the value is nil
theTimer.text = defaults.string(forKey: TimeData.allCurrentTime) ?? "00:00:00"
}
Also it is better to use .string(forKey:) to get a String from UserDefaults because then the value is already typed for you and therefore you don't need to add as? String

Trying to subtract the input of a UITextField by one but I keep getting an error

My code is here:
#IBAction func updateTime(){
let inputVal: String = textMinutes.text!
var inputInt = Int(inputVal)
timerLabel?.text = "\(inputInt)"
if inputInt != 0 {
inputInt -= 1
}
else {
endTimer()
}
So on the inputInt -=1 I keep getting the error "Cannot convert value of type 'Int?' to expected argument type 'inout Int'". I am not sure how to fix this or if my code is even correct here. This is all for this:
func startTimer() {
countdownTimer = Timer.scheduledTimer(timeInterval: 1.0, target: self, selector: #selector(ViewController.updateTime), userInfo: nil, repeats: true)
}
which is for the timer I am trying to make. I am pretty new to this so if there is anything else you need to see I can post it. I've looked all over for solutions but I have not found anything. Thanks in advance.
Your logic is bad. You don't want to change text of some label every second according to same other text every time.
You should create global variable for time, let's say interval
class ViewController: UIViewController {
var interval = 0
...
}
then when you start timer, make sure that user wrote number and if does, start timer and set interval
func startTimer() {
if let interval = Int(textMinutes.text!) {
self.interval = interval
timerLabel?.text = String(interval)
countdownTimer = Timer.scheduledTimer(timeInterval: 1.0, target: self, selector: #selector(ViewController.updateTime), userInfo: nil, repeats: true)
} else {
... // handle case if user didn’t write integer
}
}
then every time timer updates, just decrease interval and change text of your text field by this interval variable, then when interval is 0, invalidate your timer
#IBAction func updateTime() {
interval -= 1
timerLabel?.text = String(interval)
if interval == 0 { endTimer() }
}

How to generate a random number between 1 and 75 repeating every 10 seconds in Swift

I have the following code in Swift trying to get a simple random number generator as a simulator for a game.
var randomNumber = 0
override func viewDidLoad() {
super.viewDidLoad()
randomNumber = Int(arc4random_uniform(74) + 1)
label.text = "\(randomNumber)"
}
I'm new to programming Swift but I know to use timer() and import Foundation to use the timer function but I'm not sure how to implement and make it so a new number appears in the label every 10 seconds. Thanks for any help.
Use a Timer with an interval of 10 seconds to pull a new number from an array of numbers. Remove the number from the array so that you don't call the same number twice. When the stop button is pressed, or you are out of numbers call invalidate on the timer to stop it.
class BingoCaller: UIViewController {
#IBOutlet weak var label: UILabel!
var numbers = Array(1...75)
let letters = ["B", "I", "N", "G", "O"]
var timer: Timer?
override func viewDidLoad() {
timer = Timer.scheduledTimer(withTimeInterval: 10, repeats: true) { timer in
let index = Int(arc4random_uniform(UInt32(self.numbers.count)))
let number = self.numbers.remove(at: index)
self.label.text = "\(self.letters[(number - 1) / 15])-\(number)"
if self.numbers.isEmpty {
timer.invalidate()
}
}
}
#IBAction func stop(_ button: UIButton) {
timer?.invalidate()
}
}
Suggestions for next steps:
Add the numbers that have been pulled to a second array. Use that array to populate a tableView so that Gran is able to review the numbers when someone calls "Bingo!".
Use AVSpeechSynthesizer to have the iPhone actually speak the numbers.
Add a reset button to start a new game. Initialize the numbers to Array(1...75), the calledNumbers to [] and start again. It's a good idea to move the Timer loop to its own function so that it can be called from a start button.
You can define a helper array, that would let you check if that number was already returned:
var array = [Int]()
var timer: Timer?
override func viewDidLoad() {
super.viewDidLoad()
timer = Timer.scheduledTimer(timeInterval: 10, target: self, selector: #selector(ViewController.timerFunction), userInfo: nil, repeats: true)
}
func timerFunction(){
var n = arc4random_uniform(75) + 1
while array.contains(Int(n)){
n = arc4random_uniform(75) + 1
}
array.append(Int(n))
label.text = String(n)
if array.count == 75{
timer?.invalidate()
}
}
This way you are sure that the timer is invalidated when all the numbers have already been used and also avoid index-removal errors.

How to play a sound/vibrate on a timer if the app is closed without sending a notification?

I am building a workout timer in my app. Workflow is:
User hits "start timer" button
Timer counts down for 90
seconds
Timer ends after 90 seconds and triggers PlayAlertSound
to vibrate
This only works if the app is open, and I don't expect my user to be looking at my app when the timer reaches 0. I can send a notification, but then I'm sending dozens of notifications over the course of a single workout. Personally, I don't like having lots of notifications from a single app. It feels noisy.
Is there a way to have the app send a vibration while the app is closed without sending a notification?
I tried to ask for background resources so my timer runs after closing the app, but even if the timer continues to run, it won't fire the vibration until I open the app, i.e., the user needs to be looking at their phone.
Here is my code:
class TimerViewController: UIViewController {
#IBOutlet weak var startTimerButton: UIButton!
#IBOutlet weak var timerLabel: UILabel!
#IBOutlet weak var resetTimerButton: UIButton!
var timer = NSTimer()
let timeInterval:NSTimeInterval = 0.05
let timerEnd:NSTimeInterval = 90
var timeCount:NSTimeInterval = 0.0
var backgroundTaskIdentifier: UIBackgroundTaskIdentifier?
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func startTimerButtonTapped(sender: UIButton) {
backgroundTaskIdentifier = UIApplication.sharedApplication().beginBackgroundTaskWithExpirationHandler({
UIApplication.sharedApplication().endBackgroundTask(self.backgroundTaskIdentifier!)
})
if !timer.valid {
timerLabel.text = timeString(timeCount)
timer = NSTimer.scheduledTimerWithTimeInterval(timeInterval,
target: self,
selector: #selector(TimerViewController.timerDidEnd(_:)),
userInfo: "Time is up!!",
repeats: true) //repeating timer in the second iteration
}
}
#IBAction func resetTimerButtonTapped(sender: UIButton) {
timer.invalidate()
resetTimeCount()
timerLabel.text = timeString(timeCount)
}
func resetTimeCount(){
timeCount = timerEnd
}
func timeString(time:NSTimeInterval) -> String {
let minutes = Int(time) / 60
//let seconds = Int(time) % 60
let seconds = time - Double(minutes) * 60
let secondsFraction = seconds - Double(Int(seconds))
return String(format:"%02i:%02i.%02i",minutes,Int(seconds),Int(secondsFraction * 100.0))
}
func timerDidEnd(timer:NSTimer){
//timerLabel.text = timer.userInfo as? String
//timer that counts down
timeCount = timeCount - timeInterval
if timeCount <= 0 {
timerLabel.text = "Time is up!!"
timer.invalidate()
AudioServicesPlayAlertSound(kSystemSoundID_Vibrate)
//pushNotification()
} else { //update the time on the clock if not reached
timerLabel.text = timeString(timeCount)
}
}
//
// func pushNotification() {
// let notification = UILocalNotification()
// notification.alertAction = "Go back to App"
// notification.alertBody = "This is a Notification!"
// notification.fireDate = NSDate(timeIntervalSinceNow: 1)
// UIApplication.sharedApplication().scheduleLocalNotification(notification)
// }
//
}
Unfortunately it's not possible to wake up your app when a local notification is received - and that's a good thing, otherwise it would be abused a lot.
But you can play audio in background like this:
How to Play Audio in Background Swift?
Try to play 90 seconds of silence, and then play your alarm sound.

Looping Label Change Background task in Swift

I was looking for a way in Swift to loop a changing label that cycles through an array of Strings. Most ways I've tried have stopped all other tasks while the loop was running.
You're view controller could look something like this:
class ViewController: UIViewController {
#IBOutlet weak var cycleLabel: UILabel!
var strings: [String]!
var timer: NSTimer!
var index: Int = 0
override func viewDidLoad() {
super.viewDidLoad()
self.strings = ["Lorem", "ipsum", "dolor", "sit", "amet", "consectetur", "adipiscing", "elit", "Vestibulum", "erat", "lacus", "congue"]
}
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
self.cycleLabel.text = self.strings[self.index]
}
#IBAction func beginCyclingTapped(sender: UIButton) {
let interval = 1.0
if self.timer.valid {
self.timer.invalidate()
}
self.timer = NSTimer.scheduledTimerWithTimeInterval(interval, target: self, selector: "updateLabel", userInfo: nil, repeats: true)
}
func updateLabel() {
self.index += 1
self.cycleLabel.text = self.strings[self.index % self.strings.count]
}
}
This code will update the label text to the next string in the strings property every one second. If you'd like a different interval, change the interval constant in the beginCyclingTapped(:) method. The label will start restart from the beginning of the strings array after it reaches the last element in that array. The if statement in beginCyclingTapped(:) ensures that multiple timers are not scheduled to update that label, which would result in the label getting updated more frequently than desired. Also, make sure you hook up the IBOutlet to a UILabel on your Storyboard.