4

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).

Monomeeth
  • 753
  • 3
  • 13
  • 29

2 Answers2

8

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.

Duncan C
  • 128,072
  • 22
  • 173
  • 272
  • I will check that out (sounds like that may be the local notifications I couldn't find on Apple's Dev site). I forgot to mention in my post that the timer stops also if the iPhone enters the lock screen (even if the app was in the foreground). Would your suggestion also work in this scenario? Also, just thinking out aloud about the 3 minute limit, but can you request that multiple times? For example, if a timer is supposed to run for 8.5 minutes, set the button to actually run a 3 minute timer followed by another 3 mins followed by 1.5 mins (each time asking for 3 mins background time)? – Monomeeth Jan 24 '16 at 22:52
  • Local notifications work even if your app is terminated. The ONLY time I am aware of that local notifications won't be delivered is if the phone is powered down. (Sounds won't be played if the user has the phone muted, obviously.) – Duncan C Jan 24 '16 at 22:54
  • The penny just dropped. I was looking at local notifications all wrong and realise now that I need to rethink how my app works so that there is effectively no timer, but just a local notification that triggers at the appropriate time. – Monomeeth Jan 26 '16 at 02:53
1

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.

picciano
  • 22,341
  • 9
  • 69
  • 82
  • Is there something you can refer me to on how to achieve this? I'm new to all this and don't really know where to start. – Monomeeth Jan 25 '16 at 06:33
  • could you explain how this can be done in more detail? – Wink Nov 10 '16 at 11:46
  • After about a year, as I reflect back on this, my recommendation is to use local notifications for this type of behavior. If you do want to pursue the above solution, take a look at http://blog.dkaminsky.info/2013/01/27/keep-your-ios-app-running-in-background-forever/ or http://hayageek.com/ios-long-running-background-task/ But again, I no longer recommend this approach. – picciano Nov 10 '16 at 17:44
  • 1
    That approach may work, but it will drain the user's battery quite quickly, and may also cause Apple to reject your app. (When your app is running code it keeps the CPU running at full power, which is a huge power drain. Apple goes to a lot of effort to protect user's battery life, and to that end imposes limits on 3rd party apps like not allowing them to run in the background (or when the phone is locked) indefinitely. – Duncan C Nov 15 '17 at 00:46
  • @picciano can you please share the code snippet to understand more? – Van Aug 06 '20 at 06:47
  • @van I would no longer recommend this approach. – picciano Aug 06 '20 at 12:40