I'm totally new to iOS development and am working on an iPhone cooking app that gives the user the choice of three 'timer' options. The first timer runs for 6 mins, the second for 8.5 mins and the last for 11 mins.
Once the timer finishes counting down it plays an audio file and displays a message within the app screen. Everything works perfectly, except that I've discovered in testing that the timer stops running while the user goes to another app (e.g. checking email, using Safari, etc). Obviously, this defeats the purpose of the app as the user needs to know when the timer is finished so they can do the next step (e.g. remove a saucepan from the stove).
I've researched background modes and am getting confused. It seems that I literally have no reason (according to Apple) to run this app in the background (i.e. it's not playing music, using locations services, etc). I also keep reading that there's a 10 min limit to running in the background otherwise.
I also come across the idea of local and remote notifications, but the page I was referred to no longer exists on Apple's developer site. I'm now at a loss and confused.
Is there a way for me to actually get this app to work in the background for up to 11 minutes? If so, how?
Here's an update. I've been trying to get my head around Local Notifications and Background Tasks.
LOCAL NOTIFICATIONS
This showed some promise, but I'm not sure how I would implement this in my scenario? How would I ensure the right amount of time passes before the notification appears/plays a sound?
For example, the user selects the button for 'soft boiled eggs' at exactly 12:00:00pm and the app starts a counter for 6 mins. At 12:01:20pm the user reads an email, taking 30 seconds before putting the phone down at 12:01:50 to read the paper. Let's assume at 12:02:50 the phone goes into lock mode, how do I ensure the local notification triggers 3mins and 10secs later to make up the whole 6mins and play the sound file notifying the user their eggs are ready.
BACKGROUND TASKS
This may work for my scenario if I can start and restart background tasks to allow my timer to complete before playing the sound.
Below is a snippet of my code (relating to the eggs example above) that I hope will help put my app in context:
#IBAction internal func ButtonSoft(sender: UIButton) {
counter = 360
TimerDisplay.text = String("06:00")
timer = NSTimer.scheduledTimerWithTimeInterval(1, target: self, selector: Selector("updateCounter"), userInfo: "Eggs done!!", repeats: true)
ButtonSoft.alpha = 0.5
ButtonMedium.alpha = 0.5
ButtonHard.alpha = 0.5
ButtonSoft.enabled = false
ButtonMedium.enabled = false
ButtonHard.enabled = false
}
#IBAction internal func ButtonMedium(sender: UIButton) {
counter = 510
TimerDisplay.text = String("08:30")
timer = NSTimer.scheduledTimerWithTimeInterval(1, target: self, selector: Selector("updateCounter"), userInfo: "Eggs done!!", repeats: true)
ButtonSoft.alpha = 0.5
ButtonMedium.alpha = 0.5
ButtonHard.alpha = 0.5
ButtonSoft.enabled = false
ButtonMedium.enabled = false
ButtonHard.enabled = false
}
#IBAction internal func ButtonHard(sender: UIButton) {
counter = 660
TimerDisplay.text = String("11:00")
timer = NSTimer.scheduledTimerWithTimeInterval(1, target: self, selector: Selector("updateCounter"), userInfo: "Eggs done!!", repeats: true)
ButtonSoft.alpha = 0.5
ButtonMedium.alpha = 0.5
ButtonHard.alpha = 0.5
ButtonSoft.enabled = false
ButtonMedium.enabled = false
ButtonHard.enabled = false
}
func stopTimer() {
if counter == 0 {
timer.invalidate()
}
}
func updateCounter() {
counter--
let seconds = counter % 60
let minutes = (counter / 60) % 60
let strMinutes = minutes > 9 ? String(minutes) : "0" + String(minutes)
let strSeconds = seconds > 9 ? String(seconds) : "0" + String(seconds)
if seconds > 0 {
TimerDisplay.text = "\(strMinutes):\(strSeconds)"
}
else {
stopTimer()
TimerDisplay.text = String("Eggs done!!")
SoundPlayer.play()
}
}
#IBAction func ButtonReset(sender: AnyObject) {
timer.invalidate()
stopTimer()
TimerDisplay.text = String("Choose your eggs:")
ButtonSoft.alpha = 1.0
ButtonMedium.alpha = 1.0
ButtonHard.alpha = 1.0
ButtonSoft.enabled = true
ButtonMedium.enabled = true
ButtonHard.enabled = true
}
In terms of running background tasks, I've come across the following example of code.
To create the background task:
func someBackgroundTask(timer:NSTimer) {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), { () -> Void in
println("do some background task")
dispatch_async(dispatch_get_main_queue(), { () -> Void in
println("update some UI")
})
})
}
And the below line of code to (I think) use a timer to run the above function:
var timer = NSTimer(timeInterval: 1.0, target: self, selector: "someBackgroundTask:", userInfo: nil, repeats: true)
And the below code to stop it all:
timer.invalidate()
So, how would I adapt this for my scenario? If this isn't possible, how would I use local notifications in my app?
Or do I just give up on the iPhone version of this app (my Apple Watch version seems to work fine).
In a word, no. You can ask for background time, but recent versions of iOS give you 3 minutes.
If you are a background sound playing app or navigation app you are allowed to run in the background for longer, but you have to ask for those permissions and the app review board will check.
The bottom line is that third parties can't really do a timer app that counts down an arbitrary time longer than 3 minutes.
You might want to use timed local notifications. You can make those play a sound when they go off. Search in the Xcode docs on UILocalNotification.
Edit in Aug 2020: I would no longer recommend this approach.
I have had some success by starting a background task, then setting a timer for just under a minute. When the timer fires, I start a new background task and end the old one. I just keep rolling over the background task, creating a new one every minute.
Related
This question already has an answer here:
Show Multiple Counter in label but at same screen (Swift, IOS)
(1 answer)
Closed 5 months ago.
In my app I have spots on which a user can tap to unlock them, a user can unlock multiple spots at the same time and there can be different number of spots every time, whenever a user unlocks a spot i want to show a timer of 60 seconds on the unlocked spot, So, there can be multiple timers on same screen all having different counts. For example if a user unlocks 2 spots with the difference of 30 seconds. the timer on spot 1 should be 30 seconds left and spot 2 should be 60 seconds left. I only have 1 function for doing this task I can easily show timer on 1 spot but showing them on multiple places, multiple times is very complex. I am sharing a picture too for reference. any help would be appreciated.
timer = Timer.scheduledTimer(timeInterval: 1.0, target: self, selector: #selector(updateCounter), userInfo: nil, repeats: true)
#objc func updateCounter() {
if counter > 0 {
let time = Int(counter/60)
let dec = Int(counter.truncatingRemainder(dividingBy: 60))
spotTiming = String(time) + ":" + String(dec)
}
counter -= 1
}
i want to show timers where counter is written
You can use a timer as a trigger to update the UI every x period (example: 0.5s). You save time remaining every spots at a variable as Array and reduce each element when timer was triggered.
let step = TimeInterval(0.5)
timer = Timer.scheduledTimer(timeInterval: step, target: self, selector: #selector(updateCounter), userInfo: nil, repeats: true)
var timer_remainings = [TimeInterval]()
#objc func updateCounter() {
for (index, item) in timer_remainings.enumerated() {
timer_remainings[index] = max(0.0, timer_remainings - step)
}
// Update UI
updateUI()
}
[New to Swift]
I am making an app on Swift 5 that displays time left before my next task is to start.
My app:
Task 1: Start at 9.30 am
Task 2: Start at 10.15 am
Let's say Current Time: 09.00 am
Countdown: Time before next task start = 00:30:02 --> 00:30:01 ...
I would like to display countdown of the nearest task.
Currently, I can only have one countdown timer on my viewonLoad() that picks up the current countdown. It continues and once it finishes it does not start next timer after it has finished. I understand I have to deal with Background state at a later date, but since I am slowly starting. My idea is to make sure I can initiate next countdown timer once my current one has expired.
So at any point when I open my app, it will always display countdown till my next task.
var count = 30 // calculation simplified ... this data is calc from two different date obj
override func viewDidLoad() {
super.viewDidLoad()
timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(update), userInfo: nil, repeats: true)
}
#objc func update() {
if(count > 0) {
count = count - 1
countdownLabel.text = String(count)
}else{
timer!.invalidate()
getNextTaskTimes()
}
}
func getNextTaskTimes(){
count = 5 // not working
}
How can I achieve this please? Also, is there a better way of doing this? Given that, my user may choose to close the app and come back at a later time, I still would want my countdown to continue in the background.
Any help is appreciated. :D Thanks
I realized the above code works. I just need to make sure I do not invalidate the timer in the else block. Invalidating the timer just kills it.
So I've been searching Stackoverflow for a good solution on how to update label if the time change, but so far the results have been unsatisfactory. Most use a timer, and that's not what I want.
What I want is if the status bar time is 8:52 PM and if some arbitrary bus leaves at 9:00 PM then I want the label to show 8 min. Then, if the time changes to 8:53 PM I want the label to show 7 min.
I'd prefer some sort of notification or delegation method rather than setting a timer.
If anyone has any suggestions on what I should do (or any 3rd party libraries that could notify if the time changes) that would be great!
You only have limited choices. If your app is not running in the foreground, you're limited to local notifications, which display an alert to the user. Using those once a minute would be awful for the user.
When your app comes to the foreground you can start a timer that is timed to fire every minute on the minute and use that to update a label in your app:
weak var timer: Timer?
func startTimer() {
timer?.invalidate()
let interval = Double(Date().timeIntervalSinceReferenceDate)
let delay = 60 - fmod(interval, 60.0)
message.text = "Delay = \(delay)"
//Create a "one-off" timer that fires on the next even minute
let _ = Timer.scheduledTimer(withTimeInterval: delay, repeats: false ) { timer in
self.message.text = "\(Date())"
self.timer = Timer.scheduledTimer(withTimeInterval: 60.0,
repeats: true ) { timer in
//Put your repeating code here.
self.message.text = "\(Date())"
}
}
}
I have a game that I'm adding a timer when the player loses, of 3 hours for now. When the time passes out, the player can get free 15 coins of my game.
For now, I'm using NSTimer. The problem is that whenever i put the game in background mode, the timer stops as I read in the documentation. So the timing would run only for game playtime and not always.
How can I achieve this? I will even send a Notification to the user when that timer ended so the user can get into my game and grab the coins for free.
Here's what i have so far:
var coinCountdown = 10800 // 3hr = 10800
var coinCurrentTimer = NSTimer.init()
var coinCountdownLabel = SKLabelNode()
Then in the init of my SKScene I do:
self.coinCurrentTimer = NSTimer.scheduledTimerWithTimeInterval(1.0, target: self, selector: #selector(GameScene.updateCoinCountdown), userInfo: nil, repeats: true)
And this is the updateCoinCountdown method:
func updateCoinCountdown() {
if(self.coinCountdown > 0){
var hour = String(self.coinCountdown / 3600)
var minutes = String((self.coinCountdown % 3600) / 60)
var seconds = String((self.coinCountdown % 3600) % 60)
if Int(hour) < 10 {
hour = "0" + hour
}
if Int(minutes) < 10 {
minutes = "0" + minutes
}
if Int(seconds) < 10 {
seconds = "0" + seconds
}
self.coinCountdownLabel.text = hour + ":" + minutes + ":" + seconds
self.coinCountdown = self.coinCountdown - 1
let defaults = NSUserDefaults.standardUserDefaults()
if defaults.valueForKey("coinCountdown") != nil {
defaults.setValue(self.coinCountdown, forKey: "coinCountdown")
}
}else{
print("Done! Send notification and show get coin button")
}
}
Any hint would be very appreciated.
I haven't actually done something like this yet, but I have an idea how it could be done.
When the 'timer' is supposed to start you could schedule a local silent notification that would alert the app that the time has passed or an event has been triggered.
When the app receives this scheduled notification it could run some basic checks to see if anything has happened since the notification was scheduled that may cancel it out, if not then it could trigger an immediate local notification to alert the user of the event.
See the documentation for Local and remote notifications it even has a reference to an event in which the "TIMER_EXPIRED" which is similar to my suggestion above.
EDIT: Looking over the documentation it doesn't look like you can schedule a silent notification, but in theory this could still work for your case.
I did this code in swift (2.0)
var timer: dispatch_source_t!
var a = 0
func startTimer() {
let queue = dispatch_queue_create("com.domain.app.timer", nil)
timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue)
dispatch_source_set_timer(timer, DISPATCH_TIME_NOW, 1 * NSEC_PER_SEC, 1 * NSEC_PER_SEC) // every 60 seconds, with leeway of 1 second
dispatch_source_set_event_handler(timer) {
self.a = self.a+1
print( self.a)
}
dispatch_resume(timer)
}
func stopTimer() {
dispatch_source_cancel(timer)
timer = nil
}
the problem is that this code work well on the emulator , I see in the consol a number incrementing every second, and when I go the the home page of the emulator the number is still incrementing ,
On the device, the number stop incrementing as soon as we quit the application context .
so how can I make it keep incrementinf on the device ?
By default iOS applications do not support background execution. You can start execute finite-length background tasks with beginBackgroundTaskWithName method or you have to enable special background modes like voip, location, remote notifications to run in background. (The use of those modes are reviewed by Apple when you submit app to Appstore)
More info: Background execution documentation
I agree that it is quite misleading that it works on simulator, when it is not supposed to.